diff --git a/VIPSWeb/local_settings_sample.py b/VIPSWeb/local_settings_sample.py
index bb7d993b9a1cb27036fb0c25fc568a5fd9453b68..1672476ac32143ae320fba72379765d7438c52f5 100644
--- a/VIPSWeb/local_settings_sample.py
+++ b/VIPSWeb/local_settings_sample.py
@@ -57,7 +57,6 @@ VIPSLOGIC_LANGUAGE_CODE = "en"
 # In a Windows environment this must be set to your system time zone.
 TIME_ZONE = 'Europe/Oslo'
 
-
 DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
@@ -104,6 +103,9 @@ MAP_CENTER_LONGITUDE = 14.1
 MAP_CENTER_LATITUDE = 65.4
 # The zoom level (1-15, 1 is world wide view, 15 is greatest zoom)
 MAP_ZOOMLEVEL = 4 
+# The height of the map in pixels on the screen
+MAP_HEIGHT = 500
+
 # The attribution text that appears in a corner of the map
 MAP_ATTRIBUTION = "&copy; <a href='http://www.openstreetmap.org'>OpenStreetMap</a> contributors"
 
@@ -116,4 +118,32 @@ FRONTPAGE_MESSAGE_TAG_IDS = [1,2,3]
 # CROP_GROUPS=[
 #     {"crop_group_id": 1, "name":{"en":"Fruit", "nb":"Frukt"},"crop_ids":[23,44,53]}
 # ]
-CROP_GROUPS=[]
\ No newline at end of file
+CROP_GROUPS=[]
+
+# Structure for the main (top) menu. Supports one level of child items,
+# which are placed in dropdown menus
+# Sample: 
+# MAIN_MENU=[
+#    {"url":"/forecasts", "label": "Forecasts"},
+#    {"url":"/messages", "label": "Messages"},
+#    {"label": "Other services", "child_items":[
+#        {"url":"http://www.yr.no/", "label": "Weather forecasts"}                                
+#        ]
+#    }
+#]
+#
+MAIN_MENU=[]
+
+# Text and formatting (HTML) for the footer to be
+# shown on all web pages. Language code must be specified
+# Language selection is as follows:
+# 1. The user's selected language
+# 2. The default language for the site (Settings.LANGUAGE_CODE)
+# 3. English
+# 4. The first language in the list of texts
+# Example:
+# FOOTER HTML = {
+#        "en": "VIPS is an automatic forecasting system for agricultural pests and diseases",
+#        "nb": "VIPS er et automatisk varslingssystem for skadegjørere i landbruket"
+# }
+FOOTER_HTML= {}
\ No newline at end of file
diff --git a/VIPSWeb/locale/bg/LC_MESSAGES/django.mo b/VIPSWeb/locale/bg/LC_MESSAGES/django.mo
index e58a57713c1772e32de9bf34623f4ec133da141a..37020358b82f9d7e91b350bb63c64cd5446d0d8e 100644
Binary files a/VIPSWeb/locale/bg/LC_MESSAGES/django.mo and b/VIPSWeb/locale/bg/LC_MESSAGES/django.mo differ
diff --git a/VIPSWeb/locale/bg/LC_MESSAGES/django.po b/VIPSWeb/locale/bg/LC_MESSAGES/django.po
index 0f974efa0b06200f2619d0aeeba75e894d056925..2fda5ebf041393c4214e0b1f1fb1983acf1543d2 100644
--- a/VIPSWeb/locale/bg/LC_MESSAGES/django.po
+++ b/VIPSWeb/locale/bg/LC_MESSAGES/django.po
@@ -19,7 +19,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-04-29 13:13+0200\n"
+"POT-Creation-Date: 2014-12-28 15:16+0100\n"
 "PO-Revision-Date: 2014-05-14 09:31+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,46 +30,44 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Poedit 1.6.5\n"
 
-#: templates/base.html:45
+#: templates/base.html:50
 msgid "Toggle navigation"
 msgstr "Навигация"
 
-#: templates/base.html:56
+#: templates/base.html:86
+msgid "This is the default page contents."
+msgstr "Това е съдържанието на страницата по подразбиране. "
+
+#: templates/index.html:25
+msgid "Welcome"
+msgstr "Добре дошли"
+
+#: templates/index.html:61
+msgid "Crops"
+msgstr ""
+
+#: templates/index.html:72
 msgid "Forecasts"
 msgstr "Прогноза"
 
-#: templates/base.html:57 templates/index.html:51
+#: templates/index.html:119
 msgid "Messages"
 msgstr "Съобщения"
 
-#: templates/base.html:59
-msgid "Dropdown"
-msgstr "Падащо меню"
-
-#: templates/base.html:61
-msgid "Action"
-msgstr "Действие"
+#~ msgid "Dropdown"
+#~ msgstr "Падащо меню"
 
-#: templates/base.html:62
-msgid "Another action"
-msgstr "Друго действие"
+#~ msgid "Action"
+#~ msgstr "Действие"
 
-#: templates/base.html:63
-msgid "Something else here"
-msgstr "Друго нещо"
+#~ msgid "Another action"
+#~ msgstr "Друго действие"
 
-#: templates/base.html:64
-msgid "Separated link"
-msgstr "Отделна връзка"
+#~ msgid "Something else here"
+#~ msgstr "Друго нещо"
 
-#: templates/base.html:65
-msgid "One more separated link"
-msgstr "Друга отделна връзка"
+#~ msgid "Separated link"
+#~ msgstr "Отделна връзка"
 
-#: templates/base.html:123
-msgid "This is the default page contents."
-msgstr "Това е съдържанието на страницата по подразбиране. "
-
-#: templates/index.html:22
-msgid "Welcome"
-msgstr "Добре дошли"
+#~ msgid "One more separated link"
+#~ msgstr "Друга отделна връзка"
diff --git a/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.mo b/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.mo
index f25b7ea19b5ba176972591ac1f3d0223752272e0..ece304056becf8ff0c50b25cf376d7cabcf8bffe 100644
Binary files a/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.mo and b/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.mo differ
diff --git a/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.po b/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.po
index de9fdff32628874ebb7a4360d9bb7133f804e527..b5acc8e079e16acdc983a788e0e0a13734e941e5 100644
--- a/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.po
+++ b/VIPSWeb/locale/bg/LC_MESSAGES/djangojs.po
@@ -19,7 +19,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-01-16 14:40+0100\n"
+"POT-Creation-Date: 2014-12-29 21:08+0100\n"
 "PO-Revision-Date: 2014-05-14 09:33+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,10 +30,38 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Poedit 1.6.5\n"
 
-#: static/js/forecastmap.js:35
+#: static/js/forecastmap.js:41
 msgid "Source hostname not defined."
 msgstr "Името на източника не е определно. "
 
-#: static/js/forecastmap.js:170
+#: static/js/forecastmap.js:191
 msgid "No forecasts found for"
 msgstr "Не е открита прогноза за "
+
+#: static/js/frontpage.js:194
+msgid "No forecast available"
+msgstr ""
+
+#: static/js/frontpage.js:196
+msgid "Missing data"
+msgstr ""
+
+#: static/js/frontpage.js:198
+msgid "No risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:200
+msgid "Medium risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:202
+msgid "High risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:204
+msgid "Invalid forecast status"
+msgstr ""
+
+#: static/js/frontpage.js:241
+msgid "Unnamed"
+msgstr ""
diff --git a/VIPSWeb/locale/bs/LC_MESSAGES/django.po b/VIPSWeb/locale/bs/LC_MESSAGES/django.po
index 6f7091affd17030995a65731a3e8cfc44d8d288e..f0ae2cb2f2661da188b99d52a44cf8ae690bbe96 100644
--- a/VIPSWeb/locale/bs/LC_MESSAGES/django.po
+++ b/VIPSWeb/locale/bs/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-10-08 18:20+0200\n"
+"POT-Creation-Date: 2014-12-28 15:16+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -19,50 +19,26 @@ msgstr ""
 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
 
-#: templates/base.html:45
+#: templates/base.html:50
 msgid "Toggle navigation"
 msgstr ""
 
-#: templates/base.html:56
-msgid "Forecasts"
-msgstr ""
-
-#: templates/base.html:57 templates/index.html:64
-msgid "Messages"
-msgstr ""
-
-#: templates/base.html:59
-msgid "Dropdown"
-msgstr ""
-
-#: templates/base.html:61
-msgid "Action"
-msgstr ""
-
-#: templates/base.html:62
-msgid "Another action"
-msgstr ""
-
-#: templates/base.html:63
-msgid "Something else here"
-msgstr ""
-
-#: templates/base.html:64
-msgid "Separated link"
+#: templates/base.html:86
+msgid "This is the default page contents."
 msgstr ""
 
-#: templates/base.html:65
-msgid "One more separated link"
+#: templates/index.html:25
+msgid "Welcome"
 msgstr ""
 
-#: templates/base.html:137
-msgid "This is the default page contents."
+#: templates/index.html:61
+msgid "Crops"
 msgstr ""
 
-#: templates/index.html:22
-msgid "Welcome"
+#: templates/index.html:72
+msgid "Forecasts"
 msgstr ""
 
-#: templates/index.html:51
-msgid "Crops"
+#: templates/index.html:119
+msgid "Messages"
 msgstr ""
diff --git a/VIPSWeb/locale/bs/LC_MESSAGES/djangojs.mo b/VIPSWeb/locale/bs/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000000000000000000000000000000000000..1a2c8c460c1ac8af5c8afb6ab05e7a8883dce958
Binary files /dev/null and b/VIPSWeb/locale/bs/LC_MESSAGES/djangojs.mo differ
diff --git a/VIPSWeb/locale/bs/LC_MESSAGES/djangojs.po b/VIPSWeb/locale/bs/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000000000000000000000000000000000000..01442c2c0b37573e8c4359e1e7da3e4104dd4720
--- /dev/null
+++ b/VIPSWeb/locale/bs/LC_MESSAGES/djangojs.po
@@ -0,0 +1,56 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-12-29 21:08+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: static/js/forecastmap.js:41
+msgid "Source hostname not defined."
+msgstr ""
+
+#: static/js/forecastmap.js:191
+msgid "No forecasts found for"
+msgstr ""
+
+#: static/js/frontpage.js:194
+msgid "No forecast available"
+msgstr ""
+
+#: static/js/frontpage.js:196
+msgid "Missing data"
+msgstr ""
+
+#: static/js/frontpage.js:198
+msgid "No risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:200
+msgid "Medium risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:202
+msgid "High risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:204
+msgid "Invalid forecast status"
+msgstr ""
+
+#: static/js/frontpage.js:241
+msgid "Unnamed"
+msgstr ""
diff --git a/VIPSWeb/locale/fi/LC_MESSAGES/django.mo b/VIPSWeb/locale/fi/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000000000000000000000000000000000..817e469331fb9e343535f496b97c80a642c07282
Binary files /dev/null and b/VIPSWeb/locale/fi/LC_MESSAGES/django.mo differ
diff --git a/VIPSWeb/locale/fi/LC_MESSAGES/django.po b/VIPSWeb/locale/fi/LC_MESSAGES/django.po
new file mode 100644
index 0000000000000000000000000000000000000000..adbe7c1d88a4cf3066145887ae96d7508c98f557
--- /dev/null
+++ b/VIPSWeb/locale/fi/LC_MESSAGES/django.po
@@ -0,0 +1,43 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-12-28 15:16+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: templates/base.html:50
+msgid "Toggle navigation"
+msgstr ""
+
+#: templates/base.html:86
+msgid "This is the default page contents."
+msgstr ""
+
+#: templates/index.html:25
+msgid "Welcome"
+msgstr "Tervetuloa"
+
+#: templates/index.html:61
+msgid "Crops"
+msgstr "Sato"
+
+#: templates/index.html:72
+msgid "Forecasts"
+msgstr "Hälytykset"
+
+#: templates/index.html:119
+msgid "Messages"
+msgstr "Viestit"
diff --git a/VIPSWeb/locale/fi/LC_MESSAGES/djangojs.mo b/VIPSWeb/locale/fi/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000000000000000000000000000000000000..d303f473c56ccf12a53444700ac71aa362d8af92
Binary files /dev/null and b/VIPSWeb/locale/fi/LC_MESSAGES/djangojs.mo differ
diff --git a/VIPSWeb/locale/fi/LC_MESSAGES/djangojs.po b/VIPSWeb/locale/fi/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000000000000000000000000000000000000..555a04058a0f0b5c84d3dd85e2a730084f404979
--- /dev/null
+++ b/VIPSWeb/locale/fi/LC_MESSAGES/djangojs.po
@@ -0,0 +1,55 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-12-29 21:08+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: static/js/forecastmap.js:41
+msgid "Source hostname not defined."
+msgstr ""
+
+#: static/js/forecastmap.js:191
+msgid "No forecasts found for"
+msgstr ""
+
+#: static/js/frontpage.js:194
+msgid "No forecast available"
+msgstr ""
+
+#: static/js/frontpage.js:196
+msgid "Missing data"
+msgstr ""
+
+#: static/js/frontpage.js:198
+msgid "No risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:200
+msgid "Medium risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:202
+msgid "High risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:204
+msgid "Invalid forecast status"
+msgstr ""
+
+#: static/js/frontpage.js:241
+msgid "Unnamed"
+msgstr ""
diff --git a/VIPSWeb/locale/hr/LC_MESSAGES/django.po b/VIPSWeb/locale/hr/LC_MESSAGES/django.po
index 5905a089be2e8c793cbb8dfaa8fd0944c46536ba..2d325c7fd678182326bbfe84806cb590d3bbb157 100644
--- a/VIPSWeb/locale/hr/LC_MESSAGES/django.po
+++ b/VIPSWeb/locale/hr/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-10-10 12:47+0200\n"
+"POT-Creation-Date: 2014-12-28 15:16+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -19,50 +19,26 @@ msgstr ""
 "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
 
-#: templates/base.html:45
+#: templates/base.html:50
 msgid "Toggle navigation"
 msgstr ""
 
-#: templates/base.html:56
-msgid "Forecasts"
-msgstr ""
-
-#: templates/base.html:57 templates/index.html:64
-msgid "Messages"
-msgstr ""
-
-#: templates/base.html:59
-msgid "Dropdown"
-msgstr ""
-
-#: templates/base.html:61
-msgid "Action"
-msgstr ""
-
-#: templates/base.html:62
-msgid "Another action"
-msgstr ""
-
-#: templates/base.html:63
-msgid "Something else here"
-msgstr ""
-
-#: templates/base.html:64
-msgid "Separated link"
+#: templates/base.html:86
+msgid "This is the default page contents."
 msgstr ""
 
-#: templates/base.html:65
-msgid "One more separated link"
+#: templates/index.html:25
+msgid "Welcome"
 msgstr ""
 
-#: templates/base.html:137
-msgid "This is the default page contents."
+#: templates/index.html:61
+msgid "Crops"
 msgstr ""
 
-#: templates/index.html:22
-msgid "Welcome"
+#: templates/index.html:72
+msgid "Forecasts"
 msgstr ""
 
-#: templates/index.html:51
-msgid "Crops"
+#: templates/index.html:119
+msgid "Messages"
 msgstr ""
diff --git a/VIPSWeb/locale/hr/LC_MESSAGES/djangojs.mo b/VIPSWeb/locale/hr/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000000000000000000000000000000000000..b924eee66afd69f6e9ba9c31dc6471d7907b2884
Binary files /dev/null and b/VIPSWeb/locale/hr/LC_MESSAGES/djangojs.mo differ
diff --git a/VIPSWeb/locale/hr/LC_MESSAGES/djangojs.po b/VIPSWeb/locale/hr/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000000000000000000000000000000000000..ac123f1aaf0bb352de2568953c5f71fb4325eef4
--- /dev/null
+++ b/VIPSWeb/locale/hr/LC_MESSAGES/djangojs.po
@@ -0,0 +1,56 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-12-29 21:08+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+#: static/js/forecastmap.js:41
+msgid "Source hostname not defined."
+msgstr ""
+
+#: static/js/forecastmap.js:191
+msgid "No forecasts found for"
+msgstr ""
+
+#: static/js/frontpage.js:194
+msgid "No forecast available"
+msgstr ""
+
+#: static/js/frontpage.js:196
+msgid "Missing data"
+msgstr ""
+
+#: static/js/frontpage.js:198
+msgid "No risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:200
+msgid "Medium risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:202
+msgid "High risk of infection"
+msgstr ""
+
+#: static/js/frontpage.js:204
+msgid "Invalid forecast status"
+msgstr ""
+
+#: static/js/frontpage.js:241
+msgid "Unnamed"
+msgstr ""
diff --git a/VIPSWeb/locale/nb/LC_MESSAGES/django.mo b/VIPSWeb/locale/nb/LC_MESSAGES/django.mo
index fb17f068e04e3f43e3b8adbd97bf2ae8febdae5d..8eeb3e7ced62b5fb8b0aa59c679c843e8e01c46c 100644
Binary files a/VIPSWeb/locale/nb/LC_MESSAGES/django.mo and b/VIPSWeb/locale/nb/LC_MESSAGES/django.mo differ
diff --git a/VIPSWeb/locale/nb/LC_MESSAGES/django.po b/VIPSWeb/locale/nb/LC_MESSAGES/django.po
index c42485468440fa49c2fae184c0c5a832d5f424ac..127e9daca16fc1eb8771b4abf441507bd3ff7581 100644
--- a/VIPSWeb/locale/nb/LC_MESSAGES/django.po
+++ b/VIPSWeb/locale/nb/LC_MESSAGES/django.po
@@ -20,7 +20,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VIPSWeb\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-01-20 14:30+0100\n"
+"POT-Creation-Date: 2014-12-28 15:16+0100\n"
 "PO-Revision-Date: 2013-11-07 20:18+0200\n"
 "Last-Translator: Tor-Einar Skog <tor-einar.skog@bioforsk.no>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,46 +30,44 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: templates/base.html:45
+#: templates/base.html:50
 msgid "Toggle navigation"
 msgstr "Vis/skjul navigasjon"
 
-#: templates/base.html:56
+#: templates/base.html:86
+msgid "This is the default page contents."
+msgstr "Dette er standardinnholdet på siden"
+
+#: templates/index.html:25
+msgid "Welcome"
+msgstr "Velkommen"
+
+#: templates/index.html:61
+msgid "Crops"
+msgstr ""
+
+#: templates/index.html:72
 msgid "Forecasts"
 msgstr "Varsler"
 
-#: templates/base.html:57 templates/index.html:51
+#: templates/index.html:119
 msgid "Messages"
 msgstr "Meldinger"
 
-#: templates/base.html:59
-msgid "Dropdown"
-msgstr "Nedtrekksliste"
-
-#: templates/base.html:61
-msgid "Action"
-msgstr "Handling"
+#~ msgid "Dropdown"
+#~ msgstr "Nedtrekksliste"
 
-#: templates/base.html:62
-msgid "Another action"
-msgstr "Enda en handling"
+#~ msgid "Action"
+#~ msgstr "Handling"
 
-#: templates/base.html:63
-msgid "Something else here"
-msgstr "Noe annet her"
+#~ msgid "Another action"
+#~ msgstr "Enda en handling"
 
-#: templates/base.html:64
-msgid "Separated link"
-msgstr "Separat lenke"
+#~ msgid "Something else here"
+#~ msgstr "Noe annet her"
 
-#: templates/base.html:65
-msgid "One more separated link"
-msgstr "Enda en separat"
+#~ msgid "Separated link"
+#~ msgstr "Separat lenke"
 
-#: templates/base.html:79
-msgid "This is the default page contents."
-msgstr "Dette er standardinnholdet på siden"
-
-#: templates/index.html:22
-msgid "Welcome"
-msgstr "Velkommen"
+#~ msgid "One more separated link"
+#~ msgstr "Enda en separat"
diff --git a/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.mo b/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.mo
index 5758fef377329dcd0a0898af068efe4206fc9171..312de79fb8a054749c47ec22ce5c6910c2abea9d 100644
Binary files a/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.mo and b/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.mo differ
diff --git a/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.po b/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.po
index 278153aa249db976f8c1c8cd34e36740168a0451..6b564610f9ebc008378fa5cadca31d55ed1d70ce 100644
--- a/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.po
+++ b/VIPSWeb/locale/nb/LC_MESSAGES/djangojs.po
@@ -1,28 +1,27 @@
 #    Copyright (C) 2014 Bioforsk
 #    
 #    This file is part of VIPSWeb
-#
 #    VIPSWeb 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.
+#    it under the terms of the Bioforsk Open Source License as published by 
+#    Bioforsk, either version 1 of the License, or (at your option) any
+#    later version.
 #
 #    VIPSWeb 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.
+#    Bioforsk Open Source License for more details.
 #
-#    You should have received a copy of the GNU Affero General Public License
-#    along with VIPSWeb.  If not, see <http://www.gnu.org/licenses/>.
+#    You should have received a copy of the Bioforsk Open Source License
+#    along with VIPSWeb.  If not, see <http://www.bioforsk.no/licenses/>.
 #
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
+"Project-Id-Version: VIPSWeb\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-01-16 14:40+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"POT-Creation-Date: 2014-12-29 21:08+0100\n"
+"PO-Revision-Date: 2014-12-28 15:17+0100\n"
+"Last-Translator: Tor-Einar Skog <tor-einar.skog@bioforsk.no>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
 "MIME-Version: 1.0\n"
@@ -30,10 +29,39 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: static/js/forecastmap.js:35
+#: static/js/forecastmap.js:41
 msgid "Source hostname not defined."
 msgstr "Kildens hostname er ikke definert"
 
-#: static/js/forecastmap.js:170
+#: static/js/forecastmap.js:191
 msgid "No forecasts found for"
 msgstr "Ingen varsler funnet for"
+
+#: static/js/frontpage.js:194
+msgid "No forecast available"
+msgstr "Ingen varsler tilgjengelige"
+
+#: static/js/frontpage.js:196
+msgid "Missing data"
+msgstr "Data mangler"
+
+#: static/js/frontpage.js:198
+msgid "No risk of infection"
+msgstr "Ingen infeksjonsrisiko"
+
+#: static/js/frontpage.js:200
+msgid "Medium risk of infection"
+msgstr "Middels infeksjonsrisiko"
+
+#: static/js/frontpage.js:202
+msgid "High risk of infection"
+msgstr "Høy infeksjonsrisiko"
+
+#: static/js/frontpage.js:204
+msgid "Invalid forecast status"
+msgstr "Ikke gyldig varselstatus"
+
+#: static/js/frontpage.js:241
+msgid "Unnamed"
+msgstr "Uten navn"
+
diff --git a/VIPSWeb/settings.py b/VIPSWeb/settings.py
index 5c6a651b7dbd85123fb1e22ad6cfee603fba3a2a..6b8a5c9ded12cab5f4939e8650dc86ede4e0e162 100644
--- a/VIPSWeb/settings.py
+++ b/VIPSWeb/settings.py
@@ -115,7 +115,9 @@ TEMPLATE_DIRS = (
 
 TEMPLATE_CONTEXT_PROCESSORS = (
     'django.contrib.auth.context_processors.auth',
-    'VIPSWeb.context_processors.settings', 
+    'django.core.context_processors.request',
+    'VIPSWeb.context_processors.settings'
+    
 )
 
 INSTALLED_APPS = (
@@ -131,7 +133,8 @@ INSTALLED_APPS = (
     # 'django.contrib.admindocs',
     'forecasts',
     'messages',
-    'organisms'
+    'organisms',
+    'VIPSWeb'
 )
 
 SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
diff --git a/VIPSWeb/static/css/icons/checkbox_checked.png b/VIPSWeb/static/css/icons/checkbox_checked.png
new file mode 100644
index 0000000000000000000000000000000000000000..0667598de45b2c42c68684e08ccecd65dc72ae01
Binary files /dev/null and b/VIPSWeb/static/css/icons/checkbox_checked.png differ
diff --git a/VIPSWeb/static/css/icons/checkbox_unchecked.png b/VIPSWeb/static/css/icons/checkbox_unchecked.png
new file mode 100644
index 0000000000000000000000000000000000000000..877c7e4c2518bd2331de42cc4b2d6cfe699b82d2
Binary files /dev/null and b/VIPSWeb/static/css/icons/checkbox_unchecked.png differ
diff --git a/VIPSWeb/static/css/icons/forecast_status_1.png b/VIPSWeb/static/css/icons/forecast_status_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..75d550700c81978b09a268ff6d4f58871decb50d
Binary files /dev/null and b/VIPSWeb/static/css/icons/forecast_status_1.png differ
diff --git a/VIPSWeb/static/css/icons/forecast_status_2.png b/VIPSWeb/static/css/icons/forecast_status_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..32240891a3dceec92c6d98ded023cc8c11f34334
Binary files /dev/null and b/VIPSWeb/static/css/icons/forecast_status_2.png differ
diff --git a/VIPSWeb/static/css/icons/forecast_status_3.png b/VIPSWeb/static/css/icons/forecast_status_3.png
new file mode 100644
index 0000000000000000000000000000000000000000..745b63185d7514ea477357af52b3e9823e7a46f0
Binary files /dev/null and b/VIPSWeb/static/css/icons/forecast_status_3.png differ
diff --git a/VIPSWeb/static/css/icons/forecast_status_4.png b/VIPSWeb/static/css/icons/forecast_status_4.png
new file mode 100644
index 0000000000000000000000000000000000000000..3146879fce473a1f0e5abf7311929e9c43b50366
Binary files /dev/null and b/VIPSWeb/static/css/icons/forecast_status_4.png differ
diff --git a/VIPSWeb/static/css/icons/radio_checked.png b/VIPSWeb/static/css/icons/radio_checked.png
new file mode 100644
index 0000000000000000000000000000000000000000..a1d7c368812101a82bfdded812217115c93904cb
Binary files /dev/null and b/VIPSWeb/static/css/icons/radio_checked.png differ
diff --git a/VIPSWeb/static/css/icons/radio_unchecked.png b/VIPSWeb/static/css/icons/radio_unchecked.png
new file mode 100644
index 0000000000000000000000000000000000000000..77f04ee484e963b9a3bb9bdd73107cb99eeccd11
Binary files /dev/null and b/VIPSWeb/static/css/icons/radio_unchecked.png differ
diff --git a/VIPSWeb/static/css/icons/station_icon_status_0.png b/VIPSWeb/static/css/icons/station_icon_status_0.png
new file mode 100644
index 0000000000000000000000000000000000000000..2effd490daa3125e3fb1d4f1741ff6013b37e96c
Binary files /dev/null and b/VIPSWeb/static/css/icons/station_icon_status_0.png differ
diff --git a/VIPSWeb/static/css/icons/station_icon_status_1.png b/VIPSWeb/static/css/icons/station_icon_status_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..9e0588b80244703bb09c1175ac3abc322d2167b5
Binary files /dev/null and b/VIPSWeb/static/css/icons/station_icon_status_1.png differ
diff --git a/VIPSWeb/static/css/icons/station_icon_status_2.png b/VIPSWeb/static/css/icons/station_icon_status_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..0f873a8d8bd1a9d65116874dcc84990e49ec086f
Binary files /dev/null and b/VIPSWeb/static/css/icons/station_icon_status_2.png differ
diff --git a/VIPSWeb/static/css/icons/station_icon_status_3.png b/VIPSWeb/static/css/icons/station_icon_status_3.png
new file mode 100644
index 0000000000000000000000000000000000000000..d12c3b665458b69ba8e5dd332d35589b03f7367f
Binary files /dev/null and b/VIPSWeb/static/css/icons/station_icon_status_3.png differ
diff --git a/VIPSWeb/static/css/icons/station_icon_status_4.png b/VIPSWeb/static/css/icons/station_icon_status_4.png
new file mode 100644
index 0000000000000000000000000000000000000000..eee31a93463aeb8f12fe17e4dc902afa73e94db9
Binary files /dev/null and b/VIPSWeb/static/css/icons/station_icon_status_4.png differ
diff --git a/VIPSWeb/static/css/vipsweb.css b/VIPSWeb/static/css/vipsweb.css
index d32ee8f2cf80e367a26b4b39b3d43c1089226a67..9886ea0c841bd3f7f42d7bd11476e99254ebe8c8 100644
--- a/VIPSWeb/static/css/vipsweb.css
+++ b/VIPSWeb/static/css/vipsweb.css
@@ -26,11 +26,70 @@
         other frameworks (e.g. OpenLayers)
 */
 
+.navbar{
+	background-color: #212524;
+	border: 0px none;
+	border-radius: 0px;
+	color: #f7f6f2;
+	margin-bottom: 0px;
+	padding-top: 15px;
+	min-height: 80px;
+}
 
-.navbar-brand{
+.navbar-brand {
     padding-top: 6px;
     padding-bottom: 6px;
     text-decoration: none;
+    
+}
+
+.navbar-brand {
+	color: #f7f6f2 !important;
+	font-weight: bold;
+	font-size: large; 
+}
+
+.navbar-nav li a {
+	color: #f7f6f2 !important; 
+	font-weight: normal !important;
+	font-size: large;
+}
+
+.dropdown-menu {
+	border-radius: 0px;
+	padding: 0px;
+	border: 0px none;
+}
+
+.dropdown-menu li a {
+	color: white; 
+	font-weight: normal !important;
+	font-size: medium !important;
+	background-color: #565A59;
+	border-top: 1px solid #B6B8B5;
+}
+
+.dropdown-menu li a:hover {
+	color: #295B40; 
+	font-weight: normal;
+	background-color: #31AB6E;
+}
+
+
+.navbar-nav li a:hover {
+	color: #f7f6f2 !important; 
+	font-weight: bold !important;
+}
+
+.navbar-nav li a.currentLink {
+	color: #f7f6f2 !important;
+	font-weight: bold !important;
+	height: 65px;
+	border-bottom: 4px solid #31ab6e;
+}
+
+.navbar-nav .dropdown a .caret{
+	border-top-color: #31ab6e !important;
 }
 
 .nav a
@@ -38,6 +97,14 @@
 	text-decoration: none;
 }
 
+.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover, .navbar-default .navbar-nav>.open>a:focus{
+	background-color: transparent;
+}
+
+a.dropdown-toggle{
+	height: 65px;
+}
+
 div.messages_illustration{
 	
 }
@@ -45,11 +112,33 @@ div.messages_illustration{
 /* Styles for OpenLayers forecast map */
 
 #map{
-	position: relative;
+	/*position: relative;*/
 	width: 100%;
-	height: 400px;
+}
+/* On mobile (small) devices: Limit the height of
+ * the map so that you can scroll down the page
+ */
+@media (max-height: 800px) 
+{
+	#map { max-height: 600px !important;}
+}
+
+@media (max-height: 600px) 
+{
+	#map { max-height: 400px !important;}
 }
 
+@media (max-height: 500px) 
+{
+	#map { max-height: 300px !important;}
+}
+
+@media (max-height: 400px) 
+{
+	#map { max-height: 200px !important;}
+}
+
+
 #tooltip{
     position: absolute;
     height: 1px;
@@ -245,5 +334,146 @@ thead {
 	font-family: "Roboto", Arial, Helvetica, sans-serif;
 }
 
+footer {
+	padding: 25px 35px 35px 35px;
+	border-top: 4px solid #9f9f9f;
+	background-color: #adadad;
+}
+
+footer p {
+	 line-height: 14px;
+	 margin-bottom: 10px;
+}
+
+footer p, footer a,footer a:visited{
+	color: white;
+	font-size: 11px;
+}
 
+footer a:hover {
+	color: rgba(49, 171, 110, 1);
+}
 
+input[type="checkbox"] {
+    display:none;
+}
+input[type="checkbox"] + label span {
+    display:inline-block;
+    width:19px;
+    height:19px;
+    margin:-1px 4px 0 0;
+    vertical-align:middle;
+    background:url("./icons/checkbox_unchecked.png") left top no-repeat;
+    cursor:pointer;
+}
+input[type="checkbox"]:checked + label span {
+    background:url("./icons/checkbox_checked.png") left top no-repeat;
+}
+
+input[type="radio"] {
+    display:none;
+}
+input[type="radio"] + label span {
+    display:inline-block;
+    width:19px;
+    height:19px;
+    margin:-1px 4px 0 0;
+    vertical-align:middle;
+    background:url("./icons/checkbox_unchecked.png") left top no-repeat;
+    cursor:pointer;
+}
+input[type="radio"]:checked + label span {
+    background:url("./icons/checkbox_checked.png") left top no-repeat;
+}
+
+ul.cropList{
+	list-style: none;
+	margin-left: 0px;
+	padding-left: 0px;
+}
+
+ul.cropList li {
+	float: left;
+	
+}
+
+ul.cropList.double li { width: 50%;}
+
+div.messages_illustration {
+	margin-top: 15px;
+}
+
+select.languageSelect {
+	color: #f7f6f2;
+	background-color: #31ab6e;
+	border-color: #31ab6e;
+}
+
+div.forecastSummaries {
+		
+}
+
+#forecastSummariesTable {
+	display: none; /* When visible: block, not table */
+	max-height: 210px;
+	overflow-y: auto;
+	width: 95%;
+	border-collapse: separate;
+	border-spacing: 2px;
+	cursor: pointer;
+	/*max-height: 200px;
+	overflow-y: auto;*/
+}
+
+
+td.forecastStatus {
+	width: 14%;
+	height: 15px;
+	border: 3px solid;
+}
+
+td.forecastStatus.s0 {
+	border-color: #ADADAD;
+	
+}
+
+td.forecastStatus.s1 {
+	border-color: #0078FD;
+	background:url("./icons/forecast_status_1.png") center no-repeat;
+}
+
+td.forecastStatus.s2 {
+	border-color: #00B457;
+	background:url("./icons/forecast_status_2.png") center no-repeat;
+} 
+
+td.forecastStatus.s3 {
+	border-color: #FDCA00;
+	background:url("./icons/forecast_status_3.png") center no-repeat;
+}
+
+td.forecastStatus.s4 {
+	border-color: #E90D00;
+	background:url("./icons/forecast_status_4.png") center no-repeat;
+}
+
+td.forecastSummaryCellToday {
+	background-color: #888888;
+	color: white;
+	font-weight: bold;
+}
+
+tbody.forecastSummaryRowGroup{
+	display:inline-table;
+	width: 100%;
+}
+
+tr.forecastSummaryDateRow {
+	text-align: center;
+}
+
+tbody.forecastSummaryRowGroup:hover {
+	background-color: #dddddd;
+	/*border-color: #dddddd;
+	font-weight: bold;*/
+}
diff --git a/VIPSWeb/static/js/3rdparty/ol-debug.js b/VIPSWeb/static/js/3rdparty/ol-debug.js
index 18ae51f318033df838cb27fbcc78c6d6596c9f31..dbcf8926f26606047fa4160969012d1b97b3fb30 100644
--- a/VIPSWeb/static/js/3rdparty/ol-debug.js
+++ b/VIPSWeb/static/js/3rdparty/ol-debug.js
@@ -1,8 +1,19 @@
 // OpenLayers 3. See http://openlayers.org/
 // License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
-// Version: v3.0.0-30-g2876902
+// Version: v3.1.1
 
-var CLOSURE_NO_DEPS = true;
+(function (root, factory) {
+  if (typeof define === "function" && define.amd) {
+    define([], factory);
+  } else if (typeof exports === "object") {
+    module.exports = 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");
@@ -24,6 +35,7 @@ var CLOSURE_NO_DEPS = true;
  * global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects to
  * include their own deps file(s) from different locations.
  *
+ * @author arv@google.com (Erik Arvidsson)
  *
  * @provideGoog
  */
@@ -66,7 +78,7 @@ goog.global = this;
  *   var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
  * </pre>
  *
- * @type {Object.<string, (string|number|boolean)>|undefined}
+ * @type {Object<string, (string|number|boolean)>|undefined}
  */
 goog.global.CLOSURE_UNCOMPILED_DEFINES;
 
@@ -84,10 +96,10 @@ goog.global.CLOSURE_UNCOMPILED_DEFINES;
  *
  * Example:
  * <pre>
- *   var CLOSURE_DEFINES = {'goog.DEBUG': false};
+ *   var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
  * </pre>
  *
- * @type {Object.<string, (string|number|boolean)>|undefined}
+ * @type {Object<string, (string|number|boolean)>|undefined}
  */
 goog.global.CLOSURE_DEFINES;
 
@@ -152,8 +164,8 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
  * Defines a named value. In uncompiled mode, the value is retreived 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 compiler command-line
- * options.
+ * 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
@@ -227,31 +239,62 @@ goog.define('goog.TRUSTED_SITE', true);
  *
  * This define can be used to trigger alternate implementations compatible with
  * running in EcmaScript Strict mode or warn about unavailable functionality.
- * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode
+ *
  */
 goog.define('goog.STRICT_MODE_COMPATIBLE', false);
 
 
 /**
- * Creates object stubs for a namespace.  The presence of one or more
- * goog.provide() calls indicate that the file defines the given
- * objects/namespaces.  Provided objects must not be null or undefined.
- * Build tools also scan for provide/require statements
+ * @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);
+
+
+/**
+ * Defines a namespace in Closure.
+ *
+ * A namespace may only be defined once in a codebase. It may be defined using
+ * goog.provide() or goog.module().
+ *
+ * The presence of one or more goog.provide() calls in a file indicates
+ * that the file defines the given objects/namespaces.
+ * Provided symbols must not be null or undefined.
+ *
+ * In addition, goog.provide() creates the object stubs for a namespace
+ * (for example, goog.provide("goog.foo.bar") will create the object
+ * goog.foo.bar if it does not already exist).
+ *
+ * Build tools also scan for provide/require/module statements
  * to discern dependencies, build dependency files (see deps.js), etc.
+ *
  * @see goog.require
+ * @see goog.module
  * @param {string} name Namespace provided by this file in the form
  *     "goog.package.part".
  */
 goog.provide = function(name) {
   if (!COMPILED) {
-    // Ensure that the same namespace isn't provided twice. This is intended
-    // to teach new developers that 'goog.provide' is effectively a variable
-    // declaration. And when JSCompiler transforms goog.provide into a real
-    // variable declaration, the compiled JS should work the same as the raw
-    // JS--even when the raw JS uses goog.provide incorrectly.
+    // 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;
@@ -263,7 +306,164 @@ goog.provide = function(name) {
     }
   }
 
-  goog.exportPath_(name);
+  goog.exportPath_(name, opt_obj);
+};
+
+
+/**
+ * Module identifier validation regexp.
+ * Note: This is a conservative check, it is very possible to be more lienent,
+ *   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),
+ *   declareTestMethods: boolean
+ * }}
+ */
+goog.moduleLoaderState_ = null;
+
+
+/**
+ * @private
+ * @return {boolean} Whether a goog.module is currently being initialized.
+ */
+goog.isInModuleLoader_ = function() {
+  return goog.moduleLoaderState_ != null;
+};
+
+
+/**
+ * Indicate that a module's exports that are known test methods should
+ * be copied to the global object.  This makes the test methods visible to
+ * test runners that inspect the global object.
+ *
+ * TODO(johnlenz): Make the test framework aware of goog.module so
+ * that this isn't necessary. Alternately combine this with goog.setTestOnly
+ * to minimize boiler plate.
+ * @suppress {missingProvide}
+ */
+goog.module.declareTestMethods = function() {
+  if (!goog.isInModuleLoader_()) {
+    throw new Error('goog.module.declareTestMethods must be called from ' +
+        'within a goog.module');
+  }
+  goog.moduleLoaderState_.declareTestMethods = true;
+};
+
+
+/**
+ * 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;
 };
 
 
@@ -279,7 +479,7 @@ goog.provide = function(name) {
  *     raised when used in production code.
  */
 goog.setTestOnly = function(opt_message) {
-  if (COMPILED && !goog.DEBUG) {
+  if (goog.DISALLOW_TEST_ONLY_CODE) {
     opt_message = opt_message || '';
     throw Error('Importing test-only code into non-debug environment' +
                 (opt_message ? ': ' + opt_message : '.'));
@@ -316,8 +516,9 @@ if (!COMPILED) {
    * @private
    */
   goog.isProvided_ = function(name) {
-    return !goog.implicitNamespaces_[name] &&
-        goog.isDefAndNotNull(goog.getObjectByName(name));
+    return (name in goog.loadedModules_) ||
+        (!goog.implicitNamespaces_[name] &&
+            goog.isDefAndNotNull(goog.getObjectByName(name)));
   };
 
   /**
@@ -325,10 +526,15 @@ if (!COMPILED) {
    * goog.provide('goog.events.Event') implicitly declares that 'goog' and
    * 'goog.events' must be namespaces.
    *
-   * @type {Object}
+   * @type {!Object<string, (boolean|undefined)>}
    * @private
    */
-  goog.implicitNamespaces_ = {};
+  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.
 }
 
 
@@ -360,7 +566,7 @@ goog.getObjectByName = function(name, opt_obj) {
 /**
  * Globalizes a whole namespace, such as goog or goog.lang.
  *
- * @param {Object} obj The namespace to globalize.
+ * @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.
@@ -376,22 +582,21 @@ goog.globalize = function(obj, opt_global) {
 /**
  * Adds a dependency from a file to the files it requires.
  * @param {string} relPath The path to the js file.
- * @param {Array} provides An array of strings with the names of the objects
- *                         this file provides.
- * @param {Array} requires An array of strings with the names of the objects
- *                         this file requires.
- */
-goog.addDependency = function(relPath, provides, requires) {
+ * @param {!Array<string>} provides An array of strings with
+ *     the names of the objects this file provides.
+ * @param {!Array<string>} requires An array of strings with
+ *     the names of the objects this file requires.
+ * @param {boolean=} opt_isModule Whether this dependency must be loaded as
+ *     a module as declared by goog.module.
+ */
+goog.addDependency = function(relPath, provides, requires, opt_isModule) {
   if (goog.DEPENDENCIES_ENABLED) {
     var provide, require;
     var path = relPath.replace(/\\/g, '/');
     var deps = goog.dependencies_;
     for (var i = 0; provide = provides[i]; i++) {
       deps.nameToPath[provide] = path;
-      if (!(path in deps.pathToNames)) {
-        deps.pathToNames[path] = {};
-      }
-      deps.pathToNames[path][provide] = true;
+      deps.pathIsModule[path] = !!opt_isModule;
     }
     for (var j = 0; require = requires[j]; j++) {
       if (!(path in deps.requires)) {
@@ -436,6 +641,17 @@ goog.addDependency = function(relPath, provides, requires) {
 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
@@ -443,18 +659,23 @@ goog.define('goog.ENABLE_DEBUG_LOADER', true);
  * @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.
-  // TODO(arv): If we start to support require based on file name this has to
-  //            change.
-  // TODO(arv): If we allow goog.foo.* this has to change.
-  // TODO(arv): If we implement dynamic load after page load we should probably
-  //            not remove this code for the compiled output.
   if (!COMPILED) {
+    if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) {
+      goog.maybeProcessDeferredDep_(name);
+    }
+
     if (goog.isProvided_(name)) {
-      return;
+      if (goog.isInModuleLoader_()) {
+        return goog.module.getInternal_(name);
+      } else {
+        return null;
+      }
     }
 
     if (goog.ENABLE_DEBUG_LOADER) {
@@ -462,18 +683,14 @@ goog.require = function(name) {
       if (path) {
         goog.included_[path] = true;
         goog.writeScripts_();
-        return;
+        return null;
       }
     }
 
     var errorMessage = 'goog.require could not find: ' + name;
-    if (goog.global.console) {
-      goog.global.console['error'](errorMessage);
-    }
-
-
-      throw Error(errorMessage);
+    goog.logToConsole_(errorMessage);
 
+    throw Error(errorMessage);
   }
 };
 
@@ -575,12 +792,37 @@ goog.addSingletonGetter = function(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>}
+ * @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
+ * similiar.  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}
@@ -592,8 +834,7 @@ if (goog.DEPENDENCIES_ENABLED) {
   /**
    * Object used to keep track of urls that have already been added. This record
    * allows the prevention of circular dependencies.
-   * @type {Object}
-   * @private
+   * @private {!Object<string, boolean>}
    */
   goog.included_ = {};
 
@@ -602,15 +843,28 @@ if (goog.DEPENDENCIES_ENABLED) {
    * This object is used to keep track of dependencies and other data that is
    * used for loading scripts.
    * @private
-   * @type {Object}
+   * @type {{
+   *   pathIsModule: !Object<string, boolean>,
+   *   nameToPath: !Object<string, string>,
+   *   requires: !Object<string, !Object<string, boolean>>,
+   *   visited: !Object<string, boolean>,
+   *   written: !Object<string, boolean>,
+   *   deferred: !Object<string, string>
+   * }}
    */
   goog.dependencies_ = {
-    pathToNames: {}, // 1 to many
+    pathIsModule: {}, // 1 to 1
+
     nameToPath: {}, // 1 to 1
+
     requires: {}, // 1 to many
+
     // Used when resolving dependencies to prevent us from visiting file twice.
     visited: {},
-    written: {} // Used to keep track of script files we have written.
+
+    written: {}, // Used to keep track of script files we have written.
+
+    deferred: {} // Used to track deferred module evaluations in old IEs
   };
 
 
@@ -642,7 +896,8 @@ if (goog.DEPENDENCIES_ENABLED) {
     // 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 src = scripts[i].src;
+      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') {
@@ -657,26 +912,250 @@ if (goog.DEPENDENCIES_ENABLED) {
    * 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) {
+  goog.importScript_ = function(src, opt_sourceText) {
     var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
         goog.writeScriptTag_;
-    if (!goog.dependencies_.written[src] && importScript(src)) {
+    if (importScript(src, opt_sourceText)) {
       goog.dependencies_.written[src] = true;
     }
   };
 
 
+  /** @const @private {boolean} */
+  goog.IS_OLD_IE_ = goog.global.document &&
+      goog.global.document.all && !goog.global.atob;
+
+
+  /**
+   * Given a URL initiate retrieval and execution of the module.
+   * @param {string} src Script source URL.
+   * @private
+   */
+  goog.importModule_ = function(src) {
+    // In an attempt to keep browsers from timing out loading scripts using
+    // synchronous XHRs, put each load in its own script block.
+    var bootstrap = 'goog.retrieveAndExecModule_("' + src + '");';
+
+    if (goog.importScript_('', bootstrap)) {
+      goog.dependencies_.written[src] = true;
+    }
+  };
+
+
+  /** @private {!Array<string>} */
+  goog.queuedModules_ = [];
+
+
+  /**
+   * Return an appropriate module text. Suitable to insert into
+   * a script tag (that is unescaped).
+   * @param {string} srcUrl
+   * @param {string} scriptText
+   * @return {string}
+   * @private
+   */
+  goog.wrapModule_ = function(srcUrl, scriptText) {
+    if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) {
+      return '' +
+          'goog.loadModule(function(exports) {' +
+          '"use strict";' +
+          scriptText +
+          '\n' + // terminate any trailing single line comment.
+          ';return exports' +
+          '});' +
+          '\n//# sourceURL=' + srcUrl + '\n';
+    } else {
+      return '' +
+          'goog.loadModule(' +
+          goog.global.JSON.stringify(
+              scriptText + '\n//# sourceURL=' + srcUrl + '\n') +
+          ');';
+    }
+  };
+
+  // On IE9 and ealier, it is necessary to handle
+  // deferred module loads. In later browsers, the
+  // code to be evaluated is simply inserted as a script
+  // block in the correct order. To eval deferred
+  // code at the right time, we piggy back on goog.require to call
+  // goog.maybeProcessDeferredDep_.
+  //
+  // The goog.requires are used both to bootstrap
+  // the loading process (when no deps are available) and
+  // declare that they should be available.
+  //
+  // Here we eval the sources, if all the deps are available
+  // either already eval'd or goog.require'd.  This will
+  // be the case when all the dependencies have already
+  // been loaded, and the dependent module is loaded.
+  //
+  // But this alone isn't sufficient because it is also
+  // necessary to handle the case where there is no root
+  // that is not deferred.  For that there we register for an event
+  // and trigger goog.loadQueuedModules_ handle any remaining deferred
+  // evaluations.
+
+  /**
+   * Handle any remaining deferred goog.module evals.
+   * @private
+   */
+  goog.loadQueuedModules_ = function() {
+    var count = goog.queuedModules_.length;
+    if (count > 0) {
+      var queue = goog.queuedModules_;
+      goog.queuedModules_ = [];
+      for (var i = 0; i < count; i++) {
+        var path = queue[i];
+        goog.maybeProcessDeferredPath_(path);
+      }
+    }
+  };
+
+
+  /**
+   * Eval the named module if its dependencies are
+   * available.
+   * @param {string} name The module to load.
+   * @private
+   */
+  goog.maybeProcessDeferredDep_ = function(name) {
+    if (goog.isDeferredModule_(name) &&
+        goog.allDepsAreAvailable_(name)) {
+      var path = goog.getPathFromDeps_(name);
+      goog.maybeProcessDeferredPath_(goog.basePath + path);
+    }
+  };
+
+  /**
+   * @param {string} name The module to check.
+   * @return {boolean} Whether the name represents a
+   *     module whose evaluation has been deferred.
+   * @private
+   */
+  goog.isDeferredModule_ = function(name) {
+    var path = goog.getPathFromDeps_(name);
+    if (path && goog.dependencies_.pathIsModule[path]) {
+      var abspath = goog.basePath + path;
+      return (abspath) in goog.dependencies_.deferred;
+    }
+    return false;
+  };
+
+  /**
+   * @param {string} name The module to check.
+   * @return {boolean} Whether the name represents a
+   *     module whose declared dependencies have all been loaded
+   *     (eval'd or a deferred module load)
+   * @private
+   */
+  goog.allDepsAreAvailable_ = function(name) {
+    var path = goog.getPathFromDeps_(name);
+    if (path && (path in goog.dependencies_.requires)) {
+      for (var requireName in goog.dependencies_.requires[path]) {
+        if (!goog.isProvided_(requireName) &&
+            !goog.isDeferredModule_(requireName)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  };
+
+
+  /**
+   * @param {string} abspath
+   * @private
+   */
+  goog.maybeProcessDeferredPath_ = function(abspath) {
+    if (abspath in goog.dependencies_.deferred) {
+      var src = goog.dependencies_.deferred[abspath];
+      delete goog.dependencies_.deferred[abspath];
+      goog.globalEval(src);
+    }
+  };
+
+
+  /**
+   * @param {function(?):?|string} moduleDef The module definition.
+   */
+  goog.loadModule = function(moduleDef) {
+    // NOTE: we allow function definitions to be either in the from
+    // of a string to eval (which keeps the original source intact) or
+    // in a eval forbidden environment (CSP) we allow a function definition
+    // which in its body must call {@code goog.module}, and return the exports
+    // of the module.
+    var previousState = goog.moduleLoaderState_;
+    try {
+      goog.moduleLoaderState_ = {
+        moduleName: undefined, declareTestMethods: false};
+      var exports;
+      if (goog.isFunction(moduleDef)) {
+        exports = moduleDef.call(goog.global, {});
+      } else if (goog.isString(moduleDef)) {
+        exports = goog.loadModuleFromSource_.call(goog.global, moduleDef);
+      } else {
+        throw Error('Invalid module definition');
+      }
+
+      var moduleName = goog.moduleLoaderState_.moduleName;
+      if (!goog.isString(moduleName) || !moduleName) {
+        throw Error('Invalid module name \"' + moduleName + '\"');
+      }
+
+      // Don't seal legacy namespaces as they may be uses as a parent of
+      // another namespace
+      if (goog.moduleLoaderState_.declareLegacyNamespace) {
+        goog.constructNamespace_(moduleName, exports);
+      } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) {
+        Object.seal(exports);
+      }
+
+      goog.loadedModules_[moduleName] = exports;
+      if (goog.moduleLoaderState_.declareTestMethods) {
+        for (var entry in exports) {
+          if (entry.indexOf('test', 0) === 0 ||
+              entry == 'tearDown' ||
+              entry == 'setUp' ||
+              entry == 'setUpPage' ||
+              entry == 'tearDownPage') {
+            goog.global[entry] = exports[entry];
+          }
+        }
+      }
+    } finally {
+      goog.moduleLoaderState_ = previousState;
+    }
+  };
+
+
+  /**
+   * @param {string} source
+   * @return {!Object}
+   * @private
+   */
+  goog.loadModuleFromSource_ = function(source) {
+    // 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;
+  };
+
+
   /**
    * The default implementation of the import function. Writes a script tag to
    * import the script.
    *
-   * @param {string} src The script source.
+   * @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) {
+  goog.writeScriptTag_ = function(src, opt_sourceText) {
     if (goog.inHtmlDocument_()) {
       var doc = goog.global.document;
 
@@ -696,8 +1175,26 @@ if (goog.DEPENDENCIES_ENABLED) {
         }
       }
 
-      doc.write(
-          '<script type="text/javascript" src="' + src + '"></' + 'script>');
+      var isOldIE = goog.IS_OLD_IE_;
+
+      if (opt_sourceText === undefined) {
+        if (!isOldIE) {
+          doc.write(
+              '<script type="text/javascript" src="' +
+                  src + '"></' + 'script>');
+        } 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;
@@ -705,17 +1202,39 @@ if (goog.DEPENDENCIES_ENABLED) {
   };
 
 
+  /** @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.
    * @private
    */
   goog.writeScripts_ = function() {
-    // The scripts we need to write this time.
+    /** @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;
@@ -759,13 +1278,36 @@ if (goog.DEPENDENCIES_ENABLED) {
       }
     }
 
+    // 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;
+
+    var loadingModule = false;
     for (var i = 0; i < scripts.length; i++) {
-      if (scripts[i]) {
-        goog.importScript_(goog.basePath + scripts[i]);
+      var path = scripts[i];
+      if (path) {
+        if (!deps.pathIsModule[path]) {
+          goog.importScript_(goog.basePath + path);
+        } else {
+          loadingModule = true;
+          goog.importModule_(goog.basePath + path);
+        }
       } else {
+        goog.moduleLoaderState_ = moduleState;
         throw Error('Undefined script input');
       }
     }
+
+    // restore the current "module loading state"
+    goog.moduleLoaderState_ = moduleState;
   };
 
 
@@ -793,6 +1335,74 @@ if (goog.DEPENDENCIES_ENABLED) {
 }
 
 
+/**
+ * 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('/');
+};
+
+
+/**
+ * Retrieve and execute a module.
+ * @param {string} src Script source URL.
+ * @private
+ */
+goog.retrieveAndExecModule_ = function(src) {
+  if (!COMPILED) {
+    // The full but non-canonicalized URL for later use.
+    var originalPath = src;
+    // Canonicalize the path, removing any /./ or /../ since Chrome's debugging
+    // console doesn't auto-canonicalize XHR loads as it does <script> srcs.
+    src = goog.normalizePath_(src);
+
+    var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
+        goog.writeScriptTag_;
+
+    var scriptText = null;
+
+    var xhr = new goog.global['XMLHttpRequest']();
+
+    /** @this {Object} */
+    xhr.onload = function() {
+      scriptText = this.responseText;
+    };
+    xhr.open('get', src, false);
+    xhr.send();
+
+    scriptText = xhr.responseText;
+
+    if (scriptText != null) {
+      var execModuleScript = goog.wrapModule_(src, scriptText);
+      var isOldIE = goog.IS_OLD_IE_;
+      if (isOldIE) {
+        goog.dependencies_.deferred[originalPath] = execModuleScript;
+        goog.queuedModules_.push(originalPath);
+      } else {
+        importScript(src, execModuleScript);
+      }
+    } else {
+      throw new Error('load of ' + src + 'failed');
+    }
+  }
+};
+
 
 //==============================================================================
 // Language Enhancements
@@ -932,12 +1542,14 @@ goog.isArray = function(val) {
 /**
  * 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.
+ * 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';
 };
 
@@ -1034,7 +1646,7 @@ goog.getUid = function(obj) {
  *
  * This does not modify the object.
  *
- * @param {Object} obj The object to check.
+ * @param {!Object} obj The object to check.
  * @return {boolean} Whether there an assigned unique id for the object.
  */
 goog.hasUid = function(obj) {
@@ -1335,8 +1947,7 @@ goog.evalWorksForGlobals_ = null;
 /**
  * Optional map of CSS class names to obfuscated names used with
  * goog.getCssName().
- * @type {Object|undefined}
- * @private
+ * @private {!Object<string, string>|undefined}
  * @see goog.setCssNameMapping
  */
 goog.cssNameMapping_;
@@ -1455,7 +2066,7 @@ goog.setCssNameMapping = function(mapping, opt_style) {
  * are made in uncompiled mode.
  *
  * A hook for overriding the CSS name mapping.
- * @type {Object|undefined}
+ * @type {!Object<string, string>|undefined}
  */
 goog.global.CLOSURE_CSS_NAME_MAPPING;
 
@@ -1480,7 +2091,7 @@ if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
  * </code>
  *
  * @param {string} str Translatable string, places holders in the form {$foo}.
- * @param {Object=} opt_values Map of place holder name to value.
+ * @param {Object<string, string>=} opt_values Maps place holder name to value.
  * @return {string} message with placeholders filled.
  */
 goog.getMsg = function(str, opt_values) {
@@ -1599,7 +2210,12 @@ goog.inherits = function(childCtor, parentCtor) {
    * @return {*} The return value of the superclass method/constructor.
    */
   childCtor.base = function(me, methodName, var_args) {
-    var args = Array.prototype.slice.call(arguments, 2);
+    // 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);
   };
 };
@@ -1640,12 +2256,22 @@ goog.base = function(me, opt_methodName, var_args) {
   }
 
   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, Array.prototype.slice.call(arguments, 1));
+    return caller.superClass_.constructor.apply(me, ctorArgs);
   }
 
-  var args = Array.prototype.slice.call(arguments, 2);
+  // 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) {
@@ -1706,6 +2332,7 @@ if (!COMPILED) {
 // goog.defineClass implementation
 //==============================================================================
 
+
 /**
  * Creates a restricted form of a Closure "class":
  *   - from the compiler's perspective, the instance returned from the
@@ -1765,6 +2392,7 @@ goog.defineClass = function(superClass, def) {
  *     !Object|
  *     {constructor:!Function}|
  *     {constructor:!Function, statics:(Object|function(Function):void)}}
+ * @suppress {missingProvide}
  */
 goog.defineClass.ClassDescriptor;
 
@@ -1794,11 +2422,15 @@ goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
         superClass.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]) {
       return ctr;
     }
-    /** @this {*} */
+    /**
+     * @this {*}
+     * @return {?}
+     */
     var wrappedCtr = function() {
       // Don't seal an instance of a subclass when it calls the constructor of
       // its super class as there is most likely still setup to do.
       var instance = ctr.apply(this, arguments) || this;
+      instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_];
       if (this.constructor === wrappedCtr) {
         Object.seal(instance);
       }
@@ -1813,7 +2445,7 @@ goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
 // TODO(johnlenz): share these values with the goog.object
 /**
  * The names of the fields that are defined on Object.prototype.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
  * @private
  * @const
  */
@@ -2021,6 +2653,7 @@ goog.dom.NodeType = {
 
 /**
  * @fileoverview Utilities for string manipulation.
+ * @author arv@google.com (Erik Arvidsson)
  */
 
 
@@ -2038,6 +2671,12 @@ goog.provide('goog.string.Unicode');
 goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false);
 
 
+/**
+ * @define {boolean} Whether to force non-dom html unescaping.
+ */
+goog.define('goog.string.FORCE_NON_DOM_HTML_UNESCAPING', false);
+
+
 /**
  * Common Unicode string characters.
  * @enum {string}
@@ -2149,9 +2788,9 @@ goog.string.collapseWhitespace = function(str) {
 /**
  * Checks if a string is empty or contains only whitespaces.
  * @param {string} str The string to check.
- * @return {boolean} True if {@code str} is empty or whitespace only.
+ * @return {boolean} Whether {@code str} is empty or whitespace only.
  */
-goog.string.isEmpty = function(str) {
+goog.string.isEmptyOrWhitespace = function(str) {
   // testing length == 0 first is actually slower in all browsers (about the
   // same in Opera).
   // Since IE doesn't include non-breaking-space (0xa0) in their \s character
@@ -2161,17 +2800,54 @@ goog.string.isEmpty = function(str) {
 };
 
 
+/**
+ * Checks if a string is empty.
+ * @param {string} str The string to check.
+ * @return {boolean} Whether {@code str} is empty.
+ */
+goog.string.isEmptyString = function(str) {
+  return str.length == 0;
+};
+
+
+/**
+ * Checks if a string is empty or contains only whitespaces.
+ *
+ * TODO(user): Deprecate this when clients have been switched over to
+ * goog.string.isEmptyOrWhitespace.
+ *
+ * @param {string} str The string to check.
+ * @return {boolean} Whether {@code str} is empty or whitespace only.
+ */
+goog.string.isEmpty = goog.string.isEmptyOrWhitespace;
+
+
 /**
  * Checks if a string is null, undefined, empty or contains only whitespaces.
  * @param {*} str The string to check.
- * @return {boolean} True if{@code str} is null, undefined, empty, or
+ * @return {boolean} Whether {@code str} is null, undefined, empty, or
  *     whitespace only.
+ * @deprecated Use goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str))
+ *     instead.
  */
-goog.string.isEmptySafe = function(str) {
-  return goog.string.isEmpty(goog.string.makeSafe(str));
+goog.string.isEmptyOrWhitespaceSafe = function(str) {
+  return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str));
 };
 
 
+/**
+ * Checks if a string is null, undefined, empty or contains only whitespaces.
+ *
+ * TODO(user): Deprecate this when clients have been switched over to
+ * goog.string.isEmptyOrWhitespaceSafe.
+ *
+ * @param {*} str The string to check.
+ * @return {boolean} Whether {@code str} is null, undefined, empty, or
+ *     whitespace only.
+ */
+goog.string.isEmptySafe = goog.string.isEmptyOrWhitespaceSafe;
+
+
 /**
  * Checks if a string is all breaking whitespace.
  * @param {string} str The string to check.
@@ -2296,12 +2972,17 @@ goog.string.collapseBreakingSpaces = function(str) {
  * @param {string} str The string to trim.
  * @return {string} A trimmed copy of {@code str}.
  */
-goog.string.trim = function(str) {
-  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
-  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
-  // include it in the regexp to enforce consistent cross-browser behavior.
-  return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
-};
+goog.string.trim = (goog.TRUSTED_SITE && String.prototype.trim) ?
+    function(str) {
+      return str.trim();
+    } :
+    function(str) {
+      // Since IE doesn't include non-breaking-space (0xa0) in their \s
+      // character class (as required by section 7.2 of the ECMAScript spec),
+      // we explicitly include it in the regexp to enforce consistent
+      // cross-browser behavior.
+      return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+    };
 
 
 /**
@@ -2627,10 +3308,10 @@ goog.string.ALL_RE_ = (goog.string.DETECT_DOUBLE_ESCAPING ?
  */
 goog.string.unescapeEntities = function(str) {
   if (goog.string.contains(str, '&')) {
-    // We are careful not to use a DOM if we do not have one. We use the []
-    // notation so that the JSCompiler will not complain about these objects and
-    // fields in the case where we have no DOM.
-    if ('document' in goog.global) {
+    // We are careful not to use a DOM if we do not have one or we explicitly
+    // requested non-DOM html unescaping.
+    if (!goog.string.FORCE_NON_DOM_HTML_UNESCAPING &&
+        'document' in goog.global) {
       return goog.string.unescapeEntitiesUsingDom_(str);
     } else {
       // Fall back on pure XML entities
@@ -2667,6 +3348,7 @@ goog.string.unescapeEntitiesWithDocument = function(str, document) {
  * @return {string} The unescaped {@code str} string.
  */
 goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) {
+  /** @type {!Object<string, string>} */
   var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
   var div;
   if (opt_document) {
@@ -2869,8 +3551,7 @@ goog.string.truncateMiddle = function(str, chars,
 
 /**
  * Special chars that need to be escaped for goog.string.quote.
- * @private
- * @type {Object}
+ * @private {!Object<string, string>}
  */
 goog.string.specialEscapeChars_ = {
   '\0': '\\0',
@@ -2887,8 +3568,7 @@ goog.string.specialEscapeChars_ = {
 
 /**
  * Character mappings used internally for goog.string.escapeChar.
- * @private
- * @type {Object}
+ * @private {!Object<string, string>}
  */
 goog.string.jsEscapeCache_ = {
   '\'': '\\\''
@@ -2972,24 +3652,6 @@ goog.string.escapeChar = function(c) {
 };
 
 
-/**
- * Takes a string and creates a map (Object) in which the keys are the
- * characters in the string. The value for the key is set to true. You can
- * then use goog.object.map or goog.array.map to change the values.
- * @param {string} s The string to build the map from.
- * @return {!Object} The map of characters used.
- */
-// TODO(arv): It seems like we should have a generic goog.array.toMap. But do
-//            we want a dependency on goog.array in goog.string?
-goog.string.toMap = function(s) {
-  var rv = {};
-  for (var i = 0; i < s.length; i++) {
-    rv[s.charAt(i)] = true;
-  }
-  return rv;
-};
-
-
 /**
  * Determines whether a string contains a substring.
  * @param {string} str The string to search.
@@ -3303,7 +3965,7 @@ goog.string.createUniqueString = function() {
  */
 goog.string.toNumber = function(str) {
   var num = Number(str);
-  if (num == 0 && goog.string.isEmpty(str)) {
+  if (num == 0 && goog.string.isEmptyOrWhitespace(str)) {
     return NaN;
   }
   return num;
@@ -3410,6 +4072,25 @@ goog.string.toTitleCase = function(str, opt_delimiters) {
 };
 
 
+/**
+ * Capitalizes a string, i.e. converts the first letter to uppercase
+ * and all other letters to lowercase, e.g.:
+ *
+ * goog.string.capitalize('one')     => 'One'
+ * goog.string.capitalize('ONE')     => 'One'
+ * goog.string.capitalize('one two') => 'One two'
+ *
+ * Note that this function does not trim initial whitespace.
+ *
+ * @param {string} str String value to capitalize.
+ * @return {string} String value with first letter in uppercase.
+ */
+goog.string.capitalize = function(str) {
+  return String(str.charAt(0)).toUpperCase() +
+      String(str.substr(1)).toLowerCase();
+};
+
+
 /**
  * Parse a string in decimal or hexidecimal ('0xFFFF') form.
  *
@@ -3459,7 +4140,7 @@ goog.string.parseInt = function(value) {
  * @param {number} limit The limit to the number of splits. The resulting array
  *     will have a maximum length of limit+1.  Negative numbers are the same
  *     as zero.
- * @return {!Array.<string>} The string, split.
+ * @return {!Array<string>} The string, split.
  */
 
 goog.string.splitLimit = function(str, separator, limit) {
@@ -3482,6 +4163,46 @@ goog.string.splitLimit = function(str, separator, limit) {
 };
 
 
+/**
+ * Computes the Levenshtein edit distance between two strings.
+ * @param {string} a
+ * @param {string} b
+ * @return {number} The edit distance between the two strings.
+ */
+goog.string.editDistance = function(a, b) {
+  var v0 = [];
+  var v1 = [];
+
+  if (a == b) {
+    return 0;
+  }
+
+  if (!a.length || !b.length) {
+    return Math.max(a.length, b.length);
+  }
+
+  for (var i = 0; i < b.length + 1; i++) {
+    v0[i] = i;
+  }
+
+  for (var i = 0; i < a.length; i++) {
+    v1[0] = i + 1;
+
+    for (var j = 0; j < b.length; j++) {
+      var cost = a[i] != b[j];
+      // Cost for the substring is the minimum of adding one character, removing
+      // one character, or a swap.
+      v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
+    }
+
+    for (var j = 0; j < v0.length; j++) {
+      v0[j] = v1[j];
+    }
+  }
+
+  return v1[b.length];
+};
+
 // Copyright 2008 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -3515,6 +4236,7 @@ goog.string.splitLimit = function(str, separator, limit) {
  * The compiler will leave in foo() (because its return value is used),
  * but it will remove bar() because it assumes it does not have side-effects.
  *
+ * @author agrieve@google.com (Andrew Grieve)
  */
 
 goog.provide('goog.asserts');
@@ -3535,7 +4257,7 @@ goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
 /**
  * Error object for failed assertions.
  * @param {string} messagePattern The pattern that was used to form message.
- * @param {!Array.<*>} messageArgs The items to substitute into the pattern.
+ * @param {!Array<*>} messageArgs The items to substitute into the pattern.
  * @constructor
  * @extends {goog.debug.Error}
  * @final
@@ -3579,9 +4301,9 @@ goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
  * Throws an exception with the given message and "Assertion failed" prefixed
  * onto it.
  * @param {string} defaultMessage The message to use if givenMessage is empty.
- * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage.
+ * @param {Array<*>} defaultArgs The substitution arguments for defaultMessage.
  * @param {string|undefined} givenMessage Message supplied by the caller.
- * @param {Array.<*>} givenArgs The substitution arguments for givenMessage.
+ * @param {Array<*>} givenArgs The substitution arguments for givenMessage.
  * @throws {goog.asserts.AssertionError} When the value is not a number.
  * @private
  */
@@ -3624,7 +4346,7 @@ goog.asserts.setErrorHandler = function(errorHandler) {
  * @param {T} condition The condition to check.
  * @param {string=} opt_message Error message in case of failure.
  * @param {...*} var_args The items to substitute into the failure message.
- * @return {T} The value of the condition.
+ * @return {!T} The value of the condition.
  * @throws {goog.asserts.AssertionError} When the condition evaluates to false.
  */
 goog.asserts.assert = function(condition, opt_message, var_args) {
@@ -3741,7 +4463,7 @@ goog.asserts.assertObject = function(value, opt_message, var_args) {
  * @param {*} value The value to check.
  * @param {string=} opt_message Error message in case of failure.
  * @param {...*} var_args The items to substitute into the failure message.
- * @return {!Array} The value, guaranteed to be a non-null array.
+ * @return {!Array<?>} The value, guaranteed to be a non-null array.
  * @throws {goog.asserts.AssertionError} When the value is not an array.
  */
 goog.asserts.assertArray = function(value, opt_message, var_args) {
@@ -3750,7 +4472,7 @@ goog.asserts.assertArray = function(value, opt_message, var_args) {
         [goog.typeOf(value), value], opt_message,
         Array.prototype.slice.call(arguments, 2));
   }
-  return /** @type {!Array} */ (value);
+  return /** @type {!Array<?>} */ (value);
 };
 
 
@@ -3810,7 +4532,8 @@ goog.asserts.assertElement = function(value, opt_message, var_args) {
  */
 goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
   if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
-    goog.asserts.doAssertFailure_('instanceof check failed.', null,
+    goog.asserts.doAssertFailure_('Expected instanceof %s but got %s.',
+        [goog.asserts.getType_(type), goog.asserts.getType_(value)],
         opt_message, Array.prototype.slice.call(arguments, 3));
   }
   return value;
@@ -3827,6 +4550,25 @@ goog.asserts.assertObjectPrototypeIsIntact = function() {
   }
 };
 
+
+/**
+ * Returns the type of a value. If a constructor is passed, and a suitable
+ * string cannot be found, 'unknown type name' will be returned.
+ * @param {*} value A constructor, object, or primitive.
+ * @return {string} The best display name for the value, or 'unknown type name'.
+ * @private
+ */
+goog.asserts.getType_ = function(value) {
+  if (value instanceof Function) {
+    return value.displayName || value.name || 'unknown type name';
+  } else if (value instanceof Object) {
+    return value.constructor.displayName || value.constructor.name ||
+        Object.prototype.toString.call(value);
+  } else {
+    return value === null ? 'null' : typeof value;
+  }
+};
+
 // Copyright 2006 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -3844,6 +4586,7 @@ goog.asserts.assertObjectPrototypeIsIntact = function() {
 /**
  * @fileoverview Utilities for manipulating arrays.
  *
+ * @author arv@google.com (Erik Arvidsson)
  */
 
 
@@ -3888,7 +4631,7 @@ goog.array.ArrayLike;
 /**
  * Returns the last element in an array without removing it.
  * Same as goog.array.last.
- * @param {Array.<T>|goog.array.ArrayLike} array The array.
+ * @param {Array<T>|goog.array.ArrayLike} array The array.
  * @return {T} Last item in array.
  * @template T
  */
@@ -3900,7 +4643,7 @@ goog.array.peek = function(array) {
 /**
  * Returns the last element in an array without removing it.
  * Same as goog.array.peek.
- * @param {Array.<T>|goog.array.ArrayLike} array The array.
+ * @param {Array<T>|goog.array.ArrayLike} array The array.
  * @return {T} Last item in array.
  * @template T
  */
@@ -3927,7 +4670,7 @@ goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
  *
  * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {Array<T>|goog.array.ArrayLike} arr The array to be searched.
  * @param {T} obj The object for which we are searching.
  * @param {number=} opt_fromIndex The index at which to start the search. If
  *     omitted the search starts at index 0.
@@ -3969,7 +4712,7 @@ goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
  *
  * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
  *
- * @param {!Array.<T>|!goog.array.ArrayLike} arr The array to be searched.
+ * @param {!Array<T>|!goog.array.ArrayLike} arr The array to be searched.
  * @param {T} obj The object for which we are searching.
  * @param {?number=} opt_fromIndex The index at which to start the search. If
  *     omitted the search starts at the end of the array.
@@ -4014,7 +4757,7 @@ goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
  * Calls a function for each element in an array. Skips holes in the array.
  * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
  *     which to iterate.
  * @param {?function(this: S, T, number, ?): ?} f The function to call for every
  *     element. This function takes 3 arguments (the element, the index and the
@@ -4045,7 +4788,7 @@ goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
  * Calls a function for each element in an array, starting from the last
  * element rather than the first.
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this: S, T, number, ?): ?} f The function to call for every
  *     element. This function
@@ -4072,7 +4815,7 @@ goog.array.forEachRight = function(arr, f, opt_obj) {
  *
  * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?):boolean} f The function to call for
  *     every element. This function
@@ -4081,7 +4824,7 @@ goog.array.forEachRight = function(arr, f, opt_obj) {
  *     result array. If it is false the element is not included.
  * @param {S=} opt_obj The object to be used as the value of 'this'
  *     within f.
- * @return {!Array.<T>} a new array in which only elements that passed the test
+ * @return {!Array<T>} a new array in which only elements that passed the test
  *     are present.
  * @template T,S
  */
@@ -4116,14 +4859,14 @@ goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
  *
  * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
  *
- * @param {Array.<VALUE>|goog.array.ArrayLike} arr Array or array like object
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr Array or array like object
  *     over which to iterate.
  * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call
  *     for every element. This function takes 3 arguments (the element,
  *     the index and the array) and should return something. The result will be
  *     inserted into a new array.
  * @param {THIS=} opt_obj The object to be used as the value of 'this' within f.
- * @return {!Array.<RESULT>} a new array with the results from f.
+ * @return {!Array<RESULT>} a new array with the results from f.
  * @template THIS, VALUE, RESULT
  */
 goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
@@ -4157,9 +4900,9 @@ goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
  * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
  * returns 10
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
- * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
+ * @param {function(this:S, R, T, number, ?) : R} f The function to call for
  *     every element. This function
  *     takes 4 arguments (the function's previous result or the initial value,
  *     the value of the current array element, the current array index, and the
@@ -4201,7 +4944,7 @@ goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES &&
  * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
  * returns 'cba'
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
  *     every element. This function
@@ -4242,7 +4985,7 @@ goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES &&
  *
  * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
  *     for every element. This function takes 3 arguments (the element, the
@@ -4279,7 +5022,7 @@ goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
  *
  * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
  *     for every element. This function takes 3 arguments (the element, the
@@ -4313,7 +5056,7 @@ goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
  * Counts the array elements that fulfill the predicate, i.e. for which the
  * callback function returns true. Skips holes in the array.
  *
- * @param {!(Array.<T>|goog.array.ArrayLike)} arr Array or array like object
+ * @param {!(Array<T>|goog.array.ArrayLike)} arr Array or array like object
  *     over which to iterate.
  * @param {function(this: S, T, number, ?): boolean} f The function to call for
  *     every element. Takes 3 arguments (the element, the index and the array).
@@ -4335,7 +5078,7 @@ goog.array.count = function(arr, f, opt_obj) {
 /**
  * Search an array for the first element that satisfies a given condition and
  * return that element.
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?) : boolean} f The function to call
  *     for every element. This function takes 3 arguments (the element, the
@@ -4354,7 +5097,7 @@ goog.array.find = function(arr, f, opt_obj) {
 /**
  * Search an array for the first element that satisfies a given condition and
  * return its index.
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
  *     every element. This function
@@ -4380,7 +5123,7 @@ goog.array.findIndex = function(arr, f, opt_obj) {
 /**
  * Search an array (in reverse order) for the last element that satisfies a
  * given condition and return that element.
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?) : boolean} f The function to call
  *     for every element. This function
@@ -4400,13 +5143,13 @@ goog.array.findRight = function(arr, f, opt_obj) {
 /**
  * Search an array (in reverse order) for the last element that satisfies a
  * given condition and return its index.
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?) : boolean} f The function to call
  *     for every element. This function
  *     takes 3 arguments (the element, the index and the array) and should
  *     return a boolean.
- * @param {Object=} opt_obj An optional "this" context for the function.
+ * @param {S=} opt_obj An optional "this" context for the function.
  * @return {number} The index of the last array element that passes the test,
  *     or -1 if no element is found.
  * @template T,S
@@ -4463,7 +5206,7 @@ goog.array.clear = function(arr) {
 
 /**
  * Pushes an item into an array, if it's not already in the array.
- * @param {Array.<T>} arr Array into which to insert the item.
+ * @param {Array<T>} arr Array into which to insert the item.
  * @param {T} obj Value to add.
  * @template T
  */
@@ -4500,7 +5243,7 @@ goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
 
 /**
  * Inserts an object into an array before a specified object.
- * @param {Array.<T>} arr The array to modify.
+ * @param {Array<T>} arr The array to modify.
  * @param {T} obj The object to insert.
  * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
  *     is omitted or not found, obj is inserted at the end of the array.
@@ -4518,7 +5261,7 @@ goog.array.insertBefore = function(arr, obj, opt_obj2) {
 
 /**
  * Removes the first occurrence of a particular value from an array.
- * @param {Array.<T>|goog.array.ArrayLike} arr Array from which to remove
+ * @param {Array<T>|goog.array.ArrayLike} arr Array from which to remove
  *     value.
  * @param {T} obj Object to remove.
  * @return {boolean} True if an element was removed.
@@ -4553,7 +5296,7 @@ goog.array.removeAt = function(arr, i) {
 
 /**
  * Removes the first value that satisfies the given condition.
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
  *     like object over which to iterate.
  * @param {?function(this:S, T, number, ?) : boolean} f The function to call
  *     for every element. This function
@@ -4573,6 +5316,31 @@ goog.array.removeIf = function(arr, f, opt_obj) {
 };
 
 
+/**
+ * Removes all values that satisfy the given condition.
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array
+ *     like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ *     for every element. This function
+ *     takes 3 arguments (the element, the index and the array) and should
+ *     return a boolean.
+ * @param {S=} opt_obj An optional "this" context for the function.
+ * @return {number} The number of items removed
+ * @template T,S
+ */
+goog.array.removeAllIf = function(arr, f, opt_obj) {
+  var removedCount = 0;
+  goog.array.forEachRight(arr, function(val, index) {
+    if (f.call(opt_obj, val, index, arr)) {
+      if (goog.array.removeAt(arr, index)) {
+        removedCount++;
+      }
+    }
+  });
+  return removedCount;
+};
+
+
 /**
  * Returns a new array that is the result of joining the arguments.  If arrays
  * are passed then their items are added, however, if non-arrays are passed they
@@ -4598,7 +5366,7 @@ goog.array.removeIf = function(arr, f, opt_obj) {
  *
  * @param {...*} var_args Items to concatenate.  Arrays will have each item
  *     added, while primitives and objects will be added as is.
- * @return {!Array} The new resultant array.
+ * @return {!Array<?>} The new resultant array.
  */
 goog.array.concat = function(var_args) {
   return goog.array.ARRAY_PROTOTYPE_.concat.apply(
@@ -4608,8 +5376,8 @@ goog.array.concat = function(var_args) {
 
 /**
  * Returns a new array that contains the contents of all the arrays passed.
- * @param {...!Array.<T>} var_args
- * @return {!Array.<T>}
+ * @param {...!Array<T>} var_args
+ * @return {!Array<T>}
  * @template T
  */
 goog.array.join = function(var_args) {
@@ -4620,9 +5388,9 @@ goog.array.join = function(var_args) {
 
 /**
  * Converts an object to an array.
- * @param {Array.<T>|goog.array.ArrayLike} object  The object to convert to an
+ * @param {Array<T>|goog.array.ArrayLike} object  The object to convert to an
  *     array.
- * @return {!Array.<T>} The object converted into an array. If object has a
+ * @return {!Array<T>} The object converted into an array. If object has a
  *     length property, every property indexed with a non-negative number
  *     less than length will be included in the result. If object does not
  *     have a length property, an empty array will be returned.
@@ -4647,9 +5415,9 @@ goog.array.toArray = function(object) {
 
 /**
  * Does a shallow copy of an array.
- * @param {Array.<T>|goog.array.ArrayLike} arr  Array or array-like object to
+ * @param {Array<T>|goog.array.ArrayLike} arr  Array or array-like object to
  *     clone.
- * @return {!Array.<T>} Clone of the input array.
+ * @return {!Array<T>} Clone of the input array.
  * @template T
  */
 goog.array.clone = goog.array.toArray;
@@ -4666,30 +5434,18 @@ goog.array.clone = goog.array.toArray;
  * goog.array.extend(a, 2);
  * a; // [0, 1, 2]
  *
- * @param {Array.<VALUE>} arr1  The array to modify.
- * @param {...(Array.<VALUE>|VALUE)} var_args The elements or arrays of elements
+ * @param {Array<VALUE>} arr1  The array to modify.
+ * @param {...(Array<VALUE>|VALUE)} var_args The elements or arrays of elements
  *     to add to arr1.
  * @template VALUE
  */
 goog.array.extend = function(arr1, var_args) {
   for (var i = 1; i < arguments.length; i++) {
     var arr2 = arguments[i];
-    // If we have an Array or an Arguments object we can just call push
-    // directly.
-    var isArrayLike;
-    if (goog.isArray(arr2) ||
-        // Detect Arguments. ES5 says that the [[Class]] of an Arguments object
-        // is "Arguments" but only V8 and JSC/Safari gets this right. We instead
-        // detect Arguments by checking for array like and presence of "callee".
-        (isArrayLike = goog.isArrayLike(arr2)) &&
-            // The getter for callee throws an exception in strict mode
-            // according to section 10.6 in ES5 so check for presence instead.
-            Object.prototype.hasOwnProperty.call(arr2, 'callee')) {
-      arr1.push.apply(arr1, arr2);
-    } else if (isArrayLike) {
-      // Otherwise loop over arr2 to prevent copying the object.
-      var len1 = arr1.length;
-      var len2 = arr2.length;
+    if (goog.isArrayLike(arr2)) {
+      var len1 = arr1.length || 0;
+      var len2 = arr2.length || 0;
+      arr1.length = len1 + len2;
       for (var j = 0; j < len2; j++) {
         arr1[len1 + j] = arr2[j];
       }
@@ -4705,7 +5461,7 @@ goog.array.extend = function(arr1, var_args) {
  * splice. This means that it might work on other objects similar to arrays,
  * such as the arguments object.
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr The array to modify.
+ * @param {Array<T>|goog.array.ArrayLike} arr The array to modify.
  * @param {number|undefined} index The index at which to start changing the
  *     array. If not defined, treated as 0.
  * @param {number} howMany How many elements to remove (0 means no removal. A
@@ -4713,7 +5469,7 @@ goog.array.extend = function(arr1, var_args) {
  *     are floored).
  * @param {...T} var_args Optional, additional elements to insert into the
  *     array.
- * @return {!Array.<T>} the removed elements.
+ * @return {!Array<T>} the removed elements.
  * @template T
  */
 goog.array.splice = function(arr, index, howMany, var_args) {
@@ -4729,11 +5485,11 @@ goog.array.splice = function(arr, index, howMany, var_args) {
  * Array slice. This means that it might work on other objects similar to
  * arrays, such as the arguments object.
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr The array from
+ * @param {Array<T>|goog.array.ArrayLike} arr The array from
  * which to copy a segment.
  * @param {number} start The index of the first element to copy.
  * @param {number=} opt_end The index after the last element to copy.
- * @return {!Array.<T>} A new array containing the specified segment of the
+ * @return {!Array<T>} A new array containing the specified segment of the
  *     original array.
  * @template T
  */
@@ -4766,7 +5522,7 @@ goog.array.slice = function(arr, start, opt_end) {
  * Runtime: N,
  * Worstcase space: 2N (no dupes)
  *
- * @param {Array.<T>|goog.array.ArrayLike} arr The array from which to remove
+ * @param {Array<T>|goog.array.ArrayLike} arr The array from which to remove
  *     duplicates.
  * @param {Array=} opt_rv An optional array in which to return the results,
  *     instead of performing the removal inplace.  If specified, the original
@@ -4812,7 +5568,7 @@ goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) {
  *
  * Runtime: O(log n)
  *
- * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
  * @param {TARGET} target The sought value.
  * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison
  *     function by which the array is ordered. Should take 2 arguments to
@@ -4841,7 +5597,7 @@ goog.array.binarySearch = function(arr, target, opt_compareFn) {
  *
  * Runtime: O(log n)
  *
- * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
  * @param {function(this:THIS, VALUE, number, ?): number} evaluator
  *     Evaluator function that receives 3 arguments (the element, the index and
  *     the array). Should return a negative number, zero, or a positive number
@@ -4874,7 +5630,7 @@ goog.array.binarySelect = function(arr, evaluator, opt_obj) {
  *
  * Runtime: O(log n)
  *
- * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
  * @param {function(TARGET, VALUE): number|
  *         function(this:THIS, VALUE, number, ?): number} compareFn Either an
  *     evaluator or a comparison function, as defined by binarySearch
@@ -4932,7 +5688,7 @@ goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target,
  *
  * Runtime: Same as <code>Array.prototype.sort</code>
  *
- * @param {Array.<T>} arr The array to be sorted.
+ * @param {Array<T>} arr The array to be sorted.
  * @param {?function(T,T):number=} opt_compareFn Optional comparison
  *     function by which the
  *     array is to be ordered. Should take 2 arguments to compare, and return a
@@ -4956,7 +5712,7 @@ goog.array.sort = function(arr, opt_compareFn) {
  * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
  * O(n) overhead of copying the array twice.
  *
- * @param {Array.<T>} arr The array to be sorted.
+ * @param {Array<T>} arr The array to be sorted.
  * @param {?function(T, T): number=} opt_compareFn Optional comparison function
  *     by which the array is to be ordered. Should take 2 arguments to compare,
  *     and return a negative number, zero, or a positive number depending on
@@ -4979,28 +5735,55 @@ goog.array.stableSort = function(arr, opt_compareFn) {
 };
 
 
+/**
+ * Sort the specified array into ascending order based on item keys
+ * returned by the specified key function.
+ * If no opt_compareFn is specified, the keys are compared in ascending order
+ * using <code>goog.array.defaultCompare</code>.
+ *
+ * Runtime: O(S(f(n)), where S is runtime of <code>goog.array.sort</code>
+ * and f(n) is runtime of the key function.
+ *
+ * @param {Array<T>} arr The array to be sorted.
+ * @param {function(T): K} keyFn Function taking array element and returning
+ *     a key used for sorting this element.
+ * @param {?function(K, K): number=} opt_compareFn Optional comparison function
+ *     by which the keys are to be ordered. Should take 2 arguments to compare,
+ *     and return a negative number, zero, or a positive number depending on
+ *     whether the first argument is less than, equal to, or greater than the
+ *     second.
+ * @template T
+ * @template K
+ */
+goog.array.sortByKey = function(arr, keyFn, opt_compareFn) {
+  var keyCompareFn = opt_compareFn || goog.array.defaultCompare;
+  goog.array.sort(arr, function(a, b) {
+    return keyCompareFn(keyFn(a), keyFn(b));
+  });
+};
+
+
 /**
  * Sorts an array of objects by the specified object key and compare
  * function. If no compare function is provided, the key values are
  * compared in ascending order using <code>goog.array.defaultCompare</code>.
  * This won't work for keys that get renamed by the compiler. So use
  * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
- * @param {Array.<Object>} arr An array of objects to sort.
+ * @param {Array<Object>} arr An array of objects to sort.
  * @param {string} key The object key to sort by.
  * @param {Function=} opt_compareFn The function to use to compare key
  *     values.
  */
 goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
-  var compare = opt_compareFn || goog.array.defaultCompare;
-  goog.array.sort(arr, function(a, b) {
-    return compare(a[key], b[key]);
-  });
+  goog.array.sortByKey(arr,
+      function(obj) { return obj[key]; },
+      opt_compareFn);
 };
 
 
 /**
  * Tells if the array is sorted.
- * @param {!Array.<T>} arr The array.
+ * @param {!Array<T>} arr The array.
  * @param {?function(T,T):number=} opt_compareFn Function to compare the
  *     array elements.
  *     Should take 2 arguments to compare, and return a negative number, zero,
@@ -5053,9 +5836,9 @@ goog.array.equals = function(arr1, arr2, opt_equalsFn) {
 
 /**
  * 3-way array compare function.
- * @param {!Array.<VALUE>|!goog.array.ArrayLike} arr1 The first array to
+ * @param {!Array<VALUE>|!goog.array.ArrayLike} arr1 The first array to
  *     compare.
- * @param {!Array.<VALUE>|!goog.array.ArrayLike} arr2 The second array to
+ * @param {!Array<VALUE>|!goog.array.ArrayLike} arr2 The second array to
  *     compare.
  * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
  *     function by which the array is to be ordered. Should take 2 arguments to
@@ -5108,7 +5891,7 @@ goog.array.defaultCompareEquality = function(a, b) {
 /**
  * Inserts a value into a sorted array. The array is not modified if the
  * value is already present.
- * @param {Array.<VALUE>|goog.array.ArrayLike} array The array to modify.
+ * @param {Array<VALUE>|goog.array.ArrayLike} array The array to modify.
  * @param {VALUE} value The object to insert.
  * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
  *     function by which the array is ordered. Should take 2 arguments to
@@ -5130,7 +5913,7 @@ goog.array.binaryInsert = function(array, value, opt_compareFn) {
 
 /**
  * Removes a value from a sorted array.
- * @param {!Array.<VALUE>|!goog.array.ArrayLike} array The array to modify.
+ * @param {!Array<VALUE>|!goog.array.ArrayLike} array The array to modify.
  * @param {VALUE} value The object to remove.
  * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
  *     function by which the array is ordered. Should take 2 arguments to
@@ -5148,8 +5931,8 @@ goog.array.binaryRemove = function(array, value, opt_compareFn) {
 
 /**
  * Splits an array into disjoint buckets according to a splitting function.
- * @param {Array.<T>} array The array.
- * @param {function(this:S, T,number,Array.<T>):?} sorter Function to call for
+ * @param {Array<T>} array The array.
+ * @param {function(this:S, T,number,Array<T>):?} sorter Function to call for
  *     every element.  This takes 3 arguments (the element, the index and the
  *     array) and must return a valid object key (a string, number, etc), or
  *     undefined, if that object should not be placed in a bucket.
@@ -5180,7 +5963,7 @@ goog.array.bucket = function(array, sorter, opt_obj) {
 /**
  * Creates a new object built from the provided array and the key-generation
  * function.
- * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
+ * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
  *     which to iterate whose elements will be the values in the new object.
  * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
  *     call for every element. This function takes 3 arguments (the element, the
@@ -5190,7 +5973,7 @@ goog.array.bucket = function(array, sorter, opt_obj) {
  *     implementation-defined.
  * @param {S=} opt_obj The object to be used as the value of 'this'
  *     within keyFunc.
- * @return {!Object.<T>} The new object.
+ * @return {!Object<T>} The new object.
  * @template T,S
  */
 goog.array.toObject = function(arr, keyFunc, opt_obj) {
@@ -5218,7 +6001,7 @@ goog.array.toObject = function(arr, keyFunc, opt_obj) {
  * @param {number=} opt_end The optional end value of the range.
  * @param {number=} opt_step The step size between range values. Defaults to 1
  *     if opt_step is undefined or 0.
- * @return {!Array.<number>} An array of numbers for the requested range. May be
+ * @return {!Array<number>} An array of numbers for the requested range. May be
  *     an empty array if adding the step would not converge toward the end
  *     value.
  */
@@ -5255,7 +6038,7 @@ goog.array.range = function(startOrEnd, opt_end, opt_step) {
  *
  * @param {VALUE} value The value to repeat.
  * @param {number} n The repeat count.
- * @return {!Array.<VALUE>} An array with the repeated value.
+ * @return {!Array<VALUE>} An array with the repeated value.
  * @template VALUE
  */
 goog.array.repeat = function(value, n) {
@@ -5272,14 +6055,22 @@ goog.array.repeat = function(value, n) {
  * expanded in-place recursively.
  *
  * @param {...*} var_args The values to flatten.
- * @return {!Array} An array containing the flattened values.
+ * @return {!Array<?>} An array containing the flattened values.
  */
 goog.array.flatten = function(var_args) {
+  var CHUNK_SIZE = 8192;
+
   var result = [];
   for (var i = 0; i < arguments.length; i++) {
     var element = arguments[i];
     if (goog.isArray(element)) {
-      result.push.apply(result, goog.array.flatten.apply(null, element));
+      for (var c = 0; c < element.length; c += CHUNK_SIZE) {
+        var chunk = goog.array.slice(element, c, c + CHUNK_SIZE);
+        var recurseResult = goog.array.flatten.apply(null, chunk);
+        for (var r = 0; r < recurseResult.length; r++) {
+          result.push(recurseResult[r]);
+        }
+      }
     } else {
       result.push(element);
     }
@@ -5297,9 +6088,9 @@ goog.array.flatten = function(var_args) {
  * For example, suppose list comprises [t, a, n, k, s]. After invoking
  * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
  *
- * @param {!Array.<T>} array The array to rotate.
+ * @param {!Array<T>} array The array to rotate.
  * @param {number} n The amount to rotate.
- * @return {!Array.<T>} The array.
+ * @return {!Array<T>} The array.
  * @template T
  */
 goog.array.rotate = function(array, n) {
@@ -5349,7 +6140,8 @@ goog.array.moveItem = function(arr, fromIndex, toIndex) {
  * http://docs.python.org/library/functions.html#zip}
  *
  * @param {...!goog.array.ArrayLike} var_args Arrays to be combined.
- * @return {!Array.<!Array>} A new array of arrays created from provided arrays.
+ * @return {!Array<!Array<?>>} A new array of arrays created from
+ *     provided arrays.
  */
 goog.array.zip = function(var_args) {
   if (!arguments.length) {
@@ -5379,7 +6171,7 @@ goog.array.zip = function(var_args) {
  *
  * Runtime: O(n)
  *
- * @param {!Array} arr The array to be shuffled.
+ * @param {!Array<?>} arr The array to be shuffled.
  * @param {function():number=} opt_randFn Optional random function to use for
  *     shuffling.
  *     Takes no arguments, and returns a random number on the interval [0, 1).
@@ -5416,12 +6208,6 @@ goog.array.shuffle = function(arr, opt_randFn) {
  * @fileoverview Utilities used by goog.labs.userAgent tools. These functions
  * should not be used outside of goog.labs.userAgent.*.
  *
- * @visibility {//closure/goog/bin/sizetests:__pkg__}
- * @visibility {//closure/goog/dom:__subpackages__}
- * @visibility {//closure/goog/style:__pkg__}
- * @visibility {//closure/goog/testing:__pkg__}
- * @visibility {//closure/goog/useragent:__subpackages__}
- * @visibility {//testing/puppet/modules:__pkg__} *
  *
  * @author nnaze@google.com (Nathan Naze)
  */
@@ -5514,7 +6300,7 @@ goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
 /**
  * Parses the user agent into tuples for each section.
  * @param {string} userAgent
- * @return {!Array.<!Array.<string>>} Tuples of key, version, and the contents
+ * @return {!Array<!Array<string>>} Tuples of key, version, and the contents
  *     of the parenthetical.
  */
 goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
@@ -5553,7 +6339,7 @@ goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
 };
 
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// 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.
@@ -5568,395 +6354,679 @@ goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
 // limitations under the License.
 
 /**
- * @fileoverview Closure user agent detection (Browser).
- * @see <a href="http://www.useragentstring.com/">User agent strings</a>
- * For more information on rendering engine, platform, or device see the other
- * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
- * goog.labs.userAgent.device respectively.)
- *
+ * @fileoverview Utilities for manipulating objects/maps/hashes.
+ * @author arv@google.com (Erik Arvidsson)
  */
 
-goog.provide('goog.labs.userAgent.browser');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.labs.userAgent.util');
-goog.require('goog.string');
+goog.provide('goog.object');
 
 
 /**
- * @return {boolean} Whether the user's browser is Opera.
- * @private
+ * Calls a function for each element in an object/map/hash.
+ *
+ * @param {Object<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object<K,V>):?} f The function to call
+ *     for every element. This function takes 3 arguments (the element, the
+ *     index and the object) and the return value is ignored.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @template T,K,V
  */
-goog.labs.userAgent.browser.matchOpera_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Opera') ||
-      goog.labs.userAgent.util.matchUserAgent('OPR');
+goog.object.forEach = function(obj, f, opt_obj) {
+  for (var key in obj) {
+    f.call(opt_obj, obj[key], key, obj);
+  }
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is IE.
- * @private
+ * Calls a function for each element in an object/map/hash. If that call returns
+ * true, adds the element to a new object.
+ *
+ * @param {Object<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call
+ *     for every element. This
+ *     function takes 3 arguments (the element, the index and the object)
+ *     and should return a boolean. If the return value is true the
+ *     element is added to the result object. If it is false the
+ *     element is not included.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {!Object<K,V>} a new object in which only elements that passed the
+ *     test are present.
+ * @template T,K,V
  */
-goog.labs.userAgent.browser.matchIE_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Trident') ||
-      goog.labs.userAgent.util.matchUserAgent('MSIE');
+goog.object.filter = function(obj, f, opt_obj) {
+  var res = {};
+  for (var key in obj) {
+    if (f.call(opt_obj, obj[key], key, obj)) {
+      res[key] = obj[key];
+    }
+  }
+  return res;
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is Firefox.
- * @private
+ * For every element in an object/map/hash calls a function and inserts the
+ * result into a new object.
+ *
+ * @param {Object<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object<K,V>):R} f The function to call
+ *     for every element. This function
+ *     takes 3 arguments (the element, the index and the object)
+ *     and should return something. The result will be inserted
+ *     into a new object.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {!Object<K,R>} a new object with the results from f.
+ * @template T,K,V,R
  */
-goog.labs.userAgent.browser.matchFirefox_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Firefox');
+goog.object.map = function(obj, f, opt_obj) {
+  var res = {};
+  for (var key in obj) {
+    res[key] = f.call(opt_obj, obj[key], key, obj);
+  }
+  return res;
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is Safari.
- * @private
+ * Calls a function for each element in an object/map/hash. If any
+ * call returns true, returns true (without checking the rest). If
+ * all calls return false, returns false.
+ *
+ * @param {Object<K,V>} obj The object to check.
+ * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to
+ *     call for every element. This function
+ *     takes 3 arguments (the element, the index and the object) and should
+ *     return a boolean.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {boolean} true if any element passes the test.
+ * @template T,K,V
  */
-goog.labs.userAgent.browser.matchSafari_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Safari') &&
-      !goog.labs.userAgent.util.matchUserAgent('Chrome') &&
-      !goog.labs.userAgent.util.matchUserAgent('CriOS') &&
-      !goog.labs.userAgent.util.matchUserAgent('Android');
+goog.object.some = function(obj, f, opt_obj) {
+  for (var key in obj) {
+    if (f.call(opt_obj, obj[key], key, obj)) {
+      return true;
+    }
+  }
+  return false;
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is Chrome.
- * @private
+ * Calls a function for each element in an object/map/hash. If
+ * all calls return true, returns true. If any call returns false, returns
+ * false at this point and does not continue to check the remaining elements.
+ *
+ * @param {Object<K,V>} obj The object to check.
+ * @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to
+ *     call for every element. This function
+ *     takes 3 arguments (the element, the index and the object) and should
+ *     return a boolean.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {boolean} false if any element fails the test.
+ * @template T,K,V
  */
-goog.labs.userAgent.browser.matchChrome_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Chrome') ||
-      goog.labs.userAgent.util.matchUserAgent('CriOS');
+goog.object.every = function(obj, f, opt_obj) {
+  for (var key in obj) {
+    if (!f.call(opt_obj, obj[key], key, obj)) {
+      return false;
+    }
+  }
+  return true;
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is the Android browser.
- * @private
+ * Returns the number of key-value pairs in the object map.
+ *
+ * @param {Object} obj The object for which to get the number of key-value
+ *     pairs.
+ * @return {number} The number of key-value pairs in the object map.
  */
-goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Android') &&
-      !goog.labs.userAgent.util.matchUserAgent('Chrome') &&
-      !goog.labs.userAgent.util.matchUserAgent('CriOS');
+goog.object.getCount = function(obj) {
+  // JS1.5 has __count__ but it has been deprecated so it raises a warning...
+  // in other words do not use. Also __count__ only includes the fields on the
+  // actual object and not in the prototype chain.
+  var rv = 0;
+  for (var key in obj) {
+    rv++;
+  }
+  return rv;
 };
 
 
 /**
- * @return {boolean} Whether the user's browser is Opera.
+ * Returns one key from the object map, if any exists.
+ * For map literals the returned key will be the first one in most of the
+ * browsers (a know exception is Konqueror).
+ *
+ * @param {Object} obj The object to pick a key from.
+ * @return {string|undefined} The key or undefined if the object is empty.
  */
-goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
+goog.object.getAnyKey = function(obj) {
+  for (var key in obj) {
+    return key;
+  }
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is IE.
+ * Returns one value from the object map, if any exists.
+ * For map literals the returned value will be the first one in most of the
+ * browsers (a know exception is Konqueror).
+ *
+ * @param {Object<K,V>} obj The object to pick a value from.
+ * @return {V|undefined} The value or undefined if the object is empty.
+ * @template K,V
  */
-goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
+goog.object.getAnyValue = function(obj) {
+  for (var key in obj) {
+    return obj[key];
+  }
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is Firefox.
+ * Whether the object/hash/map contains the given object as a value.
+ * An alias for goog.object.containsValue(obj, val).
+ *
+ * @param {Object<K,V>} obj The object in which to look for val.
+ * @param {V} val The object for which to check.
+ * @return {boolean} true if val is present.
+ * @template K,V
  */
-goog.labs.userAgent.browser.isFirefox =
-    goog.labs.userAgent.browser.matchFirefox_;
+goog.object.contains = function(obj, val) {
+  return goog.object.containsValue(obj, val);
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is Safari.
+ * Returns the values of the object/map/hash.
+ *
+ * @param {Object<K,V>} obj The object from which to get the values.
+ * @return {!Array<V>} The values in the object/map/hash.
+ * @template K,V
  */
-goog.labs.userAgent.browser.isSafari =
-    goog.labs.userAgent.browser.matchSafari_;
+goog.object.getValues = function(obj) {
+  var res = [];
+  var i = 0;
+  for (var key in obj) {
+    res[i++] = obj[key];
+  }
+  return res;
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is Chrome.
+ * Returns the keys of the object/map/hash.
+ *
+ * @param {Object} obj The object from which to get the keys.
+ * @return {!Array<string>} Array of property keys.
  */
-goog.labs.userAgent.browser.isChrome =
-    goog.labs.userAgent.browser.matchChrome_;
+goog.object.getKeys = function(obj) {
+  var res = [];
+  var i = 0;
+  for (var key in obj) {
+    res[i++] = key;
+  }
+  return res;
+};
 
 
 /**
- * @return {boolean} Whether the user's browser is the Android browser.
+ * Get a value from an object multiple levels deep.  This is useful for
+ * pulling values from deeply nested objects, such as JSON responses.
+ * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
+ *
+ * @param {!Object} obj An object to get the value from.  Can be array-like.
+ * @param {...(string|number|!Array<number|string>)} var_args A number of keys
+ *     (as strings, or numbers, for array-like objects).  Can also be
+ *     specified as a single array of keys.
+ * @return {*} The resulting value.  If, at any point, the value for a key
+ *     is undefined, returns undefined.
  */
-goog.labs.userAgent.browser.isAndroidBrowser =
-    goog.labs.userAgent.browser.matchAndroidBrowser_;
+goog.object.getValueByKeys = function(obj, var_args) {
+  var isArrayLike = goog.isArrayLike(var_args);
+  var keys = isArrayLike ? var_args : arguments;
+
+  // Start with the 2nd parameter for the variable parameters syntax.
+  for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
+    obj = obj[keys[i]];
+    if (!goog.isDef(obj)) {
+      break;
+    }
+  }
+
+  return obj;
+};
 
 
 /**
- * For more information, see:
- * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
- * @return {boolean} Whether the user's browser is Silk.
+ * Whether the object/map/hash contains the given key.
+ *
+ * @param {Object} obj The object in which to look for key.
+ * @param {*} key The key for which to check.
+ * @return {boolean} true If the map contains the key.
  */
-goog.labs.userAgent.browser.isSilk = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Silk');
+goog.object.containsKey = function(obj, key) {
+  return key in obj;
 };
 
 
 /**
- * @return {string} The browser version or empty string if version cannot be
- *     determined. Note that for Internet Explorer, this returns the version of
- *     the browser, not the version of the rendering engine. (IE 8 in
- *     compatibility mode will return 8.0 rather than 7.0. To determine the
- *     rendering engine version, look at document.documentMode instead. See
- *     http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
- *     details.)
+ * Whether the object/map/hash contains the given value. This is O(n).
+ *
+ * @param {Object<K,V>} obj The object in which to look for val.
+ * @param {V} val The value for which to check.
+ * @return {boolean} true If the map contains the value.
+ * @template K,V
  */
-goog.labs.userAgent.browser.getVersion = function() {
-  var userAgentString = goog.labs.userAgent.util.getUserAgent();
-  // Special case IE since IE's version is inside the parenthesis and
-  // without the '/'.
-  if (goog.labs.userAgent.browser.isIE()) {
-    return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
+goog.object.containsValue = function(obj, val) {
+  for (var key in obj) {
+    if (obj[key] == val) {
+      return true;
+    }
   }
+  return false;
+};
 
-  if (goog.labs.userAgent.browser.isOpera()) {
-    return goog.labs.userAgent.browser.getOperaVersion_(userAgentString);
-  }
 
-  var versionTuples =
-      goog.labs.userAgent.util.extractVersionTuples(userAgentString);
-  return goog.labs.userAgent.browser.getVersionFromTuples_(versionTuples);
+/**
+ * Searches an object for an element that satisfies the given condition and
+ * returns its key.
+ * @param {Object<K,V>} obj The object to search in.
+ * @param {function(this:T,V,string,Object<K,V>):boolean} f The
+ *      function to call for every element. Takes 3 arguments (the value,
+ *     the key and the object) and should return a boolean.
+ * @param {T=} opt_this An optional "this" context for the function.
+ * @return {string|undefined} The key of an element for which the function
+ *     returns true or undefined if no such element is found.
+ * @template T,K,V
+ */
+goog.object.findKey = function(obj, f, opt_this) {
+  for (var key in obj) {
+    if (f.call(opt_this, obj[key], key, obj)) {
+      return key;
+    }
+  }
+  return undefined;
 };
 
 
 /**
- * @param {string|number} version The version to check.
- * @return {boolean} Whether the browser version is higher or the same as the
- *     given version.
+ * Searches an object for an element that satisfies the given condition and
+ * returns its value.
+ * @param {Object<K,V>} obj The object to search in.
+ * @param {function(this:T,V,string,Object<K,V>):boolean} f The function
+ *     to call for every element. Takes 3 arguments (the value, the key
+ *     and the object) and should return a boolean.
+ * @param {T=} opt_this An optional "this" context for the function.
+ * @return {V} The value of an element for which the function returns true or
+ *     undefined if no such element is found.
+ * @template T,K,V
  */
-goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
-  return goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(),
-                                     version) >= 0;
+goog.object.findValue = function(obj, f, opt_this) {
+  var key = goog.object.findKey(obj, f, opt_this);
+  return key && obj[key];
 };
 
 
 /**
- * Determines IE version. More information:
- * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
- * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
- * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
- * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
+ * Whether the object/map/hash is empty.
  *
- * @param {string} userAgent the User-Agent.
- * @return {string}
- * @private
+ * @param {Object} obj The object to test.
+ * @return {boolean} true if obj is empty.
  */
-goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) {
-  // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
-  // bug. Example UA:
-  // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
-  // like Gecko.
-  // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
-  var rv = /rv: *([\d\.]*)/.exec(userAgent);
-  if (rv && rv[1]) {
-    return rv[1];
-  }
-
-  var version = '';
-  var msie = /MSIE +([\d\.]+)/.exec(userAgent);
-  if (msie && msie[1]) {
-    // IE in compatibility mode usually identifies itself as MSIE 7.0; in this
-    // case, use the Trident version to determine the version of IE. For more
-    // details, see the links above.
-    var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
-    if (msie[1] == '7.0') {
-      if (tridentVersion && tridentVersion[1]) {
-        switch (tridentVersion[1]) {
-          case '4.0':
-            version = '8.0';
-            break;
-          case '5.0':
-            version = '9.0';
-            break;
-          case '6.0':
-            version = '10.0';
-            break;
-          case '7.0':
-            version = '11.0';
-            break;
-        }
-      } else {
-        version = '7.0';
-      }
-    } else {
-      version = msie[1];
-    }
+goog.object.isEmpty = function(obj) {
+  for (var key in obj) {
+    return false;
   }
-  return version;
+  return true;
 };
 
 
 /**
- * Determines Opera version. More information:
- * http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
+ * Removes all key value pairs from the object/map/hash.
  *
- * @param {string} userAgent The User-Agent.
- * @return {string}
- * @private
+ * @param {Object} obj The object to clear.
  */
-goog.labs.userAgent.browser.getOperaVersion_ = function(userAgent) {
-  var versionTuples =
-      goog.labs.userAgent.util.extractVersionTuples(userAgent);
-  var lastTuple = goog.array.peek(versionTuples);
-  if (lastTuple[0] == 'OPR' && lastTuple[1]) {
-    return lastTuple[1];
+goog.object.clear = function(obj) {
+  for (var i in obj) {
+    delete obj[i];
   }
-
-  return goog.labs.userAgent.browser.getVersionFromTuples_(versionTuples);
 };
 
 
 /**
- * Nearly all User-Agents start with Mozilla/N.0. This looks at the second tuple
- * for the actual browser version number.
- * @param {!Array.<!Array.<string>>} versionTuples
- * @return {string} The version or empty string if it cannot be determined.
- * @private
+ * Removes a key-value pair based on the key.
+ *
+ * @param {Object} obj The object from which to remove the key.
+ * @param {*} key The key to remove.
+ * @return {boolean} Whether an element was removed.
  */
-goog.labs.userAgent.browser.getVersionFromTuples_ = function(versionTuples) {
-  // versionTuples[2] (The first X/Y tuple after the parenthesis) contains the
-  // browser version number.
-  goog.asserts.assert(versionTuples.length > 2,
-      'Couldn\'t extract version tuple from user agent string');
-  return versionTuples[2] && versionTuples[2][1] ? versionTuples[2][1] : '';
+goog.object.remove = function(obj, key) {
+  var rv;
+  if ((rv = key in obj)) {
+    delete obj[key];
+  }
+  return rv;
 };
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Closure user agent detection.
- * @see http://en.wikipedia.org/wiki/User_agent
- * For more information on browser brand, platform, or device see the other
- * sub-namespaces in goog.labs.userAgent (browser, platform, and device).
+ * Adds a key-value pair to the object. Throws an exception if the key is
+ * already in use. Use set if you want to change an existing pair.
  *
+ * @param {Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} val The value to add.
+ * @template K,V
  */
+goog.object.add = function(obj, key, val) {
+  if (key in obj) {
+    throw Error('The object already contains the key "' + key + '"');
+  }
+  goog.object.set(obj, key, val);
+};
 
-goog.provide('goog.labs.userAgent.engine');
 
-goog.require('goog.array');
-goog.require('goog.labs.userAgent.util');
-goog.require('goog.string');
+/**
+ * Returns the value for the given key.
+ *
+ * @param {Object<K,V>} obj The object from which to get the value.
+ * @param {string} key The key for which to get the value.
+ * @param {R=} opt_val The value to return if no item is found for the given
+ *     key (default is undefined).
+ * @return {V|R|undefined} The value for the given key.
+ * @template K,V,R
+ */
+goog.object.get = function(obj, key, opt_val) {
+  if (key in obj) {
+    return obj[key];
+  }
+  return opt_val;
+};
 
 
 /**
- * @return {boolean} Whether the rendering engine is Presto.
+ * Adds a key-value pair to the object/map/hash.
+ *
+ * @param {Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} value The value to add.
+ * @template K,V
  */
-goog.labs.userAgent.engine.isPresto = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Presto');
+goog.object.set = function(obj, key, value) {
+  obj[key] = value;
 };
 
 
 /**
- * @return {boolean} Whether the rendering engine is Trident.
+ * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
+ *
+ * @param {Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} value The value to add if the key wasn't present.
+ * @return {V} The value of the entry at the end of the function.
+ * @template K,V
  */
-goog.labs.userAgent.engine.isTrident = function() {
-  // IE only started including the Trident token in IE8.
-  return goog.labs.userAgent.util.matchUserAgent('Trident') ||
-      goog.labs.userAgent.util.matchUserAgent('MSIE');
+goog.object.setIfUndefined = function(obj, key, value) {
+  return key in obj ? obj[key] : (obj[key] = value);
 };
 
 
 /**
- * @return {boolean} Whether the rendering engine is WebKit.
+ * Sets a key and value to an object if the key is not set. The value will be
+ * the return value of the given function. If the key already exists, the
+ * object will not be changed and the function will not be called (the function
+ * will be lazily evaluated -- only called if necessary).
+ *
+ * This function is particularly useful for use with a map used a as a cache.
+ *
+ * @param {!Object<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {function():V} f The value to add if the key wasn't present.
+ * @return {V} The value of the entry at the end of the function.
+ * @template K,V
  */
-goog.labs.userAgent.engine.isWebKit = function() {
-  return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit');
+goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
+  if (key in obj) {
+    return obj[key];
+  }
+
+  var val = f();
+  obj[key] = val;
+  return val;
 };
 
 
 /**
- * @return {boolean} Whether the rendering engine is Gecko.
+ * Compares two objects for equality using === on the values.
+ *
+ * @param {!Object<K,V>} a
+ * @param {!Object<K,V>} b
+ * @return {boolean}
+ * @template K,V
  */
-goog.labs.userAgent.engine.isGecko = function() {
-  return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
-      !goog.labs.userAgent.engine.isWebKit() &&
-      !goog.labs.userAgent.engine.isTrident();
+goog.object.equals = function(a, b) {
+  for (var k in a) {
+    if (!(k in b) || a[k] !== b[k]) {
+      return false;
+    }
+  }
+  for (var k in b) {
+    if (!(k in a)) {
+      return false;
+    }
+  }
+  return true;
 };
 
 
 /**
- * @return {string} The rendering engine's version or empty string if version
- *     can't be determined.
+ * Does a flat clone of the object.
+ *
+ * @param {Object<K,V>} obj Object to clone.
+ * @return {!Object<K,V>} Clone of the input object.
+ * @template K,V
  */
-goog.labs.userAgent.engine.getVersion = function() {
-  var userAgentString = goog.labs.userAgent.util.getUserAgent();
-  if (userAgentString) {
-    var tuples = goog.labs.userAgent.util.extractVersionTuples(
-        userAgentString);
+goog.object.clone = function(obj) {
+  // We cannot use the prototype trick because a lot of methods depend on where
+  // the actual key is set.
 
-    var engineTuple = tuples[1];
-    if (engineTuple) {
-      // In Gecko, the version string is either in the browser info or the
-      // Firefox version.  See Gecko user agent string reference:
-      // http://goo.gl/mULqa
-      if (engineTuple[0] == 'Gecko') {
-        return goog.labs.userAgent.engine.getVersionForKey_(
-            tuples, 'Firefox');
-      }
+  var res = {};
+  for (var key in obj) {
+    res[key] = obj[key];
+  }
+  return res;
+  // We could also use goog.mixin but I wanted this to be independent from that.
+};
 
-      return engineTuple[1];
+
+/**
+ * Clones a value. The input may be an Object, Array, or basic type. Objects and
+ * arrays will be cloned recursively.
+ *
+ * WARNINGS:
+ * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
+ * that refer to themselves will cause infinite recursion.
+ *
+ * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
+ * copies UIDs created by <code>getUid</code> into cloned results.
+ *
+ * @param {*} obj The value to clone.
+ * @return {*} A clone of the input value.
+ */
+goog.object.unsafeClone = function(obj) {
+  var type = goog.typeOf(obj);
+  if (type == 'object' || type == 'array') {
+    if (obj.clone) {
+      return obj.clone();
+    }
+    var clone = type == 'array' ? [] : {};
+    for (var key in obj) {
+      clone[key] = goog.object.unsafeClone(obj[key]);
     }
+    return clone;
+  }
 
-    // IE has only one version identifier, and the Trident version is
-    // specified in the parenthetical.
-    var browserTuple = tuples[0];
-    var info;
-    if (browserTuple && (info = browserTuple[2])) {
-      var match = /Trident\/([^\s;]+)/.exec(info);
-      if (match) {
-        return match[1];
+  return obj;
+};
+
+
+/**
+ * Returns a new object in which all the keys and values are interchanged
+ * (keys become values and values become keys). If multiple keys map to the
+ * same value, the chosen transposed value is implementation-dependent.
+ *
+ * @param {Object} obj The object to transpose.
+ * @return {!Object} The transposed object.
+ */
+goog.object.transpose = function(obj) {
+  var transposed = {};
+  for (var key in obj) {
+    transposed[obj[key]] = key;
+  }
+  return transposed;
+};
+
+
+/**
+ * The names of the fields that are defined on Object.prototype.
+ * @type {Array<string>}
+ * @private
+ */
+goog.object.PROTOTYPE_FIELDS_ = [
+  'constructor',
+  'hasOwnProperty',
+  'isPrototypeOf',
+  'propertyIsEnumerable',
+  'toLocaleString',
+  'toString',
+  'valueOf'
+];
+
+
+/**
+ * Extends an object with another object.
+ * This operates 'in-place'; it does not create a new Object.
+ *
+ * Example:
+ * var o = {};
+ * goog.object.extend(o, {a: 0, b: 1});
+ * o; // {a: 0, b: 1}
+ * goog.object.extend(o, {b: 2, c: 3});
+ * o; // {a: 0, b: 2, c: 3}
+ *
+ * @param {Object} target The object to modify. Existing properties will be
+ *     overwritten if they are also present in one of the objects in
+ *     {@code var_args}.
+ * @param {...Object} var_args The objects from which values will be copied.
+ */
+goog.object.extend = function(target, var_args) {
+  var key, source;
+  for (var i = 1; i < arguments.length; i++) {
+    source = arguments[i];
+    for (key in source) {
+      target[key] = source[key];
+    }
+
+    // For IE the for-in-loop does not contain any properties that are not
+    // enumerable on the prototype object (for example isPrototypeOf from
+    // Object.prototype) and it will also not include 'replace' on objects that
+    // extend String and change 'replace' (not that it is common for anyone to
+    // extend anything except Object).
+
+    for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
+      key = goog.object.PROTOTYPE_FIELDS_[j];
+      if (Object.prototype.hasOwnProperty.call(source, key)) {
+        target[key] = source[key];
       }
     }
   }
-  return '';
 };
 
 
 /**
- * @param {string|number} version The version to check.
- * @return {boolean} Whether the rendering engine version is higher or the same
- *     as the given version.
+ * Creates a new object built from the key-value pairs provided as arguments.
+ * @param {...*} var_args If only one argument is provided and it is an array
+ *     then this is used as the arguments,  otherwise even arguments are used as
+ *     the property names and odd arguments are used as the property values.
+ * @return {!Object} The new object.
+ * @throws {Error} If there are uneven number of arguments or there is only one
+ *     non array argument.
  */
-goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
-  return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),
-                                     version) >= 0;
+goog.object.create = function(var_args) {
+  var argLength = arguments.length;
+  if (argLength == 1 && goog.isArray(arguments[0])) {
+    return goog.object.create.apply(null, arguments[0]);
+  }
+
+  if (argLength % 2) {
+    throw Error('Uneven number of arguments');
+  }
+
+  var rv = {};
+  for (var i = 0; i < argLength; i += 2) {
+    rv[arguments[i]] = arguments[i + 1];
+  }
+  return rv;
 };
 
 
 /**
- * @param {!Array.<!Array.<string>>} tuples Version tuples.
- * @param {string} key The key to look for.
- * @return {string} The version string of the given key, if present.
- *     Otherwise, the empty string.
- * @private
+ * Creates a new object where the property names come from the arguments but
+ * the value is always set to true
+ * @param {...*} var_args If only one argument is provided and it is an array
+ *     then this is used as the arguments,  otherwise the arguments are used
+ *     as the property names.
+ * @return {!Object} The new object.
  */
-goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
-  // TODO(nnaze): Move to util if useful elsewhere.
+goog.object.createSet = function(var_args) {
+  var argLength = arguments.length;
+  if (argLength == 1 && goog.isArray(arguments[0])) {
+    return goog.object.createSet.apply(null, arguments[0]);
+  }
 
-  var pair = goog.array.find(tuples, function(pair) {
-    return key == pair[0];
-  });
+  var rv = {};
+  for (var i = 0; i < argLength; i++) {
+    rv[arguments[i]] = true;
+  }
+  return rv;
+};
 
-  return pair && pair[1] || '';
+
+/**
+ * Creates an immutable view of the underlying object, if the browser
+ * supports immutable objects.
+ *
+ * In default mode, writes to this view will fail silently. In strict mode,
+ * they will throw an error.
+ *
+ * @param {!Object<K,V>} obj An object.
+ * @return {!Object<K,V>} An immutable view of that object, or the
+ *     original object if this browser does not support immutables.
+ * @template K,V
+ */
+goog.object.createImmutableView = function(obj) {
+  var result = obj;
+  if (Object.isFrozen && !Object.isFrozen(obj)) {
+    result = Object.create(obj);
+    Object.freeze(result);
+  }
+  return result;
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+
+/**
+ * @param {!Object} obj An object.
+ * @return {boolean} Whether this is an immutable view of the object.
+ */
+goog.object.isImmutableView = function(obj) {
+  return !!Object.isFrozen && Object.isFrozen(obj);
+};
+
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -5971,639 +7041,1208 @@ goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
 // limitations under the License.
 
 /**
- * @fileoverview Rendering engine detection.
+ * @fileoverview Closure user agent detection (Browser).
  * @see <a href="http://www.useragentstring.com/">User agent strings</a>
- * For information on the browser brand (such as Safari versus Chrome), see
- * goog.userAgent.product.
- * @see ../demos/useragent.html
+ * For more information on rendering engine, platform, or device see the other
+ * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
+ * goog.labs.userAgent.device respectively.)
+ *
+ * @author martone@google.com (Andy Martone)
  */
 
-goog.provide('goog.userAgent');
+goog.provide('goog.labs.userAgent.browser');
 
-goog.require('goog.labs.userAgent.browser');
-goog.require('goog.labs.userAgent.engine');
+goog.require('goog.array');
 goog.require('goog.labs.userAgent.util');
+goog.require('goog.object');
 goog.require('goog.string');
 
 
 /**
- * @define {boolean} Whether we know at compile-time that the browser is IE.
+ * @return {boolean} Whether the user's browser is Opera.
+ * @private
  */
-goog.define('goog.userAgent.ASSUME_IE', false);
+goog.labs.userAgent.browser.matchOpera_ = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Opera') ||
+      goog.labs.userAgent.util.matchUserAgent('OPR');
+};
 
 
 /**
- * @define {boolean} Whether we know at compile-time that the browser is GECKO.
+ * @return {boolean} Whether the user's browser is IE.
+ * @private
  */
-goog.define('goog.userAgent.ASSUME_GECKO', false);
+goog.labs.userAgent.browser.matchIE_ = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Trident') ||
+      goog.labs.userAgent.util.matchUserAgent('MSIE');
+};
 
 
 /**
- * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
+ * @return {boolean} Whether the user's browser is Firefox.
+ * @private
  */
-goog.define('goog.userAgent.ASSUME_WEBKIT', false);
+goog.labs.userAgent.browser.matchFirefox_ = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Firefox');
+};
 
 
 /**
- * @define {boolean} Whether we know at compile-time that the browser is a
- *     mobile device running WebKit e.g. iPhone or Android.
+ * @return {boolean} Whether the user's browser is Safari.
+ * @private
  */
-goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
+goog.labs.userAgent.browser.matchSafari_ = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Safari') &&
+      !goog.labs.userAgent.util.matchUserAgent('Chrome') &&
+      !goog.labs.userAgent.util.matchUserAgent('CriOS') &&
+      !goog.labs.userAgent.util.matchUserAgent('Android');
+};
 
 
 /**
- * @define {boolean} Whether we know at compile-time that the browser is OPERA.
+ * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
+ *     iOS browser).
+ * @private
  */
-goog.define('goog.userAgent.ASSUME_OPERA', false);
+goog.labs.userAgent.browser.matchCoast_ = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Coast');
+};
 
 
 /**
- * @define {boolean} Whether the
- *     {@code goog.userAgent.isVersionOrHigher}
- *     function will return true for any version.
+ * @return {boolean} Whether the user's browser is iOS Webview.
+ * @private
  */
-goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
+goog.labs.userAgent.browser.matchIosWebview_ = function() {
+  // iOS Webview does not show up as Chrome or Safari. Also check for Opera's
+  // WebKit-based iOS browser, Coast.
+  return (goog.labs.userAgent.util.matchUserAgent('iPad') ||
+          goog.labs.userAgent.util.matchUserAgent('iPhone')) &&
+      !goog.labs.userAgent.browser.matchSafari_() &&
+      !goog.labs.userAgent.browser.matchChrome_() &&
+      !goog.labs.userAgent.browser.matchCoast_() &&
+      goog.labs.userAgent.util.matchUserAgent('AppleWebKit');
+};
 
 
 /**
- * Whether we know the browser engine at compile-time.
- * @type {boolean}
+ * @return {boolean} Whether the user's browser is Chrome.
  * @private
  */
-goog.userAgent.BROWSER_KNOWN_ =
-    goog.userAgent.ASSUME_IE ||
-    goog.userAgent.ASSUME_GECKO ||
-    goog.userAgent.ASSUME_MOBILE_WEBKIT ||
-    goog.userAgent.ASSUME_WEBKIT ||
-    goog.userAgent.ASSUME_OPERA;
+goog.labs.userAgent.browser.matchChrome_ = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Chrome') ||
+      goog.labs.userAgent.util.matchUserAgent('CriOS');
+};
 
 
 /**
- * Returns the userAgent string for the current browser.
- *
- * @return {string} The userAgent string.
+ * @return {boolean} Whether the user's browser is the Android browser.
+ * @private
  */
-goog.userAgent.getUserAgentString = function() {
-  return goog.labs.userAgent.util.getUserAgent();
+goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
+  // Android can appear in the user agent string for Chrome on Android.
+  // This is not the Android standalone browser if it does.
+  return !goog.labs.userAgent.browser.isChrome() &&
+      goog.labs.userAgent.util.matchUserAgent('Android');
+
 };
 
 
 /**
- * TODO(nnaze): Change type to "Navigator" and update compilation targets.
- * @return {Object} The native navigator object.
+ * @return {boolean} Whether the user's browser is Opera.
  */
-goog.userAgent.getNavigator = function() {
-  // Need a local navigator reference instead of using the global one,
-  // to avoid the rare case where they reference different objects.
-  // (in a WorkerPool, for example).
-  return goog.global['navigator'] || null;
-};
+goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
 
 
 /**
- * Whether the user agent is Opera.
- * @type {boolean}
+ * @return {boolean} Whether the user's browser is IE.
  */
-goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
-    goog.userAgent.ASSUME_OPERA :
-    goog.labs.userAgent.browser.isOpera();
+goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
 
 
 /**
- * Whether the user agent is Internet Explorer.
- * @type {boolean}
+ * @return {boolean} Whether the user's browser is Firefox.
  */
-goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
-    goog.userAgent.ASSUME_IE :
-    goog.labs.userAgent.browser.isIE();
+goog.labs.userAgent.browser.isFirefox =
+    goog.labs.userAgent.browser.matchFirefox_;
 
 
 /**
- * Whether the user agent is Gecko. Gecko is the rendering engine used by
- * Mozilla, Firefox, and others.
- * @type {boolean}
+ * @return {boolean} Whether the user's browser is Safari.
  */
-goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
-    goog.userAgent.ASSUME_GECKO :
-    goog.labs.userAgent.engine.isGecko();
+goog.labs.userAgent.browser.isSafari =
+    goog.labs.userAgent.browser.matchSafari_;
 
 
 /**
- * Whether the user agent is WebKit. WebKit is the rendering engine that
- * Safari, Android and others use.
- * @type {boolean}
+ * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
+ *     iOS browser).
  */
-goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
-    goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
-    goog.labs.userAgent.engine.isWebKit();
+goog.labs.userAgent.browser.isCoast =
+    goog.labs.userAgent.browser.matchCoast_;
 
 
 /**
- * Whether the user agent is running on a mobile device.
- *
- * This is a separate function so that the logic can be tested.
- *
- * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
- *
- * @return {boolean} Whether the user agent is running on a mobile device.
- * @private
+ * @return {boolean} Whether the user's browser is iOS Webview.
  */
-goog.userAgent.isMobile_ = function() {
-  return goog.userAgent.WEBKIT &&
-         goog.labs.userAgent.util.matchUserAgent('Mobile');
-};
+goog.labs.userAgent.browser.isIosWebview =
+    goog.labs.userAgent.browser.matchIosWebview_;
 
 
 /**
- * Whether the user agent is running on a mobile device.
- *
- * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
- *   is promoted as the gecko/webkit logic is likely inaccurate.
- *
- * @type {boolean}
+ * @return {boolean} Whether the user's browser is Chrome.
  */
-goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
-                        goog.userAgent.isMobile_();
+goog.labs.userAgent.browser.isChrome =
+    goog.labs.userAgent.browser.matchChrome_;
 
 
 /**
- * Used while transitioning code to use WEBKIT instead.
- * @type {boolean}
- * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
- * TODO(nicksantos): Delete this from goog.userAgent.
+ * @return {boolean} Whether the user's browser is the Android browser.
  */
-goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
+goog.labs.userAgent.browser.isAndroidBrowser =
+    goog.labs.userAgent.browser.matchAndroidBrowser_;
 
 
 /**
- * @return {string} the platform (operating system) the user agent is running
- *     on. Default to empty string because navigator.platform may not be defined
- *     (on Rhino, for example).
- * @private
+ * For more information, see:
+ * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
+ * @return {boolean} Whether the user's browser is Silk.
  */
-goog.userAgent.determinePlatform_ = function() {
-  var navigator = goog.userAgent.getNavigator();
-  return navigator && navigator.platform || '';
+goog.labs.userAgent.browser.isSilk = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Silk');
 };
 
 
 /**
- * The platform (operating system) the user agent is running on. Default to
- * empty string because navigator.platform may not be defined (on Rhino, for
- * example).
- * @type {string}
+ * @return {string} The browser version or empty string if version cannot be
+ *     determined. Note that for Internet Explorer, this returns the version of
+ *     the browser, not the version of the rendering engine. (IE 8 in
+ *     compatibility mode will return 8.0 rather than 7.0. To determine the
+ *     rendering engine version, look at document.documentMode instead. See
+ *     http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
+ *     details.)
  */
-goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
+goog.labs.userAgent.browser.getVersion = function() {
+  var userAgentString = goog.labs.userAgent.util.getUserAgent();
+  // Special case IE since IE's version is inside the parenthesis and
+  // without the '/'.
+  if (goog.labs.userAgent.browser.isIE()) {
+    return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
+  }
 
+  var versionTuples = goog.labs.userAgent.util.extractVersionTuples(
+      userAgentString);
 
-/**
- * @define {boolean} Whether the user agent is running on a Macintosh operating
- *     system.
- */
-goog.define('goog.userAgent.ASSUME_MAC', false);
+  // Construct a map for easy lookup.
+  var versionMap = {};
+  goog.array.forEach(versionTuples, function(tuple) {
+    // Note that the tuple is of length three, but we only care about the
+    // first two.
+    var key = tuple[0];
+    var value = tuple[1];
+    versionMap[key] = value;
+  });
 
+  var versionMapHasKey = goog.partial(goog.object.containsKey, versionMap);
 
-/**
- * @define {boolean} Whether the user agent is running on a Windows operating
- *     system.
- */
-goog.define('goog.userAgent.ASSUME_WINDOWS', false);
+  // Gives the value with the first key it finds, otherwise empty string.
+  function lookUpValueWithKeys(keys) {
+    var key = goog.array.find(keys, versionMapHasKey);
+    return versionMap[key] || '';
+  }
 
+  // Check Opera before Chrome since Opera 15+ has "Chrome" in the string.
+  // See
+  // http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
+  if (goog.labs.userAgent.browser.isOpera()) {
+    // Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.
+    // Opera uses 'OPR' for more recent UAs.
+    return lookUpValueWithKeys(['Version', 'Opera', 'OPR']);
+  }
 
-/**
- * @define {boolean} Whether the user agent is running on a Linux operating
- *     system.
- */
-goog.define('goog.userAgent.ASSUME_LINUX', false);
+  if (goog.labs.userAgent.browser.isChrome()) {
+    return lookUpValueWithKeys(['Chrome', 'CriOS']);
+  }
+
+  // Usually products browser versions are in the third tuple after "Mozilla"
+  // and the engine.
+  var tuple = versionTuples[2];
+  return tuple && tuple[1] || '';
+};
 
 
 /**
- * @define {boolean} Whether the user agent is running on a X11 windowing
- *     system.
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the browser version is higher or the same as the
+ *     given version.
  */
-goog.define('goog.userAgent.ASSUME_X11', false);
+goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
+  return goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(),
+                                     version) >= 0;
+};
 
 
 /**
- * @define {boolean} Whether the user agent is running on Android.
+ * Determines IE version. More information:
+ * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
+ * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
+ * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
+ * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
+ *
+ * @param {string} userAgent the User-Agent.
+ * @return {string}
+ * @private
  */
-goog.define('goog.userAgent.ASSUME_ANDROID', false);
+goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) {
+  // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
+  // bug. Example UA:
+  // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
+  // like Gecko.
+  // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
+  var rv = /rv: *([\d\.]*)/.exec(userAgent);
+  if (rv && rv[1]) {
+    return rv[1];
+  }
+
+  var version = '';
+  var msie = /MSIE +([\d\.]+)/.exec(userAgent);
+  if (msie && msie[1]) {
+    // IE in compatibility mode usually identifies itself as MSIE 7.0; in this
+    // case, use the Trident version to determine the version of IE. For more
+    // details, see the links above.
+    var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
+    if (msie[1] == '7.0') {
+      if (tridentVersion && tridentVersion[1]) {
+        switch (tridentVersion[1]) {
+          case '4.0':
+            version = '8.0';
+            break;
+          case '5.0':
+            version = '9.0';
+            break;
+          case '6.0':
+            version = '10.0';
+            break;
+          case '7.0':
+            version = '11.0';
+            break;
+        }
+      } else {
+        version = '7.0';
+      }
+    } else {
+      version = msie[1];
+    }
+  }
+  return version;
+};
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @define {boolean} Whether the user agent is running on an iPhone.
+ * @fileoverview Closure user agent detection.
+ * @see http://en.wikipedia.org/wiki/User_agent
+ * For more information on browser brand, platform, or device see the other
+ * sub-namespaces in goog.labs.userAgent (browser, platform, and device).
+ *
  */
-goog.define('goog.userAgent.ASSUME_IPHONE', false);
+
+goog.provide('goog.labs.userAgent.engine');
+
+goog.require('goog.array');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
 
 
 /**
- * @define {boolean} Whether the user agent is running on an iPad.
+ * @return {boolean} Whether the rendering engine is Presto.
  */
-goog.define('goog.userAgent.ASSUME_IPAD', false);
+goog.labs.userAgent.engine.isPresto = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Presto');
+};
 
 
 /**
- * @type {boolean}
- * @private
+ * @return {boolean} Whether the rendering engine is Trident.
  */
-goog.userAgent.PLATFORM_KNOWN_ =
-    goog.userAgent.ASSUME_MAC ||
-    goog.userAgent.ASSUME_WINDOWS ||
-    goog.userAgent.ASSUME_LINUX ||
-    goog.userAgent.ASSUME_X11 ||
-    goog.userAgent.ASSUME_ANDROID ||
-    goog.userAgent.ASSUME_IPHONE ||
-    goog.userAgent.ASSUME_IPAD;
+goog.labs.userAgent.engine.isTrident = function() {
+  // IE only started including the Trident token in IE8.
+  return goog.labs.userAgent.util.matchUserAgent('Trident') ||
+      goog.labs.userAgent.util.matchUserAgent('MSIE');
+};
 
 
 /**
- * Initialize the goog.userAgent constants that define which platform the user
- * agent is running on.
- * @private
+ * @return {boolean} Whether the rendering engine is WebKit.
  */
-goog.userAgent.initPlatform_ = function() {
-  /**
-   * Whether the user agent is running on a Macintosh operating system.
-   * @type {boolean}
-   * @private
-   */
-  goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM,
-      'Mac');
+goog.labs.userAgent.engine.isWebKit = function() {
+  return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit');
+};
 
-  /**
-   * Whether the user agent is running on a Windows operating system.
-   * @type {boolean}
-   * @private
-   */
-  goog.userAgent.detectedWindows_ = goog.string.contains(
-      goog.userAgent.PLATFORM, 'Win');
 
-  /**
-   * Whether the user agent is running on a Linux operating system.
-   * @type {boolean}
-   * @private
-   */
-  goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM,
-      'Linux');
+/**
+ * @return {boolean} Whether the rendering engine is Gecko.
+ */
+goog.labs.userAgent.engine.isGecko = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
+      !goog.labs.userAgent.engine.isWebKit() &&
+      !goog.labs.userAgent.engine.isTrident();
+};
 
-  /**
-   * Whether the user agent is running on a X11 windowing system.
-   * @type {boolean}
-   * @private
-   */
-  goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() &&
-      goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '',
-          'X11');
 
-  // Need user agent string for Android/IOS detection
-  var ua = goog.userAgent.getUserAgentString();
+/**
+ * @return {string} The rendering engine's version or empty string if version
+ *     can't be determined.
+ */
+goog.labs.userAgent.engine.getVersion = function() {
+  var userAgentString = goog.labs.userAgent.util.getUserAgent();
+  if (userAgentString) {
+    var tuples = goog.labs.userAgent.util.extractVersionTuples(
+        userAgentString);
 
-  /**
-   * Whether the user agent is running on Android.
-   * @type {boolean}
-   * @private
-   */
-  goog.userAgent.detectedAndroid_ = !!ua &&
-      goog.string.contains(ua, 'Android');
+    var engineTuple = tuples[1];
+    if (engineTuple) {
+      // In Gecko, the version string is either in the browser info or the
+      // Firefox version.  See Gecko user agent string reference:
+      // http://goo.gl/mULqa
+      if (engineTuple[0] == 'Gecko') {
+        return goog.labs.userAgent.engine.getVersionForKey_(
+            tuples, 'Firefox');
+      }
 
-  /**
-   * Whether the user agent is running on an iPhone.
-   * @type {boolean}
-   * @private
-   */
-  goog.userAgent.detectedIPhone_ = !!ua && goog.string.contains(ua, 'iPhone');
+      return engineTuple[1];
+    }
 
-  /**
-   * Whether the user agent is running on an iPad.
-   * @type {boolean}
-   * @private
-   */
-  goog.userAgent.detectedIPad_ = !!ua && goog.string.contains(ua, 'iPad');
+    // IE has only one version identifier, and the Trident version is
+    // specified in the parenthetical.
+    var browserTuple = tuples[0];
+    var info;
+    if (browserTuple && (info = browserTuple[2])) {
+      var match = /Trident\/([^\s;]+)/.exec(info);
+      if (match) {
+        return match[1];
+      }
+    }
+  }
+  return '';
 };
 
 
-if (!goog.userAgent.PLATFORM_KNOWN_) {
-  goog.userAgent.initPlatform_();
-}
-
-
 /**
- * Whether the user agent is running on a Macintosh operating system.
- * @type {boolean}
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the rendering engine version is higher or the same
+ *     as the given version.
  */
-goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
-    goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_;
+goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
+  return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),
+                                     version) >= 0;
+};
 
 
 /**
- * Whether the user agent is running on a Windows operating system.
- * @type {boolean}
+ * @param {!Array<!Array<string>>} tuples Version tuples.
+ * @param {string} key The key to look for.
+ * @return {string} The version string of the given key, if present.
+ *     Otherwise, the empty string.
+ * @private
  */
-goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
-    goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_;
+goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
+  // TODO(nnaze): Move to util if useful elsewhere.
 
+  var pair = goog.array.find(tuples, function(pair) {
+    return key == pair[0];
+  });
 
-/**
- * Whether the user agent is running on a Linux operating system.
- * @type {boolean}
- */
-goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
-    goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_;
+  return pair && pair[1] || '';
+};
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Whether the user agent is running on a X11 windowing system.
- * @type {boolean}
+ * @fileoverview Closure user agent platform detection.
+ * @see <a href="http://www.useragentstring.com/">User agent strings</a>
+ * For more information on browser brand, rendering engine, or device see the
+ * other sub-namespaces in goog.labs.userAgent (browser, engine, and device
+ * respectively).
+ *
  */
-goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
-    goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_;
 
+goog.provide('goog.labs.userAgent.platform');
 
-/**
- * Whether the user agent is running on Android.
- * @type {boolean}
- */
-goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
-    goog.userAgent.ASSUME_ANDROID : goog.userAgent.detectedAndroid_;
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
 
 
 /**
- * Whether the user agent is running on an iPhone.
- * @type {boolean}
+ * @return {boolean} Whether the platform is Android.
  */
-goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
-    goog.userAgent.ASSUME_IPHONE : goog.userAgent.detectedIPhone_;
+goog.labs.userAgent.platform.isAndroid = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Android');
+};
 
 
 /**
- * Whether the user agent is running on an iPad.
- * @type {boolean}
+ * @return {boolean} Whether the platform is iPod.
  */
-goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
-    goog.userAgent.ASSUME_IPAD : goog.userAgent.detectedIPad_;
+goog.labs.userAgent.platform.isIpod = function() {
+  return goog.labs.userAgent.util.matchUserAgent('iPod');
+};
 
 
 /**
- * @return {string} The string that describes the version number of the user
- *     agent.
- * @private
+ * @return {boolean} Whether the platform is iPhone.
  */
-goog.userAgent.determineVersion_ = function() {
-  // All browsers have different ways to detect the version and they all have
-  // different naming schemes.
-
-  // version is a string rather than a number because it may contain 'b', 'a',
-  // and so on.
-  var version = '', re;
-
-  if (goog.userAgent.OPERA && goog.global['opera']) {
-    var operaVersion = goog.global['opera'].version;
-    return goog.isFunction(operaVersion) ? operaVersion() : operaVersion;
-  }
-
-  if (goog.userAgent.GECKO) {
-    re = /rv\:([^\);]+)(\)|;)/;
-  } else if (goog.userAgent.IE) {
-    re = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/;
-  } else if (goog.userAgent.WEBKIT) {
-    // WebKit/125.4
-    re = /WebKit\/(\S+)/;
-  }
-
-  if (re) {
-    var arr = re.exec(goog.userAgent.getUserAgentString());
-    version = arr ? arr[1] : '';
-  }
-
-  if (goog.userAgent.IE) {
-    // IE9 can be in document mode 9 but be reporting an inconsistent user agent
-    // version.  If it is identifying as a version lower than 9 we take the
-    // documentMode as the version instead.  IE8 has similar behavior.
-    // It is recommended to set the X-UA-Compatible header to ensure that IE9
-    // uses documentMode 9.
-    var docMode = goog.userAgent.getDocumentMode_();
-    if (docMode > parseFloat(version)) {
-      return String(docMode);
-    }
-  }
-
-  return version;
+goog.labs.userAgent.platform.isIphone = function() {
+  return goog.labs.userAgent.util.matchUserAgent('iPhone') &&
+      !goog.labs.userAgent.util.matchUserAgent('iPod') &&
+      !goog.labs.userAgent.util.matchUserAgent('iPad');
 };
 
 
 /**
- * @return {number|undefined} Returns the document mode (for testing).
- * @private
+ * @return {boolean} Whether the platform is iPad.
  */
-goog.userAgent.getDocumentMode_ = function() {
-  // NOTE(user): goog.userAgent may be used in context where there is no DOM.
-  var doc = goog.global['document'];
-  return doc ? doc['documentMode'] : undefined;
+goog.labs.userAgent.platform.isIpad = function() {
+  return goog.labs.userAgent.util.matchUserAgent('iPad');
 };
 
 
 /**
- * The version of the user agent. This is a string because it might contain
- * 'b' (as in beta) as well as multiple dots.
- * @type {string}
+ * @return {boolean} Whether the platform is iOS.
  */
-goog.userAgent.VERSION = goog.userAgent.determineVersion_();
+goog.labs.userAgent.platform.isIos = function() {
+  return goog.labs.userAgent.platform.isIphone() ||
+      goog.labs.userAgent.platform.isIpad() ||
+      goog.labs.userAgent.platform.isIpod();
+};
 
 
 /**
- * Compares two version numbers.
- *
- * @param {string} v1 Version of first item.
- * @param {string} v2 Version of second item.
- *
- * @return {number}  1 if first argument is higher
- *                   0 if arguments are equal
- *                  -1 if second argument is higher.
- * @deprecated Use goog.string.compareVersions.
+ * @return {boolean} Whether the platform is Mac.
  */
-goog.userAgent.compare = function(v1, v2) {
-  return goog.string.compareVersions(v1, v2);
+goog.labs.userAgent.platform.isMacintosh = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Macintosh');
 };
 
 
 /**
- * Cache for {@link goog.userAgent.isVersionOrHigher}.
- * Calls to compareVersions are surprisingly expensive and, as a browser's
- * version number is unlikely to change during a session, we cache the results.
- * @const
- * @private
+ * Note: ChromeOS is not considered to be Linux as it does not report itself
+ * as Linux in the user agent string.
+ * @return {boolean} Whether the platform is Linux.
  */
-goog.userAgent.isVersionOrHigherCache_ = {};
+goog.labs.userAgent.platform.isLinux = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Linux');
+};
 
 
 /**
- * Whether the user agent version is higher or the same as the given version.
- * NOTE: When checking the version numbers for Firefox and Safari, be sure to
- * use the engine's version, not the browser's version number.  For example,
- * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
- * Opera and Internet Explorer versions match the product release number.<br>
- * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
- *     Webkit</a>
- * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
- *
- * @param {string|number} version The version to check.
- * @return {boolean} Whether the user agent version is higher or the same as
- *     the given version.
+ * @return {boolean} Whether the platform is Windows.
  */
-goog.userAgent.isVersionOrHigher = function(version) {
-  return goog.userAgent.ASSUME_ANY_VERSION ||
-      goog.userAgent.isVersionOrHigherCache_[version] ||
-      (goog.userAgent.isVersionOrHigherCache_[version] =
-          goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
+goog.labs.userAgent.platform.isWindows = function() {
+  return goog.labs.userAgent.util.matchUserAgent('Windows');
 };
 
 
 /**
- * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
- * @param {string|number} version The version to check.
- * @return {boolean} Whether the user agent version is higher or the same as
- *     the given version.
- * @deprecated Use goog.userAgent.isVersionOrHigher().
+ * @return {boolean} Whether the platform is ChromeOS.
  */
-goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
+goog.labs.userAgent.platform.isChromeOS = function() {
+  return goog.labs.userAgent.util.matchUserAgent('CrOS');
+};
 
 
 /**
- * Whether the IE effective document mode is higher or the same as the given
- * document mode version.
- * NOTE: Only for IE, return false for another browser.
+ * The version of the platform. We only determine the version for Windows,
+ * Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
+ * look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
+ * version 0.0.
  *
- * @param {number} documentMode The document mode version to check.
- * @return {boolean} Whether the IE effective document mode is higher or the
- *     same as the given version.
+ * @return {string} The platform version or empty string if version cannot be
+ *     determined.
  */
-goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
-  return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode;
+goog.labs.userAgent.platform.getVersion = function() {
+  var userAgentString = goog.labs.userAgent.util.getUserAgent();
+  var version = '', re;
+  if (goog.labs.userAgent.platform.isWindows()) {
+    re = /Windows (?:NT|Phone) ([0-9.]+)/;
+    var match = re.exec(userAgentString);
+    if (match) {
+      version = match[1];
+    } else {
+      version = '0.0';
+    }
+  } else if (goog.labs.userAgent.platform.isIos()) {
+    re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
+    var match = re.exec(userAgentString);
+    // Report the version as x.y.z and not x_y_z
+    version = match && match[1].replace(/_/g, '.');
+  } else if (goog.labs.userAgent.platform.isMacintosh()) {
+    re = /Mac OS X ([0-9_.]+)/;
+    var match = re.exec(userAgentString);
+    // Note: some old versions of Camino do not report an OSX version.
+    // Default to 10.
+    version = match ? match[1].replace(/_/g, '.') : '10';
+  } else if (goog.labs.userAgent.platform.isAndroid()) {
+    re = /Android\s+([^\);]+)(\)|;)/;
+    var match = re.exec(userAgentString);
+    version = match && match[1];
+  } else if (goog.labs.userAgent.platform.isChromeOS()) {
+    re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
+    var match = re.exec(userAgentString);
+    version = match && match[1];
+  }
+  return version || '';
 };
 
 
 /**
- * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
- * @param {number} version The version to check.
- * @return {boolean} Whether the IE effective document mode is higher or the
- *      same as the given version.
- * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the browser version is higher or the same as the
+ *     given version.
  */
-goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
+goog.labs.userAgent.platform.isVersionOrHigher = function(version) {
+  return goog.string.compareVersions(goog.labs.userAgent.platform.getVersion(),
+                                     version) >= 0;
+};
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * For IE version < 7, documentMode is undefined, so attempt to use the
- * CSS1Compat property to see if we are in standards mode. If we are in
- * standards mode, treat the browser version as the document mode. Otherwise,
- * IE is emulating version 5.
- * @type {number|undefined}
- * @const
+ * @fileoverview Rendering engine detection.
+ * @see <a href="http://www.useragentstring.com/">User agent strings</a>
+ * For information on the browser brand (such as Safari versus Chrome), see
+ * goog.userAgent.product.
+ * @author arv@google.com (Erik Arvidsson)
+ * @see ../demos/useragent.html
  */
-goog.userAgent.DOCUMENT_MODE = (function() {
-  var doc = goog.global['document'];
-  if (!doc || !goog.userAgent.IE) {
-    return undefined;
-  }
-  var mode = goog.userAgent.getDocumentMode_();
-  return mode || (doc['compatMode'] == 'CSS1Compat' ?
-      parseInt(goog.userAgent.VERSION, 10) : 5);
-})();
 
-goog.require('goog.userAgent');
+goog.provide('goog.userAgent');
 
-goog.provide('ol');
+goog.require('goog.labs.userAgent.browser');
+goog.require('goog.labs.userAgent.engine');
+goog.require('goog.labs.userAgent.platform');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
 
 
 /**
- * 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} Whether we know at compile-time that the browser is IE.
  */
+goog.define('goog.userAgent.ASSUME_IE', false);
 
 
 /**
- * @define {boolean} Assume touch.  Default is `false`.
+ * @define {boolean} Whether we know at compile-time that the browser is GECKO.
  */
-ol.ASSUME_TOUCH = false;
+goog.define('goog.userAgent.ASSUME_GECKO', false);
 
 
 /**
- * @define {boolean} Replace unused entries with NaNs.
+ * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
  */
-ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS = goog.DEBUG;
+goog.define('goog.userAgent.ASSUME_WEBKIT', 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.
+ * @define {boolean} Whether we know at compile-time that the browser is a
+ *     mobile device running WebKit e.g. iPhone or Android.
  */
-ol.DEFAULT_MAX_ZOOM = 42;
+goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
 
 
 /**
- * @define {number} Default min zoom level for the map view.  Default is `0`.
+ * @define {boolean} Whether we know at compile-time that the browser is OPERA.
  */
-ol.DEFAULT_MIN_ZOOM = 0;
+goog.define('goog.userAgent.ASSUME_OPERA', false);
 
 
 /**
- * @define {number} Default high water mark.
+ * @define {boolean} Whether the
+ *     {@code goog.userAgent.isVersionOrHigher}
+ *     function will return true for any version.
  */
-ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK = 2048;
+goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
 
 
 /**
- * @define {number} Default tile size.
+ * Whether we know the browser engine at compile-time.
+ * @type {boolean}
+ * @private
  */
-ol.DEFAULT_TILE_SIZE = 256;
+goog.userAgent.BROWSER_KNOWN_ =
+    goog.userAgent.ASSUME_IE ||
+    goog.userAgent.ASSUME_GECKO ||
+    goog.userAgent.ASSUME_MOBILE_WEBKIT ||
+    goog.userAgent.ASSUME_WEBKIT ||
+    goog.userAgent.ASSUME_OPERA;
 
 
 /**
- * @define {string} Default WMS version.
+ * Returns the userAgent string for the current browser.
+ *
+ * @return {string} The userAgent string.
  */
-ol.DEFAULT_WMS_VERSION = '1.3.0';
+goog.userAgent.getUserAgentString = function() {
+  return goog.labs.userAgent.util.getUserAgent();
+};
 
 
 /**
- * @define {number} Drag-rotate-zoom animation duration.
+ * TODO(nnaze): Change type to "Navigator" and update compilation targets.
+ * @return {Object} The native navigator object.
  */
-ol.DRAGROTATEANDZOOM_ANIMATION_DURATION = 400;
+goog.userAgent.getNavigator = function() {
+  // Need a local navigator reference instead of using the global one,
+  // to avoid the rare case where they reference different objects.
+  // (in a WorkerPool, for example).
+  return goog.global['navigator'] || null;
+};
 
 
 /**
- * @define {number} Drag-rotate animation duration.
+ * Whether the user agent is Opera.
+ * @type {boolean}
  */
-ol.DRAGROTATE_ANIMATION_DURATION = 250;
+goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
+    goog.userAgent.ASSUME_OPERA :
+    goog.labs.userAgent.browser.isOpera();
 
 
 /**
- * @define {number} Drag-zoom animation duration.
+ * Whether the user agent is Internet Explorer.
+ * @type {boolean}
  */
-ol.DRAGZOOM_ANIMATION_DURATION = 200;
+goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
+    goog.userAgent.ASSUME_IE :
+    goog.labs.userAgent.browser.isIE();
 
 
 /**
- * @define {number} Hysterisis pixels.
+ * Whether the user agent is Gecko. Gecko is the rendering engine used by
+ * Mozilla, Firefox, and others.
+ * @type {boolean}
  */
-ol.DRAG_BOX_HYSTERESIS_PIXELS = 8;
+goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
+    goog.userAgent.ASSUME_GECKO :
+    goog.labs.userAgent.engine.isGecko();
 
 
 /**
- * @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.
+ * Whether the user agent is WebKit. WebKit is the rendering engine that
+ * Safari, Android and others use.
+ * @type {boolean}
  */
-ol.ENABLE_CANVAS = true;
+goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
+    goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
+    goog.labs.userAgent.engine.isWebKit();
 
 
 /**
- * @define {boolean} Enable the DOM renderer (used as a fallback where Canvas is
- *     not available).  Default is `true`. Setting this to false at compile time
- *     in advanced mode removes all code supporting the DOM renderer from the
- *     build.
- */
+ * Whether the user agent is running on a mobile device.
+ *
+ * This is a separate function so that the logic can be tested.
+ *
+ * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
+ *
+ * @return {boolean} Whether the user agent is running on a mobile device.
+ * @private
+ */
+goog.userAgent.isMobile_ = function() {
+  return goog.userAgent.WEBKIT &&
+         goog.labs.userAgent.util.matchUserAgent('Mobile');
+};
+
+
+/**
+ * Whether the user agent is running on a mobile device.
+ *
+ * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
+ *   is promoted as the gecko/webkit logic is likely inaccurate.
+ *
+ * @type {boolean}
+ */
+goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
+                        goog.userAgent.isMobile_();
+
+
+/**
+ * Used while transitioning code to use WEBKIT instead.
+ * @type {boolean}
+ * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
+ * TODO(nicksantos): Delete this from goog.userAgent.
+ */
+goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
+
+
+/**
+ * @return {string} the platform (operating system) the user agent is running
+ *     on. Default to empty string because navigator.platform may not be defined
+ *     (on Rhino, for example).
+ * @private
+ */
+goog.userAgent.determinePlatform_ = function() {
+  var navigator = goog.userAgent.getNavigator();
+  return navigator && navigator.platform || '';
+};
+
+
+/**
+ * The platform (operating system) the user agent is running on. Default to
+ * empty string because navigator.platform may not be defined (on Rhino, for
+ * example).
+ * @type {string}
+ */
+goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Macintosh operating
+ *     system.
+ */
+goog.define('goog.userAgent.ASSUME_MAC', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Windows operating
+ *     system.
+ */
+goog.define('goog.userAgent.ASSUME_WINDOWS', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Linux operating
+ *     system.
+ */
+goog.define('goog.userAgent.ASSUME_LINUX', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a X11 windowing
+ *     system.
+ */
+goog.define('goog.userAgent.ASSUME_X11', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on Android.
+ */
+goog.define('goog.userAgent.ASSUME_ANDROID', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on an iPhone.
+ */
+goog.define('goog.userAgent.ASSUME_IPHONE', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on an iPad.
+ */
+goog.define('goog.userAgent.ASSUME_IPAD', false);
+
+
+/**
+ * @type {boolean}
+ * @private
+ */
+goog.userAgent.PLATFORM_KNOWN_ =
+    goog.userAgent.ASSUME_MAC ||
+    goog.userAgent.ASSUME_WINDOWS ||
+    goog.userAgent.ASSUME_LINUX ||
+    goog.userAgent.ASSUME_X11 ||
+    goog.userAgent.ASSUME_ANDROID ||
+    goog.userAgent.ASSUME_IPHONE ||
+    goog.userAgent.ASSUME_IPAD;
+
+
+/**
+ * Whether the user agent is running on a Macintosh operating system.
+ * @type {boolean}
+ */
+goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
+    goog.userAgent.ASSUME_MAC : goog.labs.userAgent.platform.isMacintosh();
+
+
+/**
+ * Whether the user agent is running on a Windows operating system.
+ * @type {boolean}
+ */
+goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
+    goog.userAgent.ASSUME_WINDOWS :
+    goog.labs.userAgent.platform.isWindows();
+
+
+/**
+ * Whether the user agent is Linux per the legacy behavior of
+ * goog.userAgent.LINUX, which considered ChromeOS to also be
+ * Linux.
+ * @return {boolean}
+ * @private
+ */
+goog.userAgent.isLegacyLinux_ = function() {
+  return goog.labs.userAgent.platform.isLinux() ||
+      goog.labs.userAgent.platform.isChromeOS();
+};
+
+
+/**
+ * Whether the user agent is running on a Linux operating system.
+ *
+ * Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
+ * while goog.labs.userAgent.platform considers ChromeOS and
+ * Linux to be different OSes.
+ *
+ * @type {boolean}
+ */
+goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
+    goog.userAgent.ASSUME_LINUX :
+    goog.userAgent.isLegacyLinux_();
+
+
+/**
+ * @return {boolean} Whether the user agent is an X11 windowing system.
+ * @private
+ */
+goog.userAgent.isX11_ = function() {
+  var navigator = goog.userAgent.getNavigator();
+  return !!navigator &&
+      goog.string.contains(navigator['appVersion'] || '', 'X11');
+};
+
+
+/**
+ * Whether the user agent is running on a X11 windowing system.
+ * @type {boolean}
+ */
+goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
+    goog.userAgent.ASSUME_X11 :
+    goog.userAgent.isX11_();
+
+
+/**
+ * Whether the user agent is running on Android.
+ * @type {boolean}
+ */
+goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
+    goog.userAgent.ASSUME_ANDROID :
+    goog.labs.userAgent.platform.isAndroid();
+
+
+/**
+ * Whether the user agent is running on an iPhone.
+ * @type {boolean}
+ */
+goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
+    goog.userAgent.ASSUME_IPHONE :
+    goog.labs.userAgent.platform.isIphone();
+
+
+/**
+ * Whether the user agent is running on an iPad.
+ * @type {boolean}
+ */
+goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
+    goog.userAgent.ASSUME_IPAD :
+    goog.labs.userAgent.platform.isIpad();
+
+
+/**
+ * @return {string} The string that describes the version number of the user
+ *     agent.
+ * @private
+ */
+goog.userAgent.determineVersion_ = function() {
+  // All browsers have different ways to detect the version and they all have
+  // different naming schemes.
+
+  // version is a string rather than a number because it may contain 'b', 'a',
+  // and so on.
+  var version = '', re;
+
+  if (goog.userAgent.OPERA && goog.global['opera']) {
+    var operaVersion = goog.global['opera'].version;
+    return goog.isFunction(operaVersion) ? operaVersion() : operaVersion;
+  }
+
+  if (goog.userAgent.GECKO) {
+    re = /rv\:([^\);]+)(\)|;)/;
+  } else if (goog.userAgent.IE) {
+    re = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/;
+  } else if (goog.userAgent.WEBKIT) {
+    // WebKit/125.4
+    re = /WebKit\/(\S+)/;
+  }
+
+  if (re) {
+    var arr = re.exec(goog.userAgent.getUserAgentString());
+    version = arr ? arr[1] : '';
+  }
+
+  if (goog.userAgent.IE) {
+    // IE9 can be in document mode 9 but be reporting an inconsistent user agent
+    // version.  If it is identifying as a version lower than 9 we take the
+    // documentMode as the version instead.  IE8 has similar behavior.
+    // It is recommended to set the X-UA-Compatible header to ensure that IE9
+    // uses documentMode 9.
+    var docMode = goog.userAgent.getDocumentMode_();
+    if (docMode > parseFloat(version)) {
+      return String(docMode);
+    }
+  }
+
+  return version;
+};
+
+
+/**
+ * @return {number|undefined} Returns the document mode (for testing).
+ * @private
+ */
+goog.userAgent.getDocumentMode_ = function() {
+  // NOTE(user): goog.userAgent may be used in context where there is no DOM.
+  var doc = goog.global['document'];
+  return doc ? doc['documentMode'] : undefined;
+};
+
+
+/**
+ * The version of the user agent. This is a string because it might contain
+ * 'b' (as in beta) as well as multiple dots.
+ * @type {string}
+ */
+goog.userAgent.VERSION = goog.userAgent.determineVersion_();
+
+
+/**
+ * Compares two version numbers.
+ *
+ * @param {string} v1 Version of first item.
+ * @param {string} v2 Version of second item.
+ *
+ * @return {number}  1 if first argument is higher
+ *                   0 if arguments are equal
+ *                  -1 if second argument is higher.
+ * @deprecated Use goog.string.compareVersions.
+ */
+goog.userAgent.compare = function(v1, v2) {
+  return goog.string.compareVersions(v1, v2);
+};
+
+
+/**
+ * Cache for {@link goog.userAgent.isVersionOrHigher}.
+ * Calls to compareVersions are surprisingly expensive and, as a browser's
+ * version number is unlikely to change during a session, we cache the results.
+ * @const
+ * @private
+ */
+goog.userAgent.isVersionOrHigherCache_ = {};
+
+
+/**
+ * Whether the user agent version is higher or the same as the given version.
+ * NOTE: When checking the version numbers for Firefox and Safari, be sure to
+ * use the engine's version, not the browser's version number.  For example,
+ * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
+ * Opera and Internet Explorer versions match the product release number.<br>
+ * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
+ *     Webkit</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
+ *
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the user agent version is higher or the same as
+ *     the given version.
+ */
+goog.userAgent.isVersionOrHigher = function(version) {
+  return goog.userAgent.ASSUME_ANY_VERSION ||
+      goog.userAgent.isVersionOrHigherCache_[version] ||
+      (goog.userAgent.isVersionOrHigherCache_[version] =
+          goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
+};
+
+
+/**
+ * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the user agent version is higher or the same as
+ *     the given version.
+ * @deprecated Use goog.userAgent.isVersionOrHigher().
+ */
+goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
+
+
+/**
+ * Whether the IE effective document mode is higher or the same as the given
+ * document mode version.
+ * NOTE: Only for IE, return false for another browser.
+ *
+ * @param {number} documentMode The document mode version to check.
+ * @return {boolean} Whether the IE effective document mode is higher or the
+ *     same as the given version.
+ */
+goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
+  return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode;
+};
+
+
+/**
+ * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
+ * @param {number} version The version to check.
+ * @return {boolean} Whether the IE effective document mode is higher or the
+ *      same as the given version.
+ * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
+ */
+goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
+
+
+/**
+ * For IE version < 7, documentMode is undefined, so attempt to use the
+ * CSS1Compat property to see if we are in standards mode. If we are in
+ * standards mode, treat the browser version as the document mode. Otherwise,
+ * IE is emulating version 5.
+ * @type {number|undefined}
+ * @const
+ */
+goog.userAgent.DOCUMENT_MODE = (function() {
+  var doc = goog.global['document'];
+  if (!doc || !goog.userAgent.IE) {
+    return undefined;
+  }
+  var mode = goog.userAgent.getDocumentMode_();
+  return mode || (doc['compatMode'] == 'CSS1Compat' ?
+      parseInt(goog.userAgent.VERSION, 10) : 5);
+})();
+
+goog.require('goog.userAgent');
+
+goog.provide('ol');
+
+
+/**
+ * Constants defined with the define tag cannot be changed in application
+ * code, but can be set at compile time.
+ * Some reduce the size of the build in advanced compile mode.
+ */
+
+
+/**
+ * @define {boolean} Assume touch.  Default is `false`.
+ */
+ol.ASSUME_TOUCH = false;
+
+
+/**
+ * @define {boolean} Replace unused entries with NaNs.
+ */
+ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS = goog.DEBUG;
+
+
+/**
+ * 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 high water mark.
+ */
+ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK = 2048;
+
+
+/**
+ * @define {number} Default tile size.
+ */
+ol.DEFAULT_TILE_SIZE = 256;
+
+
+/**
+ * @define {string} Default WMS version.
+ */
+ol.DEFAULT_WMS_VERSION = '1.3.0';
+
+
+/**
+ * @define {number} Drag-rotate-zoom animation duration.
+ */
+ol.DRAGROTATEANDZOOM_ANIMATION_DURATION = 400;
+
+
+/**
+ * @define {number} Drag-rotate animation duration.
+ */
+ol.DRAGROTATE_ANIMATION_DURATION = 250;
+
+
+/**
+ * @define {number} Drag-zoom animation duration.
+ */
+ol.DRAGZOOM_ANIMATION_DURATION = 200;
+
+
+/**
+ * @define {number} Hysteresis pixels.
+ */
+ol.DRAG_BOX_HYSTERESIS_PIXELS = 8;
+
+
+/**
+ * @define {boolean} Enable the Canvas renderer.  Default is `true`. Setting
+ *     this to false at compile time in advanced mode removes all code
+ *     supporting the Canvas renderer from the build.
+ */
+ol.ENABLE_CANVAS = true;
+
+
+/**
+ * @define {boolean} Enable the DOM renderer (used as a fallback where Canvas is
+ *     not available).  Default is `true`. Setting this to false at compile time
+ *     in advanced mode removes all code supporting the DOM renderer from the
+ *     build.
+ */
 ol.ENABLE_DOM = true;
 
 
@@ -6658,10 +8297,19 @@ ol.ENABLE_WEBGL = true;
 
 /**
  * @define {boolean} Support legacy IE (7-8).  Default is `false`.
+ *     If set to `true`, `goog.array.ASSUME_NATIVE_FUNCTIONS` must be set
+ *     to `false` because legacy IE do not support ECMAScript 5 array functions.
  */
 ol.LEGACY_IE_SUPPORT = false;
 
 
+/**
+ * @define {number} The size in pixels of the first atlas image. Default is
+ * `256`.
+ */
+ol.INITIAL_ATLAS_SIZE = 256;
+
+
 /**
  * The page is loaded using HTTPS.
  * @const
@@ -6685,6 +8333,14 @@ ol.IS_LEGACY_IE = goog.userAgent.IE &&
 ol.KEYBOARD_PAN_DURATION = 100;
 
 
+/**
+ * @define {number} The maximum size in pixels of atlas images. Default is
+ * `-1`, meaning it is not used (and `ol.ol.WEBGL_MAX_TEXTURE_SIZE` is
+ * used instead).
+ */
+ol.MAX_ATLAS_SIZE = -1;
+
+
 /**
  * @define {number} Maximum mouse wheel delta.
  */
@@ -6697,6 +8353,20 @@ ol.MOUSEWHEELZOOM_MAXDELTA = 1;
 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} Rotate animation duration.
  */
@@ -6715,6 +8385,24 @@ ol.SIMPLIFY_TOLERANCE = 0.5;
 ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
 
 
+/**
+ * The maximum supported WebGL texture size in pixels. If WebGL is not
+ * supported, the value is set to `undefined`.
+ * @const
+ * @type {number|undefined}
+ * @api
+ */
+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`
+
+
 /**
  * @define {number} Zoom slider animation duration.
  */
@@ -6987,8 +8675,8 @@ goog.math.sign = function(x) {
  *
  * Returns the longest possible array that is subarray of both of given arrays.
  *
- * @param {Array.<Object>} array1 First array of objects.
- * @param {Array.<Object>} array2 Second array of objects.
+ * @param {Array<Object>} array1 First array of objects.
+ * @param {Array<Object>} array2 Second array of objects.
  * @param {Function=} opt_compareFn Function that acts as a custom comparator
  *     for the array ojects. Function should return true if objects are equal,
  *     otherwise false.
@@ -6996,7 +8684,7 @@ goog.math.sign = function(x) {
  *     as a result subsequence. It accepts 2 arguments: index of common element
  *     in the first array and index in the second. The default function returns
  *     element from the first array.
- * @return {!Array.<Object>} A list of objects that are common to both arrays
+ * @return {!Array<Object>} A list of objects that are common to both arrays
  *     such that there is no common subsequence with size greater than the
  *     length of the list.
  */
@@ -7273,25 +8961,6 @@ ol.array.binaryFindNearest = function(arr, target) {
 };
 
 
-/**
- * Safe version of goog.array.extend that does not risk to overflow the stack
- * even if `array2` contains a large number of elements.
- *
- * @param {Array.<T>} array1 Array 1.
- * @param {Array.<T>} array2 Array 2.
- * @template T
- */
-ol.array.safeExtend = function(array1, array2) {
-  // goog.array.extend uses Array.prototype.push.apply, which can overflow the
-  // stack if array2 contains too many elements.  Repeatedly calling push
-  // performs as well on modern browsers.
-  var i, ii;
-  for (i = 0, ii = array2.length; i < ii; ++i) {
-    array1.push(array2[i]);
-  }
-};
-
-
 /**
  * @param {Array.<number>} arr Array.
  * @param {number} target Target.
@@ -7629,7 +9298,7 @@ goog.debug.EntryPointMonitor.prototype.unwrap;
 
 /**
  * An array of entry point callbacks.
- * @type {!Array.<function(!Function)>}
+ * @type {!Array<function(!Function)>}
  * @private
  */
 goog.debug.entryPointRegistry.refList_ = [];
@@ -7637,7 +9306,7 @@ goog.debug.entryPointRegistry.refList_ = [];
 
 /**
  * Monitors that should wrap all the entry points.
- * @type {!Array.<!goog.debug.EntryPointMonitor>}
+ * @type {!Array<!goog.debug.EntryPointMonitor>}
  * @private
  */
 goog.debug.entryPointRegistry.monitors_ = [];
@@ -7896,6 +9565,9 @@ goog.Disposable = function() {
     }
     goog.Disposable.instances_[goog.getUid(this)] = this;
   }
+  // Support sealing
+  this.disposed_ = this.disposed_;
+  this.onDisposeCallbacks_ = this.onDisposeCallbacks_;
 };
 
 
@@ -7943,14 +9615,14 @@ goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
 /**
  * Maps the unique ID of every undisposed {@code goog.Disposable} object to
  * the object itself.
- * @type {!Object.<number, !goog.Disposable>}
+ * @type {!Object<number, !goog.Disposable>}
  * @private
  */
 goog.Disposable.instances_ = {};
 
 
 /**
- * @return {!Array.<!goog.Disposable>} All {@code goog.Disposable} objects that
+ * @return {!Array<!goog.Disposable>} All {@code goog.Disposable} objects that
  *     haven't been disposed of.
  */
 goog.Disposable.getUndisposedObjects = function() {
@@ -7982,7 +9654,7 @@ goog.Disposable.prototype.disposed_ = false;
 
 /**
  * Callbacks to invoke when this object is disposed.
- * @type {Array.<!Function>}
+ * @type {Array<!Function>}
  * @private
  */
 goog.Disposable.prototype.onDisposeCallbacks_;
@@ -7991,7 +9663,7 @@ goog.Disposable.prototype.onDisposeCallbacks_;
 /**
  * If monitoring the goog.Disposable instances is enabled, stores the creation
  * stack trace of the Disposable instance.
- * @type {string}
+ * @const {string}
  */
 goog.Disposable.prototype.creationStack;
 
@@ -8171,7 +9843,7 @@ goog.provide('goog.events.EventId');
 /**
  * A templated class that is used when registering for events. Typical usage:
  * <code>
- *   /** @type {goog.events.EventId.<MyEventObj>}
+ *   /** @type {goog.events.EventId<MyEventObj>}
  *   var myEventId = new goog.events.EventId(
  *       goog.events.getUniqueId(('someEvent'));
  *
@@ -8260,7 +9932,7 @@ goog.events.Event = function(type, opt_target) {
   this.type = type instanceof goog.events.EventId ? String(type) : type;
 
   /**
-   * TODO(user): The type should probably be
+   * TODO(tbreisacher): The type should probably be
    * EventTarget|goog.events.EventTarget.
    *
    * Target of the event.
@@ -8378,7 +10050,6 @@ goog.events.Event.preventDefault = function(e) {
  * @fileoverview Event Types.
  *
  * @author arv@google.com (Erik Arvidsson)
- * @author mirkov@google.com (Mirko Visontai)
  */
 
 
@@ -8421,6 +10092,10 @@ goog.events.EventType = {
   // See http://msdn.microsoft.com/en-us/library/ie/ms536969(v=vs.85).aspx.
   SELECTSTART: 'selectstart', // IE, Safari, Chrome
 
+  // Wheel events
+  // http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
+  WHEEL: 'wheel',
+
   // Key events
   KEYPRESS: 'keypress',
   KEYDOWN: 'keydown',
@@ -8457,7 +10132,9 @@ goog.events.EventType = {
   DROP: 'drop',
   DRAGEND: 'dragend',
 
-  // WebKit touch events.
+  // Touch events
+  // Note that other touch events exist, but we should follow the W3C list here.
+  // http://www.w3.org/TR/touch-events/#list-of-touchevent-types
   TOUCHSTART: 'touchstart',
   TOUCHMOVE: 'touchmove',
   TOUCHEND: 'touchend',
@@ -8553,7 +10230,8 @@ goog.events.EventType = {
   MSPOINTERUP: 'MSPointerUp',
 
   // Native IMEs/input tools events.
-  TEXTINPUT: 'textinput',
+  TEXT: 'text',
+  TEXTINPUT: 'textInput',
   COMPOSITIONSTART: 'compositionstart',
   COMPOSITIONUPDATE: 'compositionupdate',
   COMPOSITIONEND: 'compositionend',
@@ -8604,6 +10282,7 @@ goog.events.EventType = {
 /**
  * @fileoverview Useful compiler idioms.
  *
+ * @author johnlenz@google.com (John Lenz)
  */
 
 goog.provide('goog.reflect');
@@ -8707,6 +10386,7 @@ goog.reflect.canAccessProperty = function(obj, prop) {
  * key and character code use {@link goog.events.KeyHandler}.
  * </pre>
  *
+ * @author arv@google.com (Erik Arvidsson)
  */
 
 goog.provide('goog.events.BrowserEvent');
@@ -8871,7 +10551,7 @@ goog.events.BrowserEvent.MouseButton = {
 
 /**
  * Static data for mapping mouse buttons.
- * @type {!Array.<number>}
+ * @type {!Array<number>}
  */
 goog.events.BrowserEvent.IEButtonMap = [
   1, // LEFT
@@ -8887,6 +10567,7 @@ goog.events.BrowserEvent.IEButtonMap = [
  * @param {EventTarget=} opt_currentTarget Current target for event.
  */
 goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
+  this.event_ = e;
   var type = this.type = e.type;
 
   // TODO(nicksantos): Change this.target to type EventTarget.
@@ -8916,12 +10597,28 @@ goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
 
   this.relatedTarget = relatedTarget;
 
-  // Webkit emits a lame warning whenever layerX/layerY is accessed.
-  // http://code.google.com/p/chromium/issues/detail?id=101733
-  this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
-      e.offsetX : e.layerX;
-  this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
-      e.offsetY : e.layerY;
+  // Lazily calculate the offsetX/Y properties because they trigger style
+  // recalc and layout. The original event's getters are themselves lazy.
+  if (Object.defineProperties) {
+    Object.defineProperties(this,
+        /** @lends {goog.events.BrowserEvent.prototype} */ ({
+          offsetX: {
+            configurable: true,
+            enumerable: true,
+            get: this.getOffsetX_,
+            set: this.setOffsetX_
+          },
+          offsetY: {
+            configurable: true,
+            enumerable: true,
+            get: this.getOffsetY_,
+            set: this.setOffsetY_
+          }
+        }));
+  } else {
+    this.offsetX = this.getOffsetX_();
+    this.offsetY = this.getOffsetY_();
+  }
 
   this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
   this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
@@ -8938,7 +10635,6 @@ goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
   this.metaKey = e.metaKey;
   this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
   this.state = e.state;
-  this.event_ = e;
   if (e.defaultPrevented) {
     this.preventDefault();
   }
@@ -9056,6 +10752,50 @@ goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
 goog.events.BrowserEvent.prototype.disposeInternal = function() {
 };
 
+
+/** @private @return {number} */
+goog.events.BrowserEvent.prototype.getOffsetX_ = function() {
+  // Webkit emits a lame warning whenever layerX/layerY is accessed.
+  // http://code.google.com/p/chromium/issues/detail?id=101733
+  return (goog.userAgent.WEBKIT || this.event_.offsetX !== undefined) ?
+      this.event_.offsetX : this.event_.layerX;
+};
+
+
+/** @private @param {number} offset */
+goog.events.BrowserEvent.prototype.setOffsetX_ = function(offset) {
+  Object.defineProperties(this,
+      /** @lends {goog.events.BrowserEvent.prototype} */ ({
+        offsetX: {
+          writable: true,
+          enumerable: true,
+          configurable: true,
+          value: offset
+        }
+      }));
+};
+
+
+/** @private @return {number} */
+goog.events.BrowserEvent.prototype.getOffsetY_ = function() {
+  return (goog.userAgent.WEBKIT || this.event_.offsetY !== undefined) ?
+      this.event_.offsetY : this.event_.layerY;
+};
+
+
+/** @private @param {number} offset */
+goog.events.BrowserEvent.prototype.setOffsetY_ = function(offset) {
+  Object.defineProperties(this,
+      /** @lends {goog.events.BrowserEvent.prototype} */ ({
+        offsetY: {
+          writable: true,
+          enumerable: true,
+          configurable: true,
+          value: offset
+        }
+      }));
+};
+
 // Copyright 2012 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -9072,6 +10812,7 @@ goog.events.BrowserEvent.prototype.disposeInternal = function() {
 
 /**
  * @fileoverview An interface for a listenable JavaScript object.
+ * @author chrishenry@google.com (Chris Henry)
  */
 
 goog.provide('goog.events.Listenable');
@@ -9154,7 +10895,7 @@ goog.events.Listenable.isImplementedBy = function(obj) {
  * (registered via listenOnce), it will no longer be a one-off
  * listener after a call to listen().
  *
- * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
  * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
  *     method.
  * @param {boolean=} opt_useCapture Whether to fire in capture phase
@@ -9178,7 +10919,7 @@ goog.events.Listenable.prototype.listen;
  * one-off listener, listenOnce does not modify the listeners (it is
  * still a once listener).
  *
- * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
  * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
  *     method.
  * @param {boolean=} opt_useCapture Whether to fire in capture phase
@@ -9194,7 +10935,7 @@ goog.events.Listenable.prototype.listenOnce;
 /**
  * Removes an event listener which was added with listen() or listenOnce().
  *
- * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
  * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
  *     method.
  * @param {boolean=} opt_useCapture Whether to fire in capture phase
@@ -9250,7 +10991,7 @@ goog.events.Listenable.prototype.removeAllListeners;
  * Returns the parent of this event target to use for capture/bubble
  * mechanism.
  *
- * NOTE(user): The name reflects the original implementation of
+ * NOTE(chrishenry): The name reflects the original implementation of
  * custom event target ({@code goog.events.EventTarget}). We decided
  * that changing the name is not worth it.
  *
@@ -9266,7 +11007,7 @@ goog.events.Listenable.prototype.getParentEventTarget;
  * does not perform actual capture/bubble. Only implementors of the
  * interface should be using this.
  *
- * @param {string|!goog.events.EventId.<EVENTOBJ>} type The type of the
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the
  *     listeners to fire.
  * @param {boolean} capture The capture mode of the listeners to fire.
  * @param {EVENTOBJ} eventObject The event object to fire.
@@ -9285,7 +11026,7 @@ goog.events.Listenable.prototype.fireListeners;
  *
  * @param {string|!goog.events.EventId} type The type of the listeners to fire.
  * @param {boolean} capture The capture mode of the listeners to fire.
- * @return {!Array.<goog.events.ListenableKey>} An array of registered
+ * @return {!Array<goog.events.ListenableKey>} An array of registered
  *     listeners.
  * @template EVENTOBJ
  */
@@ -9296,7 +11037,7 @@ goog.events.Listenable.prototype.getListeners;
  * Gets the goog.events.ListenableKey for the event or null if no such
  * listener is in use.
  *
- * @param {string|!goog.events.EventId.<EVENTOBJ>} type The name of the event
+ * @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event
  *     without the 'on' prefix.
  * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The
  *     listener function to get.
@@ -9314,7 +11055,7 @@ goog.events.Listenable.prototype.getListener;
  * signature. If either the type or capture parameters are
  * unspecified, the function will match on the remaining criteria.
  *
- * @param {string|!goog.events.EventId.<EVENTOBJ>=} opt_type Event type.
+ * @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type.
  * @param {boolean=} opt_capture Whether to check for capture or bubble
  *     listeners.
  * @return {boolean} Whether there is any active listeners matching
@@ -9523,7 +11264,7 @@ goog.events.Listener.prototype.markAsRemoved = function() {
   this.handler = null;
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -9538,4148 +11279,3501 @@ goog.events.Listener.prototype.markAsRemoved = function() {
 // limitations under the License.
 
 /**
- * @fileoverview Utilities for manipulating objects/maps/hashes.
+ * @fileoverview A map of listeners that provides utility functions to
+ * deal with listeners on an event target. Used by
+ * {@code goog.events.EventTarget}.
+ *
+ * WARNING: Do not use this class from outside goog.events package.
+ *
+ * @visibility {//closure/goog/bin/sizetests:__pkg__}
+ * @visibility {//closure/goog/events:__pkg__}
+ * @visibility {//closure/goog/labs/events:__pkg__}
  */
 
-goog.provide('goog.object');
+goog.provide('goog.events.ListenerMap');
+
+goog.require('goog.array');
+goog.require('goog.events.Listener');
+goog.require('goog.object');
+
 
 
 /**
- * Calls a function for each element in an object/map/hash.
- *
- * @param {Object.<K,V>} obj The object over which to iterate.
- * @param {function(this:T,V,?,Object.<K,V>):?} f The function to call
- *     for every element. This function takes 3 arguments (the element, the
- *     index and the object) and the return value is ignored.
- * @param {T=} opt_obj This is used as the 'this' object within f.
- * @template T,K,V
+ * Creates a new listener map.
+ * @param {EventTarget|goog.events.Listenable} src The src object.
+ * @constructor
+ * @final
  */
-goog.object.forEach = function(obj, f, opt_obj) {
-  for (var key in obj) {
-    f.call(opt_obj, obj[key], key, obj);
-  }
+goog.events.ListenerMap = function(src) {
+  /** @type {EventTarget|goog.events.Listenable} */
+  this.src = src;
+
+  /**
+   * Maps of event type to an array of listeners.
+   * @type {Object<string, !Array<!goog.events.Listener>>}
+   */
+  this.listeners = {};
+
+  /**
+   * The count of types in this map that have registered listeners.
+   * @private {number}
+   */
+  this.typeCount_ = 0;
 };
 
 
 /**
- * Calls a function for each element in an object/map/hash. If that call returns
- * true, adds the element to a new object.
- *
- * @param {Object.<K,V>} obj The object over which to iterate.
- * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to call
- *     for every element. This
- *     function takes 3 arguments (the element, the index and the object)
- *     and should return a boolean. If the return value is true the
- *     element is added to the result object. If it is false the
- *     element is not included.
- * @param {T=} opt_obj This is used as the 'this' object within f.
- * @return {!Object.<K,V>} a new object in which only elements that passed the
- *     test are present.
- * @template T,K,V
+ * @return {number} The count of event types in this map that actually
+ *     have registered listeners.
  */
-goog.object.filter = function(obj, f, opt_obj) {
-  var res = {};
-  for (var key in obj) {
-    if (f.call(opt_obj, obj[key], key, obj)) {
-      res[key] = obj[key];
-    }
-  }
-  return res;
+goog.events.ListenerMap.prototype.getTypeCount = function() {
+  return this.typeCount_;
 };
 
 
 /**
- * For every element in an object/map/hash calls a function and inserts the
- * result into a new object.
- *
- * @param {Object.<K,V>} obj The object over which to iterate.
- * @param {function(this:T,V,?,Object.<K,V>):R} f The function to call
- *     for every element. This function
- *     takes 3 arguments (the element, the index and the object)
- *     and should return something. The result will be inserted
- *     into a new object.
- * @param {T=} opt_obj This is used as the 'this' object within f.
- * @return {!Object.<K,R>} a new object with the results from f.
- * @template T,K,V,R
+ * @return {number} Total number of registered listeners.
  */
-goog.object.map = function(obj, f, opt_obj) {
-  var res = {};
-  for (var key in obj) {
-    res[key] = f.call(opt_obj, obj[key], key, obj);
+goog.events.ListenerMap.prototype.getListenerCount = function() {
+  var count = 0;
+  for (var type in this.listeners) {
+    count += this.listeners[type].length;
   }
-  return res;
+  return count;
 };
 
 
 /**
- * Calls a function for each element in an object/map/hash. If any
- * call returns true, returns true (without checking the rest). If
- * all calls return false, returns false.
+ * Adds an event listener. A listener can only be added once to an
+ * object and if it is added again the key for the listener is
+ * returned.
  *
- * @param {Object.<K,V>} obj The object to check.
- * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to
- *     call for every element. This function
- *     takes 3 arguments (the element, the index and the object) and should
- *     return a boolean.
- * @param {T=} opt_obj This is used as the 'this' object within f.
- * @return {boolean} true if any element passes the test.
- * @template T,K,V
+ * Note that a one-off listener will not change an existing listener,
+ * if any. On the other hand a normal listener will change existing
+ * one-off listener to become a normal listener.
+ *
+ * @param {string|!goog.events.EventId} type The listener event type.
+ * @param {!Function} listener This listener callback method.
+ * @param {boolean} callOnce Whether the listener is a one-off
+ *     listener.
+ * @param {boolean=} opt_useCapture The capture mode of the listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ *     listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
  */
-goog.object.some = function(obj, f, opt_obj) {
-  for (var key in obj) {
-    if (f.call(opt_obj, obj[key], key, obj)) {
-      return true;
+goog.events.ListenerMap.prototype.add = function(
+    type, listener, callOnce, opt_useCapture, opt_listenerScope) {
+  var typeStr = type.toString();
+  var listenerArray = this.listeners[typeStr];
+  if (!listenerArray) {
+    listenerArray = this.listeners[typeStr] = [];
+    this.typeCount_++;
+  }
+
+  var listenerObj;
+  var index = goog.events.ListenerMap.findListenerIndex_(
+      listenerArray, listener, opt_useCapture, opt_listenerScope);
+  if (index > -1) {
+    listenerObj = listenerArray[index];
+    if (!callOnce) {
+      // Ensure that, if there is an existing callOnce listener, it is no
+      // longer a callOnce listener.
+      listenerObj.callOnce = false;
     }
+  } else {
+    listenerObj = new goog.events.Listener(
+        listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
+    listenerObj.callOnce = callOnce;
+    listenerArray.push(listenerObj);
   }
-  return false;
+  return listenerObj;
 };
 
 
 /**
- * Calls a function for each element in an object/map/hash. If
- * all calls return true, returns true. If any call returns false, returns
- * false at this point and does not continue to check the remaining elements.
- *
- * @param {Object.<K,V>} obj The object to check.
- * @param {?function(this:T,V,?,Object.<K,V>):boolean} f The function to
- *     call for every element. This function
- *     takes 3 arguments (the element, the index and the object) and should
- *     return a boolean.
- * @param {T=} opt_obj This is used as the 'this' object within f.
- * @return {boolean} false if any element fails the test.
- * @template T,K,V
+ * Removes a matching listener.
+ * @param {string|!goog.events.EventId} type The listener event type.
+ * @param {!Function} listener This listener callback method.
+ * @param {boolean=} opt_useCapture The capture mode of the listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ *     listener.
+ * @return {boolean} Whether any listener was removed.
  */
-goog.object.every = function(obj, f, opt_obj) {
-  for (var key in obj) {
-    if (!f.call(opt_obj, obj[key], key, obj)) {
-      return false;
+goog.events.ListenerMap.prototype.remove = function(
+    type, listener, opt_useCapture, opt_listenerScope) {
+  var typeStr = type.toString();
+  if (!(typeStr in this.listeners)) {
+    return false;
+  }
+
+  var listenerArray = this.listeners[typeStr];
+  var index = goog.events.ListenerMap.findListenerIndex_(
+      listenerArray, listener, opt_useCapture, opt_listenerScope);
+  if (index > -1) {
+    var listenerObj = listenerArray[index];
+    listenerObj.markAsRemoved();
+    goog.array.removeAt(listenerArray, index);
+    if (listenerArray.length == 0) {
+      delete this.listeners[typeStr];
+      this.typeCount_--;
     }
+    return true;
   }
-  return true;
+  return false;
 };
 
 
 /**
- * Returns the number of key-value pairs in the object map.
- *
- * @param {Object} obj The object for which to get the number of key-value
- *     pairs.
- * @return {number} The number of key-value pairs in the object map.
+ * Removes the given listener object.
+ * @param {goog.events.ListenableKey} listener The listener to remove.
+ * @return {boolean} Whether the listener is removed.
  */
-goog.object.getCount = function(obj) {
-  // JS1.5 has __count__ but it has been deprecated so it raises a warning...
-  // in other words do not use. Also __count__ only includes the fields on the
-  // actual object and not in the prototype chain.
-  var rv = 0;
-  for (var key in obj) {
-    rv++;
+goog.events.ListenerMap.prototype.removeByKey = function(listener) {
+  var type = listener.type;
+  if (!(type in this.listeners)) {
+    return false;
   }
-  return rv;
+
+  var removed = goog.array.remove(this.listeners[type], listener);
+  if (removed) {
+    listener.markAsRemoved();
+    if (this.listeners[type].length == 0) {
+      delete this.listeners[type];
+      this.typeCount_--;
+    }
+  }
+  return removed;
 };
 
 
 /**
- * Returns one key from the object map, if any exists.
- * For map literals the returned key will be the first one in most of the
- * browsers (a know exception is Konqueror).
- *
- * @param {Object} obj The object to pick a key from.
- * @return {string|undefined} The key or undefined if the object is empty.
+ * Removes all listeners from this map. If opt_type is provided, only
+ * listeners that match the given type are removed.
+ * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
+ * @return {number} Number of listeners removed.
  */
-goog.object.getAnyKey = function(obj) {
-  for (var key in obj) {
-    return key;
+goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
+  var typeStr = opt_type && opt_type.toString();
+  var count = 0;
+  for (var type in this.listeners) {
+    if (!typeStr || type == typeStr) {
+      var listenerArray = this.listeners[type];
+      for (var i = 0; i < listenerArray.length; i++) {
+        ++count;
+        listenerArray[i].markAsRemoved();
+      }
+      delete this.listeners[type];
+      this.typeCount_--;
+    }
   }
+  return count;
 };
 
 
 /**
- * Returns one value from the object map, if any exists.
- * For map literals the returned value will be the first one in most of the
- * browsers (a know exception is Konqueror).
- *
- * @param {Object.<K,V>} obj The object to pick a value from.
- * @return {V|undefined} The value or undefined if the object is empty.
- * @template K,V
+ * Gets all listeners that match the given type and capture mode. The
+ * returned array is a copy (but the listener objects are not).
+ * @param {string|!goog.events.EventId} type The type of the listeners
+ *     to retrieve.
+ * @param {boolean} capture The capture mode of the listeners to retrieve.
+ * @return {!Array<goog.events.ListenableKey>} An array of matching
+ *     listeners.
  */
-goog.object.getAnyValue = function(obj) {
-  for (var key in obj) {
-    return obj[key];
+goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
+  var listenerArray = this.listeners[type.toString()];
+  var rv = [];
+  if (listenerArray) {
+    for (var i = 0; i < listenerArray.length; ++i) {
+      var listenerObj = listenerArray[i];
+      if (listenerObj.capture == capture) {
+        rv.push(listenerObj);
+      }
+    }
   }
+  return rv;
 };
 
 
 /**
- * Whether the object/hash/map contains the given object as a value.
- * An alias for goog.object.containsValue(obj, val).
+ * Gets the goog.events.ListenableKey for the event or null if no such
+ * listener is in use.
  *
- * @param {Object.<K,V>} obj The object in which to look for val.
- * @param {V} val The object for which to check.
- * @return {boolean} true if val is present.
- * @template K,V
+ * @param {string|!goog.events.EventId} type The type of the listener
+ *     to retrieve.
+ * @param {!Function} listener The listener function to get.
+ * @param {boolean} capture Whether the listener is a capturing listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ *     listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
  */
-goog.object.contains = function(obj, val) {
-  return goog.object.containsValue(obj, val);
+goog.events.ListenerMap.prototype.getListener = function(
+    type, listener, capture, opt_listenerScope) {
+  var listenerArray = this.listeners[type.toString()];
+  var i = -1;
+  if (listenerArray) {
+    i = goog.events.ListenerMap.findListenerIndex_(
+        listenerArray, listener, capture, opt_listenerScope);
+  }
+  return i > -1 ? listenerArray[i] : null;
 };
 
 
 /**
- * Returns the values of the object/map/hash.
+ * Whether there is a matching listener. If either the type or capture
+ * parameters are unspecified, the function will match on the
+ * remaining criteria.
  *
- * @param {Object.<K,V>} obj The object from which to get the values.
- * @return {!Array.<V>} The values in the object/map/hash.
- * @template K,V
+ * @param {string|!goog.events.EventId=} opt_type The type of the listener.
+ * @param {boolean=} opt_capture The capture mode of the listener.
+ * @return {boolean} Whether there is an active listener matching
+ *     the requested type and/or capture phase.
  */
-goog.object.getValues = function(obj) {
-  var res = [];
-  var i = 0;
-  for (var key in obj) {
-    res[i++] = obj[key];
-  }
-  return res;
+goog.events.ListenerMap.prototype.hasListener = function(
+    opt_type, opt_capture) {
+  var hasType = goog.isDef(opt_type);
+  var typeStr = hasType ? opt_type.toString() : '';
+  var hasCapture = goog.isDef(opt_capture);
+
+  return goog.object.some(
+      this.listeners, function(listenerArray, type) {
+        for (var i = 0; i < listenerArray.length; ++i) {
+          if ((!hasType || listenerArray[i].type == typeStr) &&
+              (!hasCapture || listenerArray[i].capture == opt_capture)) {
+            return true;
+          }
+        }
+
+        return false;
+      });
 };
 
 
 /**
- * Returns the keys of the object/map/hash.
- *
- * @param {Object} obj The object from which to get the keys.
- * @return {!Array.<string>} Array of property keys.
+ * Finds the index of a matching goog.events.Listener in the given
+ * listenerArray.
+ * @param {!Array<!goog.events.Listener>} listenerArray Array of listener.
+ * @param {!Function} listener The listener function.
+ * @param {boolean=} opt_useCapture The capture flag for the listener.
+ * @param {Object=} opt_listenerScope The listener scope.
+ * @return {number} The index of the matching listener within the
+ *     listenerArray.
+ * @private
  */
-goog.object.getKeys = function(obj) {
-  var res = [];
-  var i = 0;
-  for (var key in obj) {
-    res[i++] = key;
+goog.events.ListenerMap.findListenerIndex_ = function(
+    listenerArray, listener, opt_useCapture, opt_listenerScope) {
+  for (var i = 0; i < listenerArray.length; ++i) {
+    var listenerObj = listenerArray[i];
+    if (!listenerObj.removed &&
+        listenerObj.listener == listener &&
+        listenerObj.capture == !!opt_useCapture &&
+        listenerObj.handler == opt_listenerScope) {
+      return i;
+    }
   }
-  return res;
+  return -1;
 };
 
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Get a value from an object multiple levels deep.  This is useful for
- * pulling values from deeply nested objects, such as JSON responses.
- * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
+ * @fileoverview An event manager for both native browser event
+ * targets and custom JavaScript event targets
+ * ({@code goog.events.Listenable}). This provides an abstraction
+ * over browsers' event systems.
  *
- * @param {!Object} obj An object to get the value from.  Can be array-like.
- * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
- *     (as strings, or numbers, for array-like objects).  Can also be
- *     specified as a single array of keys.
- * @return {*} The resulting value.  If, at any point, the value for a key
- *     is undefined, returns undefined.
+ * It also provides a simulation of W3C event model's capture phase in
+ * Internet Explorer (IE 8 and below). Caveat: the simulation does not
+ * interact well with listeners registered directly on the elements
+ * (bypassing goog.events) or even with listeners registered via
+ * goog.events in a separate JS binary. In these cases, we provide
+ * no ordering guarantees.
+ *
+ * The listeners will receive a "patched" event object. Such event object
+ * contains normalized values for certain event properties that differs in
+ * different browsers.
+ *
+ * Example usage:
+ * <pre>
+ * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
+ * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.removeAll(myNode);
+ * </pre>
+ *
+ *                                            in IE and event object patching]
+ * @author arv@google.com (Erik Arvidsson)
+ *
+ * @see ../demos/events.html
+ * @see ../demos/event-propagation.html
+ * @see ../demos/stopevent.html
  */
-goog.object.getValueByKeys = function(obj, var_args) {
-  var isArrayLike = goog.isArrayLike(var_args);
-  var keys = isArrayLike ? var_args : arguments;
 
-  // Start with the 2nd parameter for the variable parameters syntax.
-  for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
-    obj = obj[keys[i]];
-    if (!goog.isDef(obj)) {
-      break;
-    }
-  }
+// IMPLEMENTATION NOTES:
+// goog.events stores an auxiliary data structure on each EventTarget
+// source being listened on. This allows us to take advantage of GC,
+// having the data structure GC'd when the EventTarget is GC'd. This
+// GC behavior is equivalent to using W3C DOM Events directly.
 
-  return obj;
-};
+goog.provide('goog.events');
+goog.provide('goog.events.CaptureSimulationMode');
+goog.provide('goog.events.Key');
+goog.provide('goog.events.ListenableType');
+
+goog.require('goog.asserts');
+goog.require('goog.debug.entryPointRegistry');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.BrowserFeature');
+goog.require('goog.events.Listenable');
+goog.require('goog.events.ListenerMap');
+
+goog.forwardDeclare('goog.debug.ErrorHandler');
+goog.forwardDeclare('goog.events.EventWrapper');
 
 
 /**
- * Whether the object/map/hash contains the given key.
- *
- * @param {Object} obj The object in which to look for key.
- * @param {*} key The key for which to check.
- * @return {boolean} true If the map contains the key.
+ * @typedef {number|goog.events.ListenableKey}
  */
-goog.object.containsKey = function(obj, key) {
-  return key in obj;
-};
+goog.events.Key;
 
 
 /**
- * Whether the object/map/hash contains the given value. This is O(n).
- *
- * @param {Object.<K,V>} obj The object in which to look for val.
- * @param {V} val The value for which to check.
- * @return {boolean} true If the map contains the value.
- * @template K,V
+ * @typedef {EventTarget|goog.events.Listenable}
  */
-goog.object.containsValue = function(obj, val) {
-  for (var key in obj) {
-    if (obj[key] == val) {
-      return true;
-    }
-  }
-  return false;
-};
+goog.events.ListenableType;
 
 
 /**
- * Searches an object for an element that satisfies the given condition and
- * returns its key.
- * @param {Object.<K,V>} obj The object to search in.
- * @param {function(this:T,V,string,Object.<K,V>):boolean} f The
- *      function to call for every element. Takes 3 arguments (the value,
- *     the key and the object) and should return a boolean.
- * @param {T=} opt_this An optional "this" context for the function.
- * @return {string|undefined} The key of an element for which the function
- *     returns true or undefined if no such element is found.
- * @template T,K,V
+ * Property name on a native event target for the listener map
+ * associated with the event target.
+ * @private @const {string}
  */
-goog.object.findKey = function(obj, f, opt_this) {
-  for (var key in obj) {
-    if (f.call(opt_this, obj[key], key, obj)) {
-      return key;
-    }
-  }
-  return undefined;
-};
+goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
 
 
 /**
- * Searches an object for an element that satisfies the given condition and
- * returns its value.
- * @param {Object.<K,V>} obj The object to search in.
- * @param {function(this:T,V,string,Object.<K,V>):boolean} f The function
- *     to call for every element. Takes 3 arguments (the value, the key
- *     and the object) and should return a boolean.
- * @param {T=} opt_this An optional "this" context for the function.
- * @return {V} The value of an element for which the function returns true or
- *     undefined if no such element is found.
- * @template T,K,V
+ * String used to prepend to IE event types.
+ * @const
+ * @private
  */
-goog.object.findValue = function(obj, f, opt_this) {
-  var key = goog.object.findKey(obj, f, opt_this);
-  return key && obj[key];
-};
+goog.events.onString_ = 'on';
 
 
 /**
- * Whether the object/map/hash is empty.
- *
- * @param {Object} obj The object to test.
- * @return {boolean} true if obj is empty.
+ * Map of computed "on<eventname>" strings for IE event types. Caching
+ * this removes an extra object allocation in goog.events.listen which
+ * improves IE6 performance.
+ * @const
+ * @dict
+ * @private
  */
-goog.object.isEmpty = function(obj) {
-  for (var key in obj) {
-    return false;
-  }
-  return true;
-};
+goog.events.onStringMap_ = {};
 
 
 /**
- * Removes all key value pairs from the object/map/hash.
- *
- * @param {Object} obj The object to clear.
+ * @enum {number} Different capture simulation mode for IE8-.
  */
-goog.object.clear = function(obj) {
-  for (var i in obj) {
-    delete obj[i];
-  }
+goog.events.CaptureSimulationMode = {
+  /**
+   * Does not perform capture simulation. Will asserts in IE8- when you
+   * add capture listeners.
+   */
+  OFF_AND_FAIL: 0,
+
+  /**
+   * Does not perform capture simulation, silently ignore capture
+   * listeners.
+   */
+  OFF_AND_SILENT: 1,
+
+  /**
+   * Performs capture simulation.
+   */
+  ON: 2
 };
 
 
 /**
- * Removes a key-value pair based on the key.
- *
- * @param {Object} obj The object from which to remove the key.
- * @param {*} key The key to remove.
- * @return {boolean} Whether an element was removed.
+ * @define {number} The capture simulation mode for IE8-. By default,
+ *     this is ON.
  */
-goog.object.remove = function(obj, key) {
-  var rv;
-  if ((rv = key in obj)) {
-    delete obj[key];
-  }
-  return rv;
-};
+goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
 
 
 /**
- * Adds a key-value pair to the object. Throws an exception if the key is
- * already in use. Use set if you want to change an existing pair.
- *
- * @param {Object.<K,V>} obj The object to which to add the key-value pair.
- * @param {string} key The key to add.
- * @param {V} val The value to add.
- * @template K,V
+ * Estimated count of total native listeners.
+ * @private {number}
  */
-goog.object.add = function(obj, key, val) {
-  if (key in obj) {
-    throw Error('The object already contains the key "' + key + '"');
-  }
-  goog.object.set(obj, key, val);
-};
+goog.events.listenerCountEstimate_ = 0;
 
 
 /**
- * Returns the value for the given key.
+ * Adds an event listener for a specific event on a native event
+ * target (such as a DOM element) or an object that has implemented
+ * {@link goog.events.Listenable}. A listener can only be added once
+ * to an object and if it is added again the key for the listener is
+ * returned. Note that if the existing listener is a one-off listener
+ * (registered via listenOnce), it will no longer be a one-off
+ * listener after a call to listen().
  *
- * @param {Object.<K,V>} obj The object from which to get the value.
- * @param {string} key The key for which to get the value.
- * @param {R=} opt_val The value to return if no item is found for the given
- *     key (default is undefined).
- * @return {V|R|undefined} The value for the given key.
- * @template K,V,R
+ * @param {EventTarget|goog.events.Listenable} src The node to listen
+ *     to events on.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
+ *     listener Callback method, or an object with a handleEvent function.
+ *     WARNING: passing an Object is now softly deprecated.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ *     false).
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @template T,EVENTOBJ
  */
-goog.object.get = function(obj, key, opt_val) {
-  if (key in obj) {
-    return obj[key];
+goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
+  if (goog.isArray(type)) {
+    for (var i = 0; i < type.length; i++) {
+      goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
+    }
+    return null;
+  }
+
+  listener = goog.events.wrapListener(listener);
+  if (goog.events.Listenable.isImplementedBy(src)) {
+    return src.listen(
+        /** @type {string|!goog.events.EventId} */ (type),
+        listener, opt_capt, opt_handler);
+  } else {
+    return goog.events.listen_(
+        /** @type {!EventTarget} */ (src),
+        /** @type {string|!goog.events.EventId} */ (type),
+        listener, /* callOnce */ false, opt_capt, opt_handler);
   }
-  return opt_val;
 };
 
 
 /**
- * Adds a key-value pair to the object/map/hash.
+ * Adds an event listener for a specific event on a native event
+ * target. A listener can only be added once to an object and if it
+ * is added again the key for the listener is returned.
  *
- * @param {Object.<K,V>} obj The object to which to add the key-value pair.
- * @param {string} key The key to add.
- * @param {V} value The value to add.
- * @template K,V
+ * Note that a one-off listener will not change an existing listener,
+ * if any. On the other hand a normal listener will change existing
+ * one-off listener to become a normal listener.
+ *
+ * @param {EventTarget} src The node to listen to events on.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {!Function} listener Callback function.
+ * @param {boolean} callOnce Whether the listener is a one-off
+ *     listener or otherwise.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ *     false).
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @private
  */
-goog.object.set = function(obj, key, value) {
-  obj[key] = value;
-};
+goog.events.listen_ = function(
+    src, type, listener, callOnce, opt_capt, opt_handler) {
+  if (!type) {
+    throw Error('Invalid event type');
+  }
+
+  var capture = !!opt_capt;
+  if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
+    if (goog.events.CAPTURE_SIMULATION_MODE ==
+        goog.events.CaptureSimulationMode.OFF_AND_FAIL) {
+      goog.asserts.fail('Can not register capture listener in IE8-.');
+      return null;
+    } else if (goog.events.CAPTURE_SIMULATION_MODE ==
+        goog.events.CaptureSimulationMode.OFF_AND_SILENT) {
+      return null;
+    }
+  }
+
+  var listenerMap = goog.events.getListenerMap_(src);
+  if (!listenerMap) {
+    src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
+        new goog.events.ListenerMap(src);
+  }
 
+  var listenerObj = listenerMap.add(
+      type, listener, callOnce, opt_capt, opt_handler);
 
-/**
- * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
- *
- * @param {Object.<K,V>} obj The object to which to add the key-value pair.
- * @param {string} key The key to add.
- * @param {V} value The value to add if the key wasn't present.
- * @return {V} The value of the entry at the end of the function.
- * @template K,V
- */
-goog.object.setIfUndefined = function(obj, key, value) {
-  return key in obj ? obj[key] : (obj[key] = value);
+  // If the listenerObj already has a proxy, it has been set up
+  // previously. We simply return.
+  if (listenerObj.proxy) {
+    return listenerObj;
+  }
+
+  var proxy = goog.events.getProxy();
+  listenerObj.proxy = proxy;
+
+  proxy.src = src;
+  proxy.listener = listenerObj;
+
+  // Attach the proxy through the browser's API
+  if (src.addEventListener) {
+    src.addEventListener(type.toString(), proxy, capture);
+  } else {
+    // The else above used to be else if (src.attachEvent) and then there was
+    // another else statement that threw an exception warning the developer
+    // they made a mistake. This resulted in an extra object allocation in IE6
+    // due to a wrapper object that had to be implemented around the element
+    // and so was removed.
+    src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
+  }
+
+  goog.events.listenerCountEstimate_++;
+  return listenerObj;
 };
 
 
 /**
- * Does a flat clone of the object.
- *
- * @param {Object.<K,V>} obj Object to clone.
- * @return {!Object.<K,V>} Clone of the input object.
- * @template K,V
+ * Helper function for returning a proxy function.
+ * @return {!Function} A new or reused function object.
  */
-goog.object.clone = function(obj) {
-  // We cannot use the prototype trick because a lot of methods depend on where
-  // the actual key is set.
-
-  var res = {};
-  for (var key in obj) {
-    res[key] = obj[key];
-  }
-  return res;
-  // We could also use goog.mixin but I wanted this to be independent from that.
+goog.events.getProxy = function() {
+  var proxyCallbackFunction = goog.events.handleBrowserEvent_;
+  // Use a local var f to prevent one allocation.
+  var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
+      function(eventObject) {
+        return proxyCallbackFunction.call(f.src, f.listener, eventObject);
+      } :
+      function(eventObject) {
+        var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
+        // NOTE(chrishenry): In IE, we hack in a capture phase. However, if
+        // there is inline event handler which tries to prevent default (for
+        // example <a href="..." onclick="return false">...</a>) in a
+        // descendant element, the prevent default will be overridden
+        // by this listener if this listener were to return true. Hence, we
+        // return undefined.
+        if (!v) return v;
+      };
+  return f;
 };
 
 
 /**
- * Clones a value. The input may be an Object, Array, or basic type. Objects and
- * arrays will be cloned recursively.
- *
- * WARNINGS:
- * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
- * that refer to themselves will cause infinite recursion.
+ * Adds an event listener for a specific event on a native event
+ * target (such as a DOM element) or an object that has implemented
+ * {@link goog.events.Listenable}. After the event has fired the event
+ * listener is removed from the target.
  *
- * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
- * copies UIDs created by <code>getUid</code> into cloned results.
+ * If an existing listener already exists, listenOnce will do
+ * nothing. In particular, if the listener was previously registered
+ * via listen(), listenOnce() will not turn the listener into a
+ * one-off listener. Similarly, if there is already an existing
+ * one-off listener, listenOnce does not modify the listeners (it is
+ * still a once listener).
  *
- * @param {*} obj The value to clone.
- * @return {*} A clone of the input value.
+ * @param {EventTarget|goog.events.Listenable} src The node to listen
+ *     to events on.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
+ *     listener Callback method.
+ * @param {boolean=} opt_capt Fire in capture phase?.
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @template T,EVENTOBJ
  */
-goog.object.unsafeClone = 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.object.unsafeClone(obj[key]);
+goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
+  if (goog.isArray(type)) {
+    for (var i = 0; i < type.length; i++) {
+      goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
     }
-    return clone;
+    return null;
   }
 
-  return obj;
+  listener = goog.events.wrapListener(listener);
+  if (goog.events.Listenable.isImplementedBy(src)) {
+    return src.listenOnce(
+        /** @type {string|!goog.events.EventId} */ (type),
+        listener, opt_capt, opt_handler);
+  } else {
+    return goog.events.listen_(
+        /** @type {!EventTarget} */ (src),
+        /** @type {string|!goog.events.EventId} */ (type),
+        listener, /* callOnce */ true, opt_capt, opt_handler);
+  }
 };
 
 
 /**
- * Returns a new object in which all the keys and values are interchanged
- * (keys become values and values become keys). If multiple keys map to the
- * same value, the chosen transposed value is implementation-dependent.
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.Listenable}. A listener can
+ * only be added once to an object.
  *
- * @param {Object} obj The object to transpose.
- * @return {!Object} The transposed object.
+ * @param {EventTarget|goog.events.Listenable} src The target to
+ *     listen to events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener
+ *     Callback method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ *     false).
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @template T
  */
-goog.object.transpose = function(obj) {
-  var transposed = {};
-  for (var key in obj) {
-    transposed[obj[key]] = key;
-  }
-  return transposed;
+goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
+    opt_handler) {
+  wrapper.listen(src, listener, opt_capt, opt_handler);
 };
 
 
 /**
- * The names of the fields that are defined on Object.prototype.
- * @type {Array.<string>}
- * @private
- */
-goog.object.PROTOTYPE_FIELDS_ = [
-  'constructor',
-  'hasOwnProperty',
-  'isPrototypeOf',
-  'propertyIsEnumerable',
-  'toLocaleString',
-  'toString',
-  'valueOf'
-];
-
-
-/**
- * Extends an object with another object.
- * This operates 'in-place'; it does not create a new Object.
- *
- * Example:
- * var o = {};
- * goog.object.extend(o, {a: 0, b: 1});
- * o; // {a: 0, b: 1}
- * goog.object.extend(o, {b: 2, c: 3});
- * o; // {a: 0, b: 2, c: 3}
+ * Removes an event listener which was added with listen().
  *
- * @param {Object} target The object to modify. Existing properties will be
- *     overwritten if they are also present in one of the objects in
- *     {@code var_args}.
- * @param {...Object} var_args The objects from which values will be copied.
+ * @param {EventTarget|goog.events.Listenable} src The target to stop
+ *     listening to events on.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type or array of event types to unlisten to.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
+ *     listener function to remove.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ *     whether the listener is fired during the capture or bubble phase of the
+ *     event.
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * @return {?boolean} indicating whether the listener was there to remove.
+ * @template EVENTOBJ
  */
-goog.object.extend = function(target, var_args) {
-  var key, source;
-  for (var i = 1; i < arguments.length; i++) {
-    source = arguments[i];
-    for (key in source) {
-      target[key] = source[key];
+goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
+  if (goog.isArray(type)) {
+    for (var i = 0; i < type.length; i++) {
+      goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
     }
+    return null;
+  }
 
-    // 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).
+  listener = goog.events.wrapListener(listener);
+  if (goog.events.Listenable.isImplementedBy(src)) {
+    return src.unlisten(
+        /** @type {string|!goog.events.EventId} */ (type),
+        listener, opt_capt, opt_handler);
+  }
 
-    for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
-      key = goog.object.PROTOTYPE_FIELDS_[j];
-      if (Object.prototype.hasOwnProperty.call(source, key)) {
-        target[key] = source[key];
-      }
+  if (!src) {
+    // TODO(chrishenry): We should tighten the API to only accept
+    // non-null objects, or add an assertion here.
+    return false;
+  }
+
+  var capture = !!opt_capt;
+  var listenerMap = goog.events.getListenerMap_(
+      /** @type {!EventTarget} */ (src));
+  if (listenerMap) {
+    var listenerObj = listenerMap.getListener(
+        /** @type {string|!goog.events.EventId} */ (type),
+        listener, capture, opt_handler);
+    if (listenerObj) {
+      return goog.events.unlistenByKey(listenerObj);
     }
   }
+
+  return false;
 };
 
 
 /**
- * Creates a new object built from the key-value pairs provided as arguments.
- * @param {...*} var_args If only one argument is provided and it is an array
- *     then this is used as the arguments,  otherwise even arguments are used as
- *     the property names and odd arguments are used as the property values.
- * @return {!Object} The new object.
- * @throws {Error} If there are uneven number of arguments or there is only one
- *     non array argument.
+ * Removes an event listener which was added with listen() by the key
+ * returned by listen().
+ *
+ * @param {goog.events.Key} key The key returned by listen() for this
+ *     event listener.
+ * @return {boolean} indicating whether the listener was there to remove.
  */
-goog.object.create = function(var_args) {
-  var argLength = arguments.length;
-  if (argLength == 1 && goog.isArray(arguments[0])) {
-    return goog.object.create.apply(null, arguments[0]);
+goog.events.unlistenByKey = function(key) {
+  // TODO(chrishenry): Remove this check when tests that rely on this
+  // are fixed.
+  if (goog.isNumber(key)) {
+    return false;
   }
 
-  if (argLength % 2) {
-    throw Error('Uneven number of arguments');
+  var listener = /** @type {goog.events.ListenableKey} */ (key);
+  if (!listener || listener.removed) {
+    return false;
   }
 
-  var rv = {};
-  for (var i = 0; i < argLength; i += 2) {
-    rv[arguments[i]] = arguments[i + 1];
+  var src = listener.src;
+  if (goog.events.Listenable.isImplementedBy(src)) {
+    return src.unlistenByKey(listener);
   }
-  return rv;
-};
 
-
-/**
- * Creates a new object where the property names come from the arguments but
- * the value is always set to true
- * @param {...*} var_args If only one argument is provided and it is an array
- *     then this is used as the arguments,  otherwise the arguments are used
- *     as the property names.
- * @return {!Object} The new object.
- */
-goog.object.createSet = function(var_args) {
-  var argLength = arguments.length;
-  if (argLength == 1 && goog.isArray(arguments[0])) {
-    return goog.object.createSet.apply(null, arguments[0]);
+  var type = listener.type;
+  var proxy = listener.proxy;
+  if (src.removeEventListener) {
+    src.removeEventListener(type, proxy, listener.capture);
+  } else if (src.detachEvent) {
+    src.detachEvent(goog.events.getOnString_(type), proxy);
   }
+  goog.events.listenerCountEstimate_--;
 
-  var rv = {};
-  for (var i = 0; i < argLength; i++) {
-    rv[arguments[i]] = true;
+  var listenerMap = goog.events.getListenerMap_(
+      /** @type {!EventTarget} */ (src));
+  // TODO(chrishenry): Try to remove this conditional and execute the
+  // first branch always. This should be safe.
+  if (listenerMap) {
+    listenerMap.removeByKey(listener);
+    if (listenerMap.getTypeCount() == 0) {
+      // Null the src, just because this is simple to do (and useful
+      // for IE <= 7).
+      listenerMap.src = null;
+      // We don't use delete here because IE does not allow delete
+      // on a window object.
+      src[goog.events.LISTENER_MAP_PROP_] = null;
+    }
+  } else {
+    listener.markAsRemoved();
   }
-  return rv;
+
+  return true;
 };
 
 
 /**
- * Creates an immutable view of the underlying object, if the browser
- * supports immutable objects.
- *
- * In default mode, writes to this view will fail silently. In strict mode,
- * they will throw an error.
+ * Removes an event listener which was added with listenWithWrapper().
  *
- * @param {!Object.<K,V>} obj An object.
- * @return {!Object.<K,V>} An immutable view of that object, or the
- *     original object if this browser does not support immutables.
- * @template K,V
+ * @param {EventTarget|goog.events.Listenable} src The target to stop
+ *     listening to events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
+ *     listener function to remove.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ *     whether the listener is fired during the capture or bubble phase of the
+ *     event.
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
  */
-goog.object.createImmutableView = function(obj) {
-  var result = obj;
-  if (Object.isFrozen && !Object.isFrozen(obj)) {
-    result = Object.create(obj);
-    Object.freeze(result);
-  }
-  return result;
-};
-
-
-/**
- * @param {!Object} obj An object.
- * @return {boolean} Whether this is an immutable view of the object.
- */
-goog.object.isImmutableView = function(obj) {
-  return !!Object.isFrozen && Object.isFrozen(obj);
+goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
+    opt_handler) {
+  wrapper.unlisten(src, listener, opt_capt, opt_handler);
 };
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview A map of listeners that provides utility functions to
- * deal with listeners on an event target. Used by
- * {@code goog.events.EventTarget}.
- *
- * WARNING: Do not use this class from outside goog.events package.
+ * Removes all listeners from an object. You can also optionally
+ * remove listeners of a particular type.
  *
- * @visibility {//closure/goog/bin/sizetests:__pkg__}
- * @visibility {//closure/goog/events:__pkg__}
- * @visibility {//closure/goog/labs/events:__pkg__}
- */
-
-goog.provide('goog.events.ListenerMap');
-
-goog.require('goog.array');
-goog.require('goog.events.Listener');
-goog.require('goog.object');
-
-
-
-/**
- * Creates a new listener map.
- * @param {EventTarget|goog.events.Listenable} src The src object.
- * @constructor
- * @final
+ * @param {Object|undefined} obj Object to remove listeners from. Must be an
+ *     EventTarget or a goog.events.Listenable.
+ * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
+ *     Default is all types.
+ * @return {number} Number of listeners removed.
  */
-goog.events.ListenerMap = function(src) {
-  /** @type {EventTarget|goog.events.Listenable} */
-  this.src = src;
-
-  /**
-   * Maps of event type to an array of listeners.
-   * @type {Object.<string, !Array.<!goog.events.Listener>>}
-   */
-  this.listeners = {};
-
-  /**
-   * The count of types in this map that have registered listeners.
-   * @private {number}
-   */
-  this.typeCount_ = 0;
-};
+goog.events.removeAll = function(obj, opt_type) {
+  // TODO(chrishenry): Change the type of obj to
+  // (!EventTarget|!goog.events.Listenable).
 
+  if (!obj) {
+    return 0;
+  }
 
-/**
- * @return {number} The count of event types in this map that actually
- *     have registered listeners.
- */
-goog.events.ListenerMap.prototype.getTypeCount = function() {
-  return this.typeCount_;
-};
+  if (goog.events.Listenable.isImplementedBy(obj)) {
+    return obj.removeAllListeners(opt_type);
+  }
 
+  var listenerMap = goog.events.getListenerMap_(
+      /** @type {!EventTarget} */ (obj));
+  if (!listenerMap) {
+    return 0;
+  }
 
-/**
- * @return {number} Total number of registered listeners.
- */
-goog.events.ListenerMap.prototype.getListenerCount = function() {
   var count = 0;
-  for (var type in this.listeners) {
-    count += this.listeners[type].length;
+  var typeStr = opt_type && opt_type.toString();
+  for (var type in listenerMap.listeners) {
+    if (!typeStr || type == typeStr) {
+      // Clone so that we don't need to worry about unlistenByKey
+      // changing the content of the ListenerMap.
+      var listeners = listenerMap.listeners[type].concat();
+      for (var i = 0; i < listeners.length; ++i) {
+        if (goog.events.unlistenByKey(listeners[i])) {
+          ++count;
+        }
+      }
+    }
   }
   return count;
 };
 
 
 /**
- * Adds an event listener. A listener can only be added once to an
- * object and if it is added again the key for the listener is
- * returned.
- *
- * Note that a one-off listener will not change an existing listener,
- * if any. On the other hand a normal listener will change existing
- * one-off listener to become a normal listener.
+ * Gets the listeners for a given object, type and capture phase.
  *
- * @param {string|!goog.events.EventId} type The listener event type.
- * @param {!Function} listener This listener callback method.
- * @param {boolean} callOnce Whether the listener is a one-off
- *     listener.
- * @param {boolean=} opt_useCapture The capture mode of the listener.
- * @param {Object=} opt_listenerScope Object in whose scope to call the
- *     listener.
- * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @param {Object} obj Object to get listeners for.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Capture phase?.
+ * @return {Array<goog.events.Listener>} Array of listener objects.
  */
-goog.events.ListenerMap.prototype.add = function(
-    type, listener, callOnce, opt_useCapture, opt_listenerScope) {
-  var typeStr = type.toString();
-  var listenerArray = this.listeners[typeStr];
-  if (!listenerArray) {
-    listenerArray = this.listeners[typeStr] = [];
-    this.typeCount_++;
-  }
-
-  var listenerObj;
-  var index = goog.events.ListenerMap.findListenerIndex_(
-      listenerArray, listener, opt_useCapture, opt_listenerScope);
-  if (index > -1) {
-    listenerObj = listenerArray[index];
-    if (!callOnce) {
-      // Ensure that, if there is an existing callOnce listener, it is no
-      // longer a callOnce listener.
-      listenerObj.callOnce = false;
-    }
+goog.events.getListeners = function(obj, type, capture) {
+  if (goog.events.Listenable.isImplementedBy(obj)) {
+    return obj.getListeners(type, capture);
   } else {
-    listenerObj = new goog.events.Listener(
-        listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
-    listenerObj.callOnce = callOnce;
-    listenerArray.push(listenerObj);
+    if (!obj) {
+      // TODO(chrishenry): We should tighten the API to accept
+      // !EventTarget|goog.events.Listenable, and add an assertion here.
+      return [];
+    }
+
+    var listenerMap = goog.events.getListenerMap_(
+        /** @type {!EventTarget} */ (obj));
+    return listenerMap ? listenerMap.getListeners(type, capture) : [];
   }
-  return listenerObj;
 };
 
 
 /**
- * Removes a matching listener.
- * @param {string|!goog.events.EventId} type The listener event type.
- * @param {!Function} listener This listener callback method.
- * @param {boolean=} opt_useCapture The capture mode of the listener.
- * @param {Object=} opt_listenerScope Object in whose scope to call the
- *     listener.
- * @return {boolean} Whether any listener was removed.
+ * Gets the goog.events.Listener for the event or null if no such listener is
+ * in use.
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target from
+ *     which to get listeners.
+ * @param {?string|!goog.events.EventId<EVENTOBJ>} type The type of the event.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The
+ *     listener function to get.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ *                            whether the listener is fired during the
+ *                            capture or bubble phase of the event.
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
+ * @template EVENTOBJ
  */
-goog.events.ListenerMap.prototype.remove = function(
-    type, listener, opt_useCapture, opt_listenerScope) {
-  var typeStr = type.toString();
-  if (!(typeStr in this.listeners)) {
-    return false;
-  }
-
-  var listenerArray = this.listeners[typeStr];
-  var index = goog.events.ListenerMap.findListenerIndex_(
-      listenerArray, listener, opt_useCapture, opt_listenerScope);
-  if (index > -1) {
-    var listenerObj = listenerArray[index];
-    listenerObj.markAsRemoved();
-    goog.array.removeAt(listenerArray, index);
-    if (listenerArray.length == 0) {
-      delete this.listeners[typeStr];
-      this.typeCount_--;
-    }
-    return true;
+goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
+  // TODO(chrishenry): Change type from ?string to string, or add assertion.
+  type = /** @type {string} */ (type);
+  listener = goog.events.wrapListener(listener);
+  var capture = !!opt_capt;
+  if (goog.events.Listenable.isImplementedBy(src)) {
+    return src.getListener(type, listener, capture, opt_handler);
   }
-  return false;
-};
-
 
-/**
- * Removes the given listener object.
- * @param {goog.events.ListenableKey} listener The listener to remove.
- * @return {boolean} Whether the listener is removed.
- */
-goog.events.ListenerMap.prototype.removeByKey = function(listener) {
-  var type = listener.type;
-  if (!(type in this.listeners)) {
-    return false;
+  if (!src) {
+    // TODO(chrishenry): We should tighten the API to only accept
+    // non-null objects, or add an assertion here.
+    return null;
   }
 
-  var removed = goog.array.remove(this.listeners[type], listener);
-  if (removed) {
-    listener.markAsRemoved();
-    if (this.listeners[type].length == 0) {
-      delete this.listeners[type];
-      this.typeCount_--;
-    }
+  var listenerMap = goog.events.getListenerMap_(
+      /** @type {!EventTarget} */ (src));
+  if (listenerMap) {
+    return listenerMap.getListener(type, listener, capture, opt_handler);
   }
-  return removed;
+  return null;
 };
 
 
 /**
- * Removes all listeners from this map. If opt_type is provided, only
- * listeners that match the given type are removed.
- * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
- * @return {number} Number of listeners removed.
+ * Returns whether an event target has any active listeners matching the
+ * specified signature. If either the type or capture parameters are
+ * unspecified, the function will match on the remaining criteria.
+ *
+ * @param {EventTarget|goog.events.Listenable} obj Target to get
+ *     listeners for.
+ * @param {string|!goog.events.EventId=} opt_type Event type.
+ * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
+ *     listeners.
+ * @return {boolean} Whether an event target has one or more listeners matching
+ *     the requested type and/or capture phase.
  */
-goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
-  var typeStr = opt_type && opt_type.toString();
-  var count = 0;
-  for (var type in this.listeners) {
-    if (!typeStr || type == typeStr) {
-      var listenerArray = this.listeners[type];
-      for (var i = 0; i < listenerArray.length; i++) {
-        ++count;
-        listenerArray[i].markAsRemoved();
-      }
-      delete this.listeners[type];
-      this.typeCount_--;
-    }
+goog.events.hasListener = function(obj, opt_type, opt_capture) {
+  if (goog.events.Listenable.isImplementedBy(obj)) {
+    return obj.hasListener(opt_type, opt_capture);
   }
-  return count;
+
+  var listenerMap = goog.events.getListenerMap_(
+      /** @type {!EventTarget} */ (obj));
+  return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
 };
 
 
 /**
- * Gets all listeners that match the given type and capture mode. The
- * returned array is a copy (but the listener objects are not).
- * @param {string|!goog.events.EventId} type The type of the listeners
- *     to retrieve.
- * @param {boolean} capture The capture mode of the listeners to retrieve.
- * @return {!Array.<goog.events.ListenableKey>} An array of matching
- *     listeners.
+ * Provides a nice string showing the normalized event objects public members
+ * @param {Object} e Event Object.
+ * @return {string} String of the public members of the normalized event object.
  */
-goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
-  var listenerArray = this.listeners[type.toString()];
-  var rv = [];
-  if (listenerArray) {
-    for (var i = 0; i < listenerArray.length; ++i) {
-      var listenerObj = listenerArray[i];
-      if (listenerObj.capture == capture) {
-        rv.push(listenerObj);
-      }
+goog.events.expose = function(e) {
+  var str = [];
+  for (var key in e) {
+    if (e[key] && e[key].id) {
+      str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
+    } else {
+      str.push(key + ' = ' + e[key]);
     }
   }
-  return rv;
+  return str.join('\n');
 };
 
 
 /**
- * Gets the goog.events.ListenableKey for the event or null if no such
- * listener is in use.
- *
- * @param {string|!goog.events.EventId} type The type of the listener
- *     to retrieve.
- * @param {!Function} listener The listener function to get.
- * @param {boolean} capture Whether the listener is a capturing listener.
- * @param {Object=} opt_listenerScope Object in whose scope to call the
- *     listener.
- * @return {goog.events.ListenableKey} the found listener or null if not found.
+ * Returns a string with on prepended to the specified type. This is used for IE
+ * which expects "on" to be prepended. This function caches the string in order
+ * to avoid extra allocations in steady state.
+ * @param {string} type Event type.
+ * @return {string} The type string with 'on' prepended.
+ * @private
  */
-goog.events.ListenerMap.prototype.getListener = function(
-    type, listener, capture, opt_listenerScope) {
-  var listenerArray = this.listeners[type.toString()];
-  var i = -1;
-  if (listenerArray) {
-    i = goog.events.ListenerMap.findListenerIndex_(
-        listenerArray, listener, capture, opt_listenerScope);
+goog.events.getOnString_ = function(type) {
+  if (type in goog.events.onStringMap_) {
+    return goog.events.onStringMap_[type];
   }
-  return i > -1 ? listenerArray[i] : null;
+  return goog.events.onStringMap_[type] = goog.events.onString_ + type;
 };
 
 
 /**
- * Whether there is a matching listener. If either the type or capture
- * parameters are unspecified, the function will match on the
- * remaining criteria.
+ * Fires an object's listeners of a particular type and phase
  *
- * @param {string|!goog.events.EventId=} opt_type The type of the listener.
- * @param {boolean=} opt_capture The capture mode of the listener.
- * @return {boolean} Whether there is an active listener matching
- *     the requested type and/or capture phase.
+ * @param {Object} obj Object whose listeners to call.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Which event phase.
+ * @param {Object} eventObject Event object to be passed to listener.
+ * @return {boolean} True if all listeners returned true else false.
  */
-goog.events.ListenerMap.prototype.hasListener = function(
-    opt_type, opt_capture) {
-  var hasType = goog.isDef(opt_type);
-  var typeStr = hasType ? opt_type.toString() : '';
-  var hasCapture = goog.isDef(opt_capture);
-
-  return goog.object.some(
-      this.listeners, function(listenerArray, type) {
-        for (var i = 0; i < listenerArray.length; ++i) {
-          if ((!hasType || listenerArray[i].type == typeStr) &&
-              (!hasCapture || listenerArray[i].capture == opt_capture)) {
-            return true;
-          }
-        }
+goog.events.fireListeners = function(obj, type, capture, eventObject) {
+  if (goog.events.Listenable.isImplementedBy(obj)) {
+    return obj.fireListeners(type, capture, eventObject);
+  }
 
-        return false;
-      });
+  return goog.events.fireListeners_(obj, type, capture, eventObject);
 };
 
 
 /**
- * Finds the index of a matching goog.events.Listener in the given
- * listenerArray.
- * @param {!Array.<!goog.events.Listener>} listenerArray Array of listener.
- * @param {!Function} listener The listener function.
- * @param {boolean=} opt_useCapture The capture flag for the listener.
- * @param {Object=} opt_listenerScope The listener scope.
- * @return {number} The index of the matching listener within the
- *     listenerArray.
+ * Fires an object's listeners of a particular type and phase.
+ * @param {Object} obj Object whose listeners to call.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Which event phase.
+ * @param {Object} eventObject Event object to be passed to listener.
+ * @return {boolean} True if all listeners returned true else false.
  * @private
  */
-goog.events.ListenerMap.findListenerIndex_ = function(
-    listenerArray, listener, opt_useCapture, opt_listenerScope) {
-  for (var i = 0; i < listenerArray.length; ++i) {
-    var listenerObj = listenerArray[i];
-    if (!listenerObj.removed &&
-        listenerObj.listener == listener &&
-        listenerObj.capture == !!opt_useCapture &&
-        listenerObj.handler == opt_listenerScope) {
-      return i;
+goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
+  /** @type {boolean} */
+  var retval = true;
+
+  var listenerMap = goog.events.getListenerMap_(
+      /** @type {EventTarget} */ (obj));
+  if (listenerMap) {
+    // TODO(chrishenry): Original code avoids array creation when there
+    // is no listener, so we do the same. If this optimization turns
+    // out to be not required, we can replace this with
+    // listenerMap.getListeners(type, capture) instead, which is simpler.
+    var listenerArray = listenerMap.listeners[type.toString()];
+    if (listenerArray) {
+      listenerArray = listenerArray.concat();
+      for (var i = 0; i < listenerArray.length; i++) {
+        var listener = listenerArray[i];
+        // We might not have a listener if the listener was removed.
+        if (listener && listener.capture == capture && !listener.removed) {
+          var result = goog.events.fireListener(listener, eventObject);
+          retval = retval && (result !== false);
+        }
+      }
     }
   }
-  return -1;
+  return retval;
 };
 
-// Copyright 2005 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview An event manager for both native browser event
- * targets and custom JavaScript event targets
- * ({@code goog.events.Listenable}). This provides an abstraction
- * over browsers' event systems.
- *
- * It also provides a simulation of W3C event model's capture phase in
- * Internet Explorer (IE 8 and below). Caveat: the simulation does not
- * interact well with listeners registered directly on the elements
- * (bypassing goog.events) or even with listeners registered via
- * goog.events in a separate JS binary. In these cases, we provide
- * no ordering guarantees.
- *
- * The listeners will receive a "patched" event object. Such event object
- * contains normalized values for certain event properties that differs in
- * different browsers.
- *
- * Example usage:
- * <pre>
- * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
- * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
- * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
- * goog.events.removeAll(myNode);
- * </pre>
- *
- *                                            in IE and event object patching]
+ * Fires a listener with a set of arguments
  *
- * @see ../demos/events.html
- * @see ../demos/event-propagation.html
- * @see ../demos/stopevent.html
+ * @param {goog.events.Listener} listener The listener object to call.
+ * @param {Object} eventObject The event object to pass to the listener.
+ * @return {boolean} Result of listener.
  */
+goog.events.fireListener = function(listener, eventObject) {
+  var listenerFn = listener.listener;
+  var listenerHandler = listener.handler || listener.src;
 
-// IMPLEMENTATION NOTES:
-// goog.events stores an auxiliary data structure on each EventTarget
-// source being listened on. This allows us to take advantage of GC,
-// having the data structure GC'd when the EventTarget is GC'd. This
-// GC behavior is equivalent to using W3C DOM Events directly.
-
-goog.provide('goog.events');
-goog.provide('goog.events.CaptureSimulationMode');
-goog.provide('goog.events.Key');
-goog.provide('goog.events.ListenableType');
+  if (listener.callOnce) {
+    goog.events.unlistenByKey(listener);
+  }
+  return listenerFn.call(listenerHandler, eventObject);
+};
 
-goog.require('goog.asserts');
-goog.require('goog.debug.entryPointRegistry');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.BrowserFeature');
-goog.require('goog.events.Listenable');
-goog.require('goog.events.ListenerMap');
 
-goog.forwardDeclare('goog.debug.ErrorHandler');
-goog.forwardDeclare('goog.events.EventWrapper');
+/**
+ * Gets the total number of listeners currently in the system.
+ * @return {number} Number of listeners.
+ * @deprecated This returns estimated count, now that Closure no longer
+ * stores a central listener registry. We still return an estimation
+ * to keep existing listener-related tests passing. In the near future,
+ * this function will be removed.
+ */
+goog.events.getTotalListenerCount = function() {
+  return goog.events.listenerCountEstimate_;
+};
 
 
 /**
- * @typedef {number|goog.events.ListenableKey}
+ * Dispatches an event (or event like object) and calls all listeners
+ * listening for events of this type. The type of the event is decided by the
+ * type property on the event object.
+ *
+ * If any of the listeners returns false OR calls preventDefault then this
+ * function will return false.  If one of the capture listeners calls
+ * stopPropagation, then the bubble listeners won't fire.
+ *
+ * @param {goog.events.Listenable} src The event target.
+ * @param {goog.events.EventLike} e Event object.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ *     if any of the handlers returns false) this will also return false.
+ *     If there are no handlers, or if all handlers return true, this returns
+ *     true.
  */
-goog.events.Key;
+goog.events.dispatchEvent = function(src, e) {
+  goog.asserts.assert(
+      goog.events.Listenable.isImplementedBy(src),
+      'Can not use goog.events.dispatchEvent with ' +
+      'non-goog.events.Listenable instance.');
+  return src.dispatchEvent(e);
+};
 
 
 /**
- * @typedef {EventTarget|goog.events.Listenable}
+ * Installs exception protection for the browser event entry point using the
+ * given error handler.
+ *
+ * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
+ *     protect the entry point.
  */
-goog.events.ListenableType;
+goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
+  goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
+      goog.events.handleBrowserEvent_);
+};
 
 
 /**
- * Property name on a native event target for the listener map
- * associated with the event target.
- * @const
+ * Handles an event and dispatches it to the correct listeners. This
+ * function is a proxy for the real listener the user specified.
+ *
+ * @param {goog.events.Listener} listener The listener object.
+ * @param {Event=} opt_evt Optional event object that gets passed in via the
+ *     native event handlers.
+ * @return {boolean} Result of the event handler.
+ * @this {EventTarget} The object or Element that fired the event.
  * @private
  */
-goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
+goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
+  if (listener.removed) {
+    return true;
+  }
+
+  // Synthesize event propagation if the browser does not support W3C
+  // event model.
+  if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
+    var ieEvent = opt_evt ||
+        /** @type {Event} */ (goog.getObjectByName('window.event'));
+    var evt = new goog.events.BrowserEvent(ieEvent, this);
+    /** @type {boolean} */
+    var retval = true;
+
+    if (goog.events.CAPTURE_SIMULATION_MODE ==
+            goog.events.CaptureSimulationMode.ON) {
+      // If we have not marked this event yet, we should perform capture
+      // simulation.
+      if (!goog.events.isMarkedIeEvent_(ieEvent)) {
+        goog.events.markIeEvent_(ieEvent);
+
+        var ancestors = [];
+        for (var parent = evt.currentTarget; parent;
+             parent = parent.parentNode) {
+          ancestors.push(parent);
+        }
+
+        // Fire capture listeners.
+        var type = listener.type;
+        for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;
+             i--) {
+          evt.currentTarget = ancestors[i];
+          var result = goog.events.fireListeners_(ancestors[i], type, true, evt);
+          retval = retval && result;
+        }
+
+        // Fire bubble listeners.
+        //
+        // We can technically rely on IE to perform bubble event
+        // propagation. However, it turns out that IE fires events in
+        // opposite order of attachEvent registration, which broke
+        // some code and tests that rely on the order. (While W3C DOM
+        // Level 2 Events TR leaves the event ordering unspecified,
+        // modern browsers and W3C DOM Level 3 Events Working Draft
+        // actually specify the order as the registration order.)
+        for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {
+          evt.currentTarget = ancestors[i];
+          var result = goog.events.fireListeners_(ancestors[i], type, false, evt);
+          retval = retval && result;
+        }
+      }
+    } else {
+      retval = goog.events.fireListener(listener, evt);
+    }
+    return retval;
+  }
+
+  // Otherwise, simply fire the listener.
+  return goog.events.fireListener(
+      listener, new goog.events.BrowserEvent(opt_evt, this));
+};
 
 
 /**
- * String used to prepend to IE event types.
- * @const
+ * This is used to mark the IE event object so we do not do the Closure pass
+ * twice for a bubbling event.
+ * @param {Event} e The IE browser event.
  * @private
  */
-goog.events.onString_ = 'on';
+goog.events.markIeEvent_ = function(e) {
+  // Only the keyCode and the returnValue can be changed. We use keyCode for
+  // non keyboard events.
+  // event.returnValue is a bit more tricky. It is undefined by default. A
+  // boolean false prevents the default action. In a window.onbeforeunload and
+  // the returnValue is non undefined it will be alerted. However, we will only
+  // modify the returnValue for keyboard events. We can get a problem if non
+  // closure events sets the keyCode or the returnValue
+
+  var useReturnValue = false;
+
+  if (e.keyCode == 0) {
+    // We cannot change the keyCode in case that srcElement is input[type=file].
+    // We could test that that is the case but that would allocate 3 objects.
+    // If we use try/catch we will only allocate extra objects in the case of a
+    // failure.
+    /** @preserveTry */
+    try {
+      e.keyCode = -1;
+      return;
+    } catch (ex) {
+      useReturnValue = true;
+    }
+  }
+
+  if (useReturnValue ||
+      /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
+    e.returnValue = true;
+  }
+};
 
 
 /**
- * Map of computed "on<eventname>" strings for IE event types. Caching
- * this removes an extra object allocation in goog.events.listen which
- * improves IE6 performance.
- * @const
- * @dict
+ * This is used to check if an IE event has already been handled by the Closure
+ * system so we do not do the Closure pass twice for a bubbling event.
+ * @param {Event} e  The IE browser event.
+ * @return {boolean} True if the event object has been marked.
  * @private
  */
-goog.events.onStringMap_ = {};
+goog.events.isMarkedIeEvent_ = function(e) {
+  return e.keyCode < 0 || e.returnValue != undefined;
+};
 
 
 /**
- * @enum {number} Different capture simulation mode for IE8-.
+ * Counter to create unique event ids.
+ * @private {number}
  */
-goog.events.CaptureSimulationMode = {
-  /**
-   * Does not perform capture simulation. Will asserts in IE8- when you
-   * add capture listeners.
-   */
-  OFF_AND_FAIL: 0,
+goog.events.uniqueIdCounter_ = 0;
 
-  /**
-   * Does not perform capture simulation, silently ignore capture
-   * listeners.
-   */
-  OFF_AND_SILENT: 1,
 
-  /**
-   * Performs capture simulation.
-   */
-  ON: 2
+/**
+ * Creates a unique event id.
+ *
+ * @param {string} identifier The identifier.
+ * @return {string} A unique identifier.
+ * @idGenerator
+ */
+goog.events.getUniqueId = function(identifier) {
+  return identifier + '_' + goog.events.uniqueIdCounter_++;
 };
 
 
 /**
- * @define {number} The capture simulation mode for IE8-. By default,
- *     this is ON.
+ * @param {EventTarget} src The source object.
+ * @return {goog.events.ListenerMap} A listener map for the given
+ *     source object, or null if none exists.
+ * @private
  */
-goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
+goog.events.getListenerMap_ = function(src) {
+  var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
+  // IE serializes the property as well (e.g. when serializing outer
+  // HTML). So we must check that the value is of the correct type.
+  return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
+};
 
 
 /**
- * Estimated count of total native listeners.
- * @private {number}
+ * Expando property for listener function wrapper for Object with
+ * handleEvent.
+ * @private @const {string}
  */
-goog.events.listenerCountEstimate_ = 0;
+goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +
+    ((Math.random() * 1e9) >>> 0);
 
 
 /**
- * Adds an event listener for a specific event on a native event
- * target (such as a DOM element) or an object that has implemented
- * {@link goog.events.Listenable}. A listener can only be added once
- * to an object and if it is added again the key for the listener is
- * returned. Note that if the existing listener is a one-off listener
- * (registered via listenOnce), it will no longer be a one-off
- * listener after a call to listen().
- *
- * @param {EventTarget|goog.events.Listenable} src The node to listen
- *     to events on.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type or array of event types.
- * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
- *     listener Callback method, or an object with a handleEvent function.
- *     WARNING: passing an Object is now softly deprecated.
- * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
- *     false).
- * @param {T=} opt_handler Element in whose scope to call the listener.
- * @return {goog.events.Key} Unique key for the listener.
- * @template T,EVENTOBJ
+ * @param {Object|Function} listener The listener function or an
+ *     object that contains handleEvent method.
+ * @return {!Function} Either the original function or a function that
+ *     calls obj.handleEvent. If the same listener is passed to this
+ *     function more than once, the same function is guaranteed to be
+ *     returned.
  */
-goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
-  if (goog.isArray(type)) {
-    for (var i = 0; i < type.length; i++) {
-      goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
-    }
-    return null;
+goog.events.wrapListener = function(listener) {
+  goog.asserts.assert(listener, 'Listener can not be null.');
+
+  if (goog.isFunction(listener)) {
+    return listener;
   }
 
-  listener = goog.events.wrapListener(listener);
-  if (goog.events.Listenable.isImplementedBy(src)) {
-    return src.listen(
-        /** @type {string|!goog.events.EventId} */ (type),
-        listener, opt_capt, opt_handler);
-  } else {
-    return goog.events.listen_(
-        /** @type {EventTarget} */ (src),
-        /** @type {string|!goog.events.EventId} */ (type),
-        listener, /* callOnce */ false, opt_capt, opt_handler);
+  goog.asserts.assert(
+      listener.handleEvent, 'An object listener must have handleEvent method.');
+  if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
+    listener[goog.events.LISTENER_WRAPPER_PROP_] =
+        function(e) { return listener.handleEvent(e); };
   }
+  return listener[goog.events.LISTENER_WRAPPER_PROP_];
 };
 
 
+// Register the browser event handler as an entry point, so that
+// it can be monitored for exception handling, etc.
+goog.debug.entryPointRegistry.register(
+    /**
+     * @param {function(!Function): !Function} transformer The transforming
+     *     function.
+     */
+    function(transformer) {
+      goog.events.handleBrowserEvent_ = transformer(
+          goog.events.handleBrowserEvent_);
+    });
+
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 /**
- * Adds an event listener for a specific event on a native event
- * target. A listener can only be added once to an object and if it
- * is added again the key for the listener is returned.
- *
- * Note that a one-off listener will not change an existing listener,
- * if any. On the other hand a normal listener will change existing
- * one-off listener to become a normal listener.
+ * @fileoverview Utilities for creating functions. Loosely inspired by the
+ * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8.
  *
- * @param {EventTarget} src The node to listen to events on.
- * @param {string|!goog.events.EventId} type Event type.
- * @param {!Function} listener Callback function.
- * @param {boolean} callOnce Whether the listener is a one-off
- *     listener or otherwise.
- * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
- *     false).
- * @param {Object=} opt_handler Element in whose scope to call the listener.
- * @return {goog.events.ListenableKey} Unique key for the listener.
- * @private
+ * @author nicksantos@google.com (Nick Santos)
  */
-goog.events.listen_ = function(
-    src, type, listener, callOnce, opt_capt, opt_handler) {
-  if (!type) {
-    throw Error('Invalid event type');
-  }
 
-  var capture = !!opt_capt;
-  if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
-    if (goog.events.CAPTURE_SIMULATION_MODE ==
-        goog.events.CaptureSimulationMode.OFF_AND_FAIL) {
-      goog.asserts.fail('Can not register capture listener in IE8-.');
-      return null;
-    } else if (goog.events.CAPTURE_SIMULATION_MODE ==
-        goog.events.CaptureSimulationMode.OFF_AND_SILENT) {
-      return null;
-    }
-  }
 
-  var listenerMap = goog.events.getListenerMap_(src);
-  if (!listenerMap) {
-    src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
-        new goog.events.ListenerMap(src);
-  }
+goog.provide('goog.functions');
 
-  var listenerObj = listenerMap.add(
-      type, listener, callOnce, opt_capt, opt_handler);
 
-  // If the listenerObj already has a proxy, it has been set up
-  // previously. We simply return.
-  if (listenerObj.proxy) {
-    return listenerObj;
-  }
+/**
+ * Creates a function that always returns the same value.
+ * @param {T} retValue The value to return.
+ * @return {function():T} The new function.
+ * @template T
+ */
+goog.functions.constant = function(retValue) {
+  return function() {
+    return retValue;
+  };
+};
 
-  var proxy = goog.events.getProxy();
-  listenerObj.proxy = proxy;
 
-  proxy.src = src;
-  proxy.listener = listenerObj;
+/**
+ * Always returns false.
+ * @type {function(...): boolean}
+ */
+goog.functions.FALSE = goog.functions.constant(false);
 
-  // Attach the proxy through the browser's API
-  if (src.addEventListener) {
-    src.addEventListener(type.toString(), proxy, capture);
-  } else {
-    // The else above used to be else if (src.attachEvent) and then there was
-    // another else statement that threw an exception warning the developer
-    // they made a mistake. This resulted in an extra object allocation in IE6
-    // due to a wrapper object that had to be implemented around the element
-    // and so was removed.
-    src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
-  }
 
-  goog.events.listenerCountEstimate_++;
-  return listenerObj;
-};
+/**
+ * Always returns true.
+ * @type {function(...): boolean}
+ */
+goog.functions.TRUE = goog.functions.constant(true);
 
 
 /**
- * Helper function for returning a proxy function.
- * @return {!Function} A new or reused function object.
+ * Always returns NULL.
+ * @type {function(...): null}
  */
-goog.events.getProxy = function() {
-  var proxyCallbackFunction = goog.events.handleBrowserEvent_;
-  // Use a local var f to prevent one allocation.
-  var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
-      function(eventObject) {
-        return proxyCallbackFunction.call(f.src, f.listener, eventObject);
-      } :
-      function(eventObject) {
-        var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
-        // NOTE(user): In IE, we hack in a capture phase. However, if
-        // there is inline event handler which tries to prevent default (for
-        // example <a href="..." onclick="return false">...</a>) in a
-        // descendant element, the prevent default will be overridden
-        // by this listener if this listener were to return true. Hence, we
-        // return undefined.
-        if (!v) return v;
-      };
-  return f;
-};
+goog.functions.NULL = goog.functions.constant(null);
 
 
 /**
- * Adds an event listener for a specific event on a native event
- * target (such as a DOM element) or an object that has implemented
- * {@link goog.events.Listenable}. After the event has fired the event
- * listener is removed from the target.
- *
- * If an existing listener already exists, listenOnce will do
- * nothing. In particular, if the listener was previously registered
- * via listen(), listenOnce() will not turn the listener into a
- * one-off listener. Similarly, if there is already an existing
- * one-off listener, listenOnce does not modify the listeners (it is
- * still a once listener).
- *
- * @param {EventTarget|goog.events.Listenable} src The node to listen
- *     to events on.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type or array of event types.
- * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
- *     listener Callback method.
- * @param {boolean=} opt_capt Fire in capture phase?.
- * @param {T=} opt_handler Element in whose scope to call the listener.
- * @return {goog.events.Key} Unique key for the listener.
- * @template T,EVENTOBJ
+ * A simple function that returns the first argument of whatever is passed
+ * into it.
+ * @param {T=} opt_returnValue The single value that will be returned.
+ * @param {...*} var_args Optional trailing arguments. These are ignored.
+ * @return {T} The first argument passed in, or undefined if nothing was passed.
+ * @template T
  */
-goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
-  if (goog.isArray(type)) {
-    for (var i = 0; i < type.length; i++) {
-      goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
-    }
-    return null;
-  }
-
-  listener = goog.events.wrapListener(listener);
-  if (goog.events.Listenable.isImplementedBy(src)) {
-    return src.listenOnce(
-        /** @type {string|!goog.events.EventId} */ (type),
-        listener, opt_capt, opt_handler);
-  } else {
-    return goog.events.listen_(
-        /** @type {EventTarget} */ (src),
-        /** @type {string|!goog.events.EventId} */ (type),
-        listener, /* callOnce */ true, opt_capt, opt_handler);
-  }
+goog.functions.identity = function(opt_returnValue, var_args) {
+  return opt_returnValue;
 };
 
 
 /**
- * Adds an event listener with a specific event wrapper on a DOM Node or an
- * object that has implemented {@link goog.events.Listenable}. A listener can
- * only be added once to an object.
- *
- * @param {EventTarget|goog.events.Listenable} src The target to
- *     listen to events on.
- * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
- * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener
- *     Callback method, or an object with a handleEvent function.
- * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
- *     false).
- * @param {T=} opt_handler Element in whose scope to call the listener.
- * @template T
+ * Creates a function that always throws an error with the given message.
+ * @param {string} message The error message.
+ * @return {!Function} The error-throwing function.
  */
-goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
-    opt_handler) {
-  wrapper.listen(src, listener, opt_capt, opt_handler);
+goog.functions.error = function(message) {
+  return function() {
+    throw Error(message);
+  };
 };
 
 
 /**
- * Removes an event listener which was added with listen().
- *
- * @param {EventTarget|goog.events.Listenable} src The target to stop
- *     listening to events on.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type or array of event types to unlisten to.
- * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
- *     listener function to remove.
- * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
- *     whether the listener is fired during the capture or bubble phase of the
- *     event.
- * @param {Object=} opt_handler Element in whose scope to call the listener.
- * @return {?boolean} indicating whether the listener was there to remove.
- * @template EVENTOBJ
+ * Creates a function that throws the given object.
+ * @param {*} err An object to be thrown.
+ * @return {!Function} The error-throwing function.
  */
-goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
-  if (goog.isArray(type)) {
-    for (var i = 0; i < type.length; i++) {
-      goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
-    }
-    return null;
+goog.functions.fail = function(err) {
+  return function() {
+    throw err;
   }
+};
 
-  listener = goog.events.wrapListener(listener);
-  if (goog.events.Listenable.isImplementedBy(src)) {
-    return src.unlisten(
-        /** @type {string|!goog.events.EventId} */ (type),
-        listener, opt_capt, opt_handler);
-  }
 
-  if (!src) {
-    // TODO(user): We should tighten the API to only accept
-    // non-null objects, or add an assertion here.
-    return false;
-  }
+/**
+ * Given a function, create a function that keeps opt_numArgs arguments and
+ * silently discards all additional arguments.
+ * @param {Function} f The original function.
+ * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0.
+ * @return {!Function} A version of f that only keeps the first opt_numArgs
+ *     arguments.
+ */
+goog.functions.lock = function(f, opt_numArgs) {
+  opt_numArgs = opt_numArgs || 0;
+  return function() {
+    return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs));
+  };
+};
 
-  var capture = !!opt_capt;
-  var listenerMap = goog.events.getListenerMap_(
-      /** @type {EventTarget} */ (src));
-  if (listenerMap) {
-    var listenerObj = listenerMap.getListener(
-        /** @type {string|!goog.events.EventId} */ (type),
-        listener, capture, opt_handler);
-    if (listenerObj) {
-      return goog.events.unlistenByKey(listenerObj);
-    }
-  }
 
-  return false;
+/**
+ * Creates a function that returns its nth argument.
+ * @param {number} n The position of the return argument.
+ * @return {!Function} A new function.
+ */
+goog.functions.nth = function(n) {
+  return function() {
+    return arguments[n];
+  };
 };
 
 
 /**
- * Removes an event listener which was added with listen() by the key
- * returned by listen().
- *
- * @param {goog.events.Key} key The key returned by listen() for this
- *     event listener.
- * @return {boolean} indicating whether the listener was there to remove.
+ * Given a function, create a new function that swallows its return value
+ * and replaces it with a new one.
+ * @param {Function} f A function.
+ * @param {T} retValue A new return value.
+ * @return {function(...[?]):T} A new function.
+ * @template T
  */
-goog.events.unlistenByKey = function(key) {
-  // TODO(user): Remove this check when tests that rely on this
-  // are fixed.
-  if (goog.isNumber(key)) {
-    return false;
-  }
+goog.functions.withReturnValue = function(f, retValue) {
+  return goog.functions.sequence(f, goog.functions.constant(retValue));
+};
 
-  var listener = /** @type {goog.events.ListenableKey} */ (key);
-  if (!listener || listener.removed) {
-    return false;
-  }
 
-  var src = listener.src;
-  if (goog.events.Listenable.isImplementedBy(src)) {
-    return src.unlistenByKey(listener);
-  }
+/**
+ * Creates a function that returns whether its arguement equals the given value.
+ *
+ * Example:
+ * var key = goog.object.findKey(obj, goog.functions.equalTo('needle'));
+ *
+ * @param {*} value The value to compare to.
+ * @param {boolean=} opt_useLooseComparison Whether to use a loose (==)
+ *     comparison rather than a strict (===) one. Defaults to false.
+ * @return {function(*):boolean} The new function.
+ */
+goog.functions.equalTo = function(value, opt_useLooseComparison) {
+  return function(other) {
+    return opt_useLooseComparison ? (value == other) : (value === other);
+  };
+};
 
-  var type = listener.type;
-  var proxy = listener.proxy;
-  if (src.removeEventListener) {
-    src.removeEventListener(type, proxy, listener.capture);
-  } else if (src.detachEvent) {
-    src.detachEvent(goog.events.getOnString_(type), proxy);
-  }
-  goog.events.listenerCountEstimate_--;
 
-  var listenerMap = goog.events.getListenerMap_(
-      /** @type {EventTarget} */ (src));
-  // TODO(user): Try to remove this conditional and execute the
-  // first branch always. This should be safe.
-  if (listenerMap) {
-    listenerMap.removeByKey(listener);
-    if (listenerMap.getTypeCount() == 0) {
-      // Null the src, just because this is simple to do (and useful
-      // for IE <= 7).
-      listenerMap.src = null;
-      // We don't use delete here because IE does not allow delete
-      // on a window object.
-      src[goog.events.LISTENER_MAP_PROP_] = null;
+/**
+ * Creates the composition of the functions passed in.
+ * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).
+ * @param {function(...[?]):T} fn The final function.
+ * @param {...Function} var_args A list of functions.
+ * @return {function(...[?]):T} The composition of all inputs.
+ * @template T
+ */
+goog.functions.compose = function(fn, var_args) {
+  var functions = arguments;
+  var length = functions.length;
+  return function() {
+    var result;
+    if (length) {
+      result = functions[length - 1].apply(this, arguments);
     }
-  } else {
-    listener.markAsRemoved();
-  }
 
-  return true;
+    for (var i = length - 2; i >= 0; i--) {
+      result = functions[i].call(this, result);
+    }
+    return result;
+  };
 };
 
 
 /**
- * Removes an event listener which was added with listenWithWrapper().
- *
- * @param {EventTarget|goog.events.Listenable} src The target to stop
- *     listening to events on.
- * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
- * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
- *     listener function to remove.
- * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
- *     whether the listener is fired during the capture or bubble phase of the
- *     event.
- * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * Creates a function that calls the functions passed in in sequence, and
+ * returns the value of the last function. For example,
+ * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).
+ * @param {...Function} var_args A list of functions.
+ * @return {!Function} A function that calls all inputs in sequence.
  */
-goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
-    opt_handler) {
-  wrapper.unlisten(src, listener, opt_capt, opt_handler);
+goog.functions.sequence = function(var_args) {
+  var functions = arguments;
+  var length = functions.length;
+  return function() {
+    var result;
+    for (var i = 0; i < length; i++) {
+      result = functions[i].apply(this, arguments);
+    }
+    return result;
+  };
 };
 
 
 /**
- * Removes all listeners from an object. You can also optionally
- * remove listeners of a particular type.
- *
- * @param {Object|undefined} obj Object to remove listeners from. Must be an
- *     EventTarget or a goog.events.Listenable.
- * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
- *     Default is all types.
- * @return {number} Number of listeners removed.
+ * Creates a function that returns true if each of its components evaluates
+ * to true. The components are evaluated in order, and the evaluation will be
+ * short-circuited as soon as a function returns false.
+ * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).
+ * @param {...Function} var_args A list of functions.
+ * @return {function(...[?]):boolean} A function that ANDs its component
+ *      functions.
  */
-goog.events.removeAll = function(obj, opt_type) {
-  // TODO(user): Change the type of obj to
-  // (!EventTarget|!goog.events.Listenable).
-
-  if (!obj) {
-    return 0;
-  }
-
-  if (goog.events.Listenable.isImplementedBy(obj)) {
-    return obj.removeAllListeners(opt_type);
-  }
-
-  var listenerMap = goog.events.getListenerMap_(
-      /** @type {EventTarget} */ (obj));
-  if (!listenerMap) {
-    return 0;
-  }
-
-  var count = 0;
-  var typeStr = opt_type && opt_type.toString();
-  for (var type in listenerMap.listeners) {
-    if (!typeStr || type == typeStr) {
-      // Clone so that we don't need to worry about unlistenByKey
-      // changing the content of the ListenerMap.
-      var listeners = listenerMap.listeners[type].concat();
-      for (var i = 0; i < listeners.length; ++i) {
-        if (goog.events.unlistenByKey(listeners[i])) {
-          ++count;
-        }
+goog.functions.and = function(var_args) {
+  var functions = arguments;
+  var length = functions.length;
+  return function() {
+    for (var i = 0; i < length; i++) {
+      if (!functions[i].apply(this, arguments)) {
+        return false;
       }
     }
-  }
-  return count;
+    return true;
+  };
 };
 
 
 /**
- * Removes all native listeners registered via goog.events. Native
- * listeners are listeners on native browser objects (such as DOM
- * elements). In particular, goog.events.Listenable and
- * goog.events.EventTarget listeners will NOT be removed.
- * @return {number} Number of listeners removed.
- * @deprecated This doesn't do anything, now that Closure no longer
- * stores a central listener registry.
+ * Creates a function that returns true if any of its components evaluates
+ * to true. The components are evaluated in order, and the evaluation will be
+ * short-circuited as soon as a function returns true.
+ * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).
+ * @param {...Function} var_args A list of functions.
+ * @return {function(...[?]):boolean} A function that ORs its component
+ *    functions.
  */
-goog.events.removeAllNativeListeners = function() {
-  goog.events.listenerCountEstimate_ = 0;
-  return 0;
+goog.functions.or = function(var_args) {
+  var functions = arguments;
+  var length = functions.length;
+  return function() {
+    for (var i = 0; i < length; i++) {
+      if (functions[i].apply(this, arguments)) {
+        return true;
+      }
+    }
+    return false;
+  };
 };
 
 
 /**
- * Gets the listeners for a given object, type and capture phase.
- *
- * @param {Object} obj Object to get listeners for.
- * @param {string|!goog.events.EventId} type Event type.
- * @param {boolean} capture Capture phase?.
- * @return {Array.<goog.events.Listener>} Array of listener objects.
+ * Creates a function that returns the Boolean opposite of a provided function.
+ * For example, (goog.functions.not(f))(x) is equivalent to !f(x).
+ * @param {!Function} f The original function.
+ * @return {function(...[?]):boolean} A function that delegates to f and returns
+ * opposite.
  */
-goog.events.getListeners = function(obj, type, capture) {
-  if (goog.events.Listenable.isImplementedBy(obj)) {
-    return obj.getListeners(type, capture);
-  } else {
-    if (!obj) {
-      // TODO(user): We should tighten the API to accept
-      // !EventTarget|goog.events.Listenable, and add an assertion here.
-      return [];
-    }
-
-    var listenerMap = goog.events.getListenerMap_(
-        /** @type {EventTarget} */ (obj));
-    return listenerMap ? listenerMap.getListeners(type, capture) : [];
-  }
+goog.functions.not = function(f) {
+  return function() {
+    return !f.apply(this, arguments);
+  };
 };
 
 
 /**
- * Gets the goog.events.Listener for the event or null if no such listener is
- * in use.
+ * Generic factory function to construct an object given the constructor
+ * and the arguments. Intended to be bound to create object factories.
  *
- * @param {EventTarget|goog.events.Listenable} src The target from
- *     which to get listeners.
- * @param {?string|!goog.events.EventId.<EVENTOBJ>} type The type of the event.
- * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The
- *     listener function to get.
- * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
- *                            whether the listener is fired during the
- *                            capture or bubble phase of the event.
- * @param {Object=} opt_handler Element in whose scope to call the listener.
- * @return {goog.events.ListenableKey} the found listener or null if not found.
- * @template EVENTOBJ
+ * Example:
+ *
+ * var factory = goog.partial(goog.functions.create, Class);
+ *
+ * @param {function(new:T, ...)} constructor The constructor for the Object.
+ * @param {...*} var_args The arguments to be passed to the constructor.
+ * @return {!T} A new instance of the class given in {@code constructor}.
+ * @template T
  */
-goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
-  // TODO(user): Change type from ?string to string, or add assertion.
-  type = /** @type {string} */ (type);
-  listener = goog.events.wrapListener(listener);
-  var capture = !!opt_capt;
-  if (goog.events.Listenable.isImplementedBy(src)) {
-    return src.getListener(type, listener, capture, opt_handler);
-  }
+goog.functions.create = function(constructor, var_args) {
+  /**
+ * @constructor
+ * @final
+ */
+  var temp = function() {};
+  temp.prototype = constructor.prototype;
 
-  if (!src) {
-    // TODO(user): We should tighten the API to only accept
-    // non-null objects, or add an assertion here.
-    return null;
-  }
+  // obj will have constructor's prototype in its chain and
+  // 'obj instanceof constructor' will be true.
+  var obj = new temp();
 
-  var listenerMap = goog.events.getListenerMap_(
-      /** @type {EventTarget} */ (src));
-  if (listenerMap) {
-    return listenerMap.getListener(type, listener, capture, opt_handler);
-  }
-  return null;
+  // obj is initialized by constructor.
+  // arguments is only array-like so lacks shift(), but can be used with
+  // the Array prototype function.
+  constructor.apply(obj, Array.prototype.slice.call(arguments, 1));
+  return obj;
 };
 
 
 /**
- * Returns whether an event target has any active listeners matching the
- * specified signature. If either the type or capture parameters are
- * unspecified, the function will match on the remaining criteria.
- *
- * @param {EventTarget|goog.events.Listenable} obj Target to get
- *     listeners for.
- * @param {string|!goog.events.EventId=} opt_type Event type.
- * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
- *     listeners.
- * @return {boolean} Whether an event target has one or more listeners matching
- *     the requested type and/or capture phase.
+ * @define {boolean} Whether the return value cache should be used.
+ *    This should only be used to disable caches when testing.
  */
-goog.events.hasListener = function(obj, opt_type, opt_capture) {
-  if (goog.events.Listenable.isImplementedBy(obj)) {
-    return obj.hasListener(opt_type, opt_capture);
-  }
-
-  var listenerMap = goog.events.getListenerMap_(
-      /** @type {EventTarget} */ (obj));
-  return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
-};
+goog.define('goog.functions.CACHE_RETURN_VALUE', true);
 
 
 /**
- * Provides a nice string showing the normalized event objects public members
- * @param {Object} e Event Object.
- * @return {string} String of the public members of the normalized event object.
+ * Gives a wrapper function that caches the return value of a parameterless
+ * function when first called.
+ *
+ * When called for the first time, the given function is called and its
+ * return value is cached (thus this is only appropriate for idempotent
+ * functions).  Subsequent calls will return the cached return value. This
+ * allows the evaluation of expensive functions to be delayed until first used.
+ *
+ * To cache the return values of functions with parameters, see goog.memoize.
+ *
+ * @param {!function():T} fn A function to lazily evaluate.
+ * @return {!function():T} A wrapped version the function.
+ * @template T
  */
-goog.events.expose = function(e) {
-  var str = [];
-  for (var key in e) {
-    if (e[key] && e[key].id) {
-      str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
-    } else {
-      str.push(key + ' = ' + e[key]);
+goog.functions.cacheReturnValue = function(fn) {
+  var called = false;
+  var value;
+
+  return function() {
+    if (!goog.functions.CACHE_RETURN_VALUE) {
+      return fn();
+    }
+
+    if (!called) {
+      value = fn();
+      called = true;
     }
+
+    return value;
   }
-  return str.join('\n');
 };
 
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Returns a string with on prepended to the specified type. This is used for IE
- * which expects "on" to be prepended. This function caches the string in order
- * to avoid extra allocations in steady state.
- * @param {string} type Event type.
- * @return {string} The type string with 'on' prepended.
- * @private
+ * @fileoverview A disposable implementation of a custom
+ * listenable/event target. See also: documentation for
+ * {@code goog.events.Listenable}.
+ *
+ * @author arv@google.com (Erik Arvidsson) [Original implementation]
+ * @see ../demos/eventtarget.html
+ * @see goog.events.Listenable
  */
-goog.events.getOnString_ = function(type) {
-  if (type in goog.events.onStringMap_) {
-    return goog.events.onStringMap_[type];
-  }
-  return goog.events.onStringMap_[type] = goog.events.onString_ + type;
-};
+
+goog.provide('goog.events.EventTarget');
+
+goog.require('goog.Disposable');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.Listenable');
+goog.require('goog.events.ListenerMap');
+goog.require('goog.object');
+
 
 
 /**
- * Fires an object's listeners of a particular type and phase
+ * An implementation of {@code goog.events.Listenable} with full W3C
+ * EventTarget-like support (capture/bubble mechanism, stopping event
+ * propagation, preventing default actions).
  *
- * @param {Object} obj Object whose listeners to call.
- * @param {string|!goog.events.EventId} type Event type.
- * @param {boolean} capture Which event phase.
- * @param {Object} eventObject Event object to be passed to listener.
- * @return {boolean} True if all listeners returned true else false.
+ * You may subclass this class to turn your class into a Listenable.
+ *
+ * Unless propagation is stopped, an event dispatched by an
+ * EventTarget will bubble to the parent returned by
+ * {@code getParentEventTarget}. To set the parent, call
+ * {@code setParentEventTarget}. Subclasses that don't support
+ * changing the parent can override the setter to throw an error.
+ *
+ * Example usage:
+ * <pre>
+ *   var source = new goog.events.EventTarget();
+ *   function handleEvent(e) {
+ *     alert('Type: ' + e.type + '; Target: ' + e.target);
+ *   }
+ *   source.listen('foo', handleEvent);
+ *   // Or: goog.events.listen(source, 'foo', handleEvent);
+ *   ...
+ *   source.dispatchEvent('foo');  // will call handleEvent
+ *   ...
+ *   source.unlisten('foo', handleEvent);
+ *   // Or: goog.events.unlisten(source, 'foo', handleEvent);
+ * </pre>
+ *
+ * @constructor
+ * @extends {goog.Disposable}
+ * @implements {goog.events.Listenable}
  */
-goog.events.fireListeners = function(obj, type, capture, eventObject) {
-  if (goog.events.Listenable.isImplementedBy(obj)) {
-    return obj.fireListeners(type, capture, eventObject);
-  }
+goog.events.EventTarget = function() {
+  goog.Disposable.call(this);
 
-  return goog.events.fireListeners_(obj, type, capture, eventObject);
+  /**
+   * Maps of event type to an array of listeners.
+   * @private {!goog.events.ListenerMap}
+   */
+  this.eventTargetListeners_ = new goog.events.ListenerMap(this);
+
+  /**
+   * The object to use for event.target. Useful when mixing in an
+   * EventTarget to another object.
+   * @private {!Object}
+   */
+  this.actualEventTarget_ = this;
+
+  /**
+   * Parent event target, used during event bubbling.
+   *
+   * TODO(chrishenry): Change this to goog.events.Listenable. This
+   * currently breaks people who expect getParentEventTarget to return
+   * goog.events.EventTarget.
+   *
+   * @private {goog.events.EventTarget}
+   */
+  this.parentEventTarget_ = null;
 };
+goog.inherits(goog.events.EventTarget, goog.Disposable);
+goog.events.Listenable.addImplementation(goog.events.EventTarget);
 
 
 /**
- * Fires an object's listeners of a particular type and phase.
- * @param {Object} obj Object whose listeners to call.
- * @param {string|!goog.events.EventId} type Event type.
- * @param {boolean} capture Which event phase.
- * @param {Object} eventObject Event object to be passed to listener.
- * @return {boolean} True if all listeners returned true else false.
+ * An artificial cap on the number of ancestors you can have. This is mainly
+ * for loop detection.
+ * @const {number}
  * @private
  */
-goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
-  var retval = 1;
-
-  var listenerMap = goog.events.getListenerMap_(
-      /** @type {EventTarget} */ (obj));
-  if (listenerMap) {
-    // TODO(user): Original code avoids array creation when there
-    // is no listener, so we do the same. If this optimization turns
-    // out to be not required, we can replace this with
-    // listenerMap.getListeners(type, capture) instead, which is simpler.
-    var listenerArray = listenerMap.listeners[type.toString()];
-    if (listenerArray) {
-      listenerArray = listenerArray.concat();
-      for (var i = 0; i < listenerArray.length; i++) {
-        var listener = listenerArray[i];
-        // We might not have a listener if the listener was removed.
-        if (listener && listener.capture == capture && !listener.removed) {
-          retval &=
-              goog.events.fireListener(listener, eventObject) !== false;
-        }
-      }
-    }
-  }
-  return Boolean(retval);
-};
+goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
 
 
 /**
- * Fires a listener with a set of arguments
+ * Returns the parent of this event target to use for bubbling.
  *
- * @param {goog.events.Listener} listener The listener object to call.
- * @param {Object} eventObject The event object to pass to the listener.
- * @return {boolean} Result of listener.
+ * @return {goog.events.EventTarget} The parent EventTarget or null if
+ *     there is no parent.
+ * @override
  */
-goog.events.fireListener = function(listener, eventObject) {
-  var listenerFn = listener.listener;
-  var listenerHandler = listener.handler || listener.src;
-
-  if (listener.callOnce) {
-    goog.events.unlistenByKey(listener);
-  }
-  return listenerFn.call(listenerHandler, eventObject);
+goog.events.EventTarget.prototype.getParentEventTarget = function() {
+  return this.parentEventTarget_;
 };
 
 
 /**
- * Gets the total number of listeners currently in the system.
- * @return {number} Number of listeners.
- * @deprecated This returns estimated count, now that Closure no longer
- * stores a central listener registry. We still return an estimation
- * to keep existing listener-related tests passing. In the near future,
- * this function will be removed.
+ * Sets the parent of this event target to use for capture/bubble
+ * mechanism.
+ * @param {goog.events.EventTarget} parent Parent listenable (null if none).
  */
-goog.events.getTotalListenerCount = function() {
-  return goog.events.listenerCountEstimate_;
+goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
+  this.parentEventTarget_ = parent;
 };
 
 
 /**
- * Dispatches an event (or event like object) and calls all listeners
- * listening for events of this type. The type of the event is decided by the
- * type property on the event object.
- *
- * If any of the listeners returns false OR calls preventDefault then this
- * function will return false.  If one of the capture listeners calls
- * stopPropagation, then the bubble listeners won't fire.
+ * Adds an event listener to the event target. The same handler can only be
+ * added once per the type. Even if you add the same handler multiple times
+ * using the same type then it will only be called once when the event is
+ * dispatched.
  *
- * @param {goog.events.Listenable} src The event target.
- * @param {goog.events.EventLike} e Event object.
- * @return {boolean} If anyone called preventDefault on the event object (or
- *     if any of the handlers returns false) this will also return false.
- *     If there are no handlers, or if all handlers return true, this returns
- *     true.
+ * @param {string} type The type of the event to listen for.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
+ *     to handle the event. The handler can also be an object that implements
+ *     the handleEvent method which takes the event object as argument.
+ * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
+ *     whether the listener is fired during the capture or bubble phase
+ *     of the event.
+ * @param {Object=} opt_handlerScope Object in whose scope to call
+ *     the listener.
+ * @deprecated Use {@code #listen} instead, when possible. Otherwise, use
+ *     {@code goog.events.listen} if you are passing Object
+ *     (instead of Function) as handler.
  */
-goog.events.dispatchEvent = function(src, e) {
-  goog.asserts.assert(
-      goog.events.Listenable.isImplementedBy(src),
-      'Can not use goog.events.dispatchEvent with ' +
-      'non-goog.events.Listenable instance.');
-  return src.dispatchEvent(e);
+goog.events.EventTarget.prototype.addEventListener = function(
+    type, handler, opt_capture, opt_handlerScope) {
+  goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
 };
 
 
 /**
- * Installs exception protection for the browser event entry point using the
- * given error handler.
+ * Removes an event listener from the event target. The handler must be the
+ * same object as the one added. If the handler has not been added then
+ * nothing is done.
  *
- * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
- *     protect the entry point.
+ * @param {string} type The type of the event to listen for.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
+ *     to handle the event. The handler can also be an object that implements
+ *     the handleEvent method which takes the event object as argument.
+ * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
+ *     whether the listener is fired during the capture or bubble phase
+ *     of the event.
+ * @param {Object=} opt_handlerScope Object in whose scope to call
+ *     the listener.
+ * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use
+ *     {@code goog.events.unlisten} if you are passing Object
+ *     (instead of Function) as handler.
  */
-goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
-  goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
-      goog.events.handleBrowserEvent_);
+goog.events.EventTarget.prototype.removeEventListener = function(
+    type, handler, opt_capture, opt_handlerScope) {
+  goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.dispatchEvent = function(e) {
+  this.assertInitialized_();
+
+  var ancestorsTree, ancestor = this.getParentEventTarget();
+  if (ancestor) {
+    ancestorsTree = [];
+    var ancestorCount = 1;
+    for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
+      ancestorsTree.push(ancestor);
+      goog.asserts.assert(
+          (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
+          'infinite loop');
+    }
+  }
+
+  return goog.events.EventTarget.dispatchEventInternal_(
+      this.actualEventTarget_, e, ancestorsTree);
 };
 
 
 /**
- * Handles an event and dispatches it to the correct listeners. This
- * function is a proxy for the real listener the user specified.
- *
- * @param {goog.events.Listener} listener The listener object.
- * @param {Event=} opt_evt Optional event object that gets passed in via the
- *     native event handlers.
- * @return {boolean} Result of the event handler.
- * @this {EventTarget} The object or Element that fired the event.
- * @private
+ * Removes listeners from this object.  Classes that extend EventTarget may
+ * need to override this method in order to remove references to DOM Elements
+ * and additional listeners.
+ * @override
  */
-goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
-  if (listener.removed) {
-    return true;
-  }
+goog.events.EventTarget.prototype.disposeInternal = function() {
+  goog.events.EventTarget.superClass_.disposeInternal.call(this);
 
-  // Synthesize event propagation if the browser does not support W3C
-  // event model.
-  if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
-    var ieEvent = opt_evt ||
-        /** @type {Event} */ (goog.getObjectByName('window.event'));
-    var evt = new goog.events.BrowserEvent(ieEvent, this);
-    var retval = true;
+  this.removeAllListeners();
+  this.parentEventTarget_ = null;
+};
 
-    if (goog.events.CAPTURE_SIMULATION_MODE ==
-            goog.events.CaptureSimulationMode.ON) {
-      // If we have not marked this event yet, we should perform capture
-      // simulation.
-      if (!goog.events.isMarkedIeEvent_(ieEvent)) {
-        goog.events.markIeEvent_(ieEvent);
 
-        var ancestors = [];
-        for (var parent = evt.currentTarget; parent;
-             parent = parent.parentNode) {
-          ancestors.push(parent);
-        }
+/** @override */
+goog.events.EventTarget.prototype.listen = function(
+    type, listener, opt_useCapture, opt_listenerScope) {
+  this.assertInitialized_();
+  return this.eventTargetListeners_.add(
+      String(type), listener, false /* callOnce */, opt_useCapture,
+      opt_listenerScope);
+};
 
-        // Fire capture listeners.
-        var type = listener.type;
-        for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;
-             i--) {
-          evt.currentTarget = ancestors[i];
-          retval &= goog.events.fireListeners_(ancestors[i], type, true, evt);
-        }
 
-        // Fire bubble listeners.
-        //
-        // We can technically rely on IE to perform bubble event
-        // propagation. However, it turns out that IE fires events in
-        // opposite order of attachEvent registration, which broke
-        // some code and tests that rely on the order. (While W3C DOM
-        // Level 2 Events TR leaves the event ordering unspecified,
-        // modern browsers and W3C DOM Level 3 Events Working Draft
-        // actually specify the order as the registration order.)
-        for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {
-          evt.currentTarget = ancestors[i];
-          retval &= goog.events.fireListeners_(ancestors[i], type, false, evt);
-        }
-      }
-    } else {
-      retval = goog.events.fireListener(listener, evt);
-    }
-    return retval;
-  }
+/** @override */
+goog.events.EventTarget.prototype.listenOnce = function(
+    type, listener, opt_useCapture, opt_listenerScope) {
+  return this.eventTargetListeners_.add(
+      String(type), listener, true /* callOnce */, opt_useCapture,
+      opt_listenerScope);
+};
 
-  // Otherwise, simply fire the listener.
-  return goog.events.fireListener(
-      listener, new goog.events.BrowserEvent(opt_evt, this));
+
+/** @override */
+goog.events.EventTarget.prototype.unlisten = function(
+    type, listener, opt_useCapture, opt_listenerScope) {
+  return this.eventTargetListeners_.remove(
+      String(type), listener, opt_useCapture, opt_listenerScope);
 };
 
 
-/**
- * This is used to mark the IE event object so we do not do the Closure pass
- * twice for a bubbling event.
- * @param {Event} e The IE browser event.
- * @private
- */
-goog.events.markIeEvent_ = function(e) {
-  // Only the keyCode and the returnValue can be changed. We use keyCode for
-  // non keyboard events.
-  // event.returnValue is a bit more tricky. It is undefined by default. A
-  // boolean false prevents the default action. In a window.onbeforeunload and
-  // the returnValue is non undefined it will be alerted. However, we will only
-  // modify the returnValue for keyboard events. We can get a problem if non
-  // closure events sets the keyCode or the returnValue
+/** @override */
+goog.events.EventTarget.prototype.unlistenByKey = function(key) {
+  return this.eventTargetListeners_.removeByKey(key);
+};
 
-  var useReturnValue = false;
 
-  if (e.keyCode == 0) {
-    // We cannot change the keyCode in case that srcElement is input[type=file].
-    // We could test that that is the case but that would allocate 3 objects.
-    // If we use try/catch we will only allocate extra objects in the case of a
-    // failure.
-    /** @preserveTry */
-    try {
-      e.keyCode = -1;
-      return;
-    } catch (ex) {
-      useReturnValue = true;
-    }
+/** @override */
+goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
+  // TODO(chrishenry): Previously, removeAllListeners can be called on
+  // uninitialized EventTarget, so we preserve that behavior. We
+  // should remove this when usages that rely on that fact are purged.
+  if (!this.eventTargetListeners_) {
+    return 0;
   }
+  return this.eventTargetListeners_.removeAll(opt_type);
+};
 
-  if (useReturnValue ||
-      /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
-    e.returnValue = true;
+
+/** @override */
+goog.events.EventTarget.prototype.fireListeners = function(
+    type, capture, eventObject) {
+  // TODO(chrishenry): Original code avoids array creation when there
+  // is no listener, so we do the same. If this optimization turns
+  // out to be not required, we can replace this with
+  // getListeners(type, capture) instead, which is simpler.
+  var listenerArray = this.eventTargetListeners_.listeners[String(type)];
+  if (!listenerArray) {
+    return true;
+  }
+  listenerArray = listenerArray.concat();
+
+  var rv = true;
+  for (var i = 0; i < listenerArray.length; ++i) {
+    var listener = listenerArray[i];
+    // We might not have a listener if the listener was removed.
+    if (listener && !listener.removed && listener.capture == capture) {
+      var listenerFn = listener.listener;
+      var listenerHandler = listener.handler || listener.src;
+
+      if (listener.callOnce) {
+        this.unlistenByKey(listener);
+      }
+      rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
+    }
   }
+
+  return rv && eventObject.returnValue_ != false;
 };
 
 
-/**
- * This is used to check if an IE event has already been handled by the Closure
- * system so we do not do the Closure pass twice for a bubbling event.
- * @param {Event} e  The IE browser event.
- * @return {boolean} True if the event object has been marked.
- * @private
- */
-goog.events.isMarkedIeEvent_ = function(e) {
-  return e.keyCode < 0 || e.returnValue != undefined;
+/** @override */
+goog.events.EventTarget.prototype.getListeners = function(type, capture) {
+  return this.eventTargetListeners_.getListeners(String(type), capture);
 };
 
 
-/**
- * Counter to create unique event ids.
- * @private {number}
- */
-goog.events.uniqueIdCounter_ = 0;
+/** @override */
+goog.events.EventTarget.prototype.getListener = function(
+    type, listener, capture, opt_listenerScope) {
+  return this.eventTargetListeners_.getListener(
+      String(type), listener, capture, opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.hasListener = function(
+    opt_type, opt_capture) {
+  var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
+  return this.eventTargetListeners_.hasListener(id, opt_capture);
+};
 
 
 /**
- * Creates a unique event id.
- *
- * @param {string} identifier The identifier.
- * @return {string} A unique identifier.
- * @idGenerator
+ * Sets the target to be used for {@code event.target} when firing
+ * event. Mainly used for testing. For example, see
+ * {@code goog.testing.events.mixinListenable}.
+ * @param {!Object} target The target.
  */
-goog.events.getUniqueId = function(identifier) {
-  return identifier + '_' + goog.events.uniqueIdCounter_++;
+goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
+  this.actualEventTarget_ = target;
 };
 
 
 /**
- * @param {EventTarget} src The source object.
- * @return {goog.events.ListenerMap} A listener map for the given
- *     source object, or null if none exists.
+ * Asserts that the event target instance is initialized properly.
  * @private
  */
-goog.events.getListenerMap_ = function(src) {
-  var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
-  // IE serializes the property as well (e.g. when serializing outer
-  // HTML). So we must check that the value is of the correct type.
-  return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
+goog.events.EventTarget.prototype.assertInitialized_ = function() {
+  goog.asserts.assert(
+      this.eventTargetListeners_,
+      'Event target is not initialized. Did you call the superclass ' +
+      '(goog.events.EventTarget) constructor?');
 };
 
 
 /**
- * Expando property for listener function wrapper for Object with
- * handleEvent.
- * @const
+ * Dispatches the given event on the ancestorsTree.
+ *
+ * @param {!Object} target The target to dispatch on.
+ * @param {goog.events.Event|Object|string} e The event object.
+ * @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors
+ *     tree of the target, in reverse order from the closest ancestor
+ *     to the root event target. May be null if the target has no ancestor.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ *     if any of the listeners returns false) this will also return false.
  * @private
  */
-goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +
-    ((Math.random() * 1e9) >>> 0);
+goog.events.EventTarget.dispatchEventInternal_ = function(
+    target, e, opt_ancestorsTree) {
+  var type = e.type || /** @type {string} */ (e);
 
+  // If accepting a string or object, create a custom event object so that
+  // preventDefault and stopPropagation work with the event.
+  if (goog.isString(e)) {
+    e = new goog.events.Event(e, target);
+  } else if (!(e instanceof goog.events.Event)) {
+    var oldEvent = e;
+    e = new goog.events.Event(type, target);
+    goog.object.extend(e, oldEvent);
+  } else {
+    e.target = e.target || target;
+  }
 
-/**
- * @param {Object|Function} listener The listener function or an
- *     object that contains handleEvent method.
- * @return {!Function} Either the original function or a function that
- *     calls obj.handleEvent. If the same listener is passed to this
- *     function more than once, the same function is guaranteed to be
- *     returned.
- */
-goog.events.wrapListener = function(listener) {
-  goog.asserts.assert(listener, 'Listener can not be null.');
+  var rv = true, currentTarget;
 
-  if (goog.isFunction(listener)) {
-    return listener;
+  // Executes all capture listeners on the ancestors, if any.
+  if (opt_ancestorsTree) {
+    for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
+         i--) {
+      currentTarget = e.currentTarget = opt_ancestorsTree[i];
+      rv = currentTarget.fireListeners(type, true, e) && rv;
+    }
   }
 
-  goog.asserts.assert(
-      listener.handleEvent, 'An object listener must have handleEvent method.');
-  if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
-    listener[goog.events.LISTENER_WRAPPER_PROP_] =
-        function(e) { return listener.handleEvent(e); };
+  // Executes capture and bubble listeners on the target.
+  if (!e.propagationStopped_) {
+    currentTarget = e.currentTarget = target;
+    rv = currentTarget.fireListeners(type, true, e) && rv;
+    if (!e.propagationStopped_) {
+      rv = currentTarget.fireListeners(type, false, e) && rv;
+    }
   }
-  return listener[goog.events.LISTENER_WRAPPER_PROP_];
+
+  // Executes all bubble listeners on the ancestors, if any.
+  if (opt_ancestorsTree) {
+    for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
+      currentTarget = e.currentTarget = opt_ancestorsTree[i];
+      rv = currentTarget.fireListeners(type, false, e) && rv;
+    }
+  }
+
+  return rv;
 };
 
+goog.provide('ol.Observable');
+
+goog.require('goog.events');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
 
-// Register the browser event handler as an entry point, so that
-// it can be monitored for exception handling, etc.
-goog.debug.entryPointRegistry.register(
-    /**
-     * @param {function(!Function): !Function} transformer The transforming
-     *     function.
-     */
-    function(transformer) {
-      goog.events.handleBrowserEvent_ = transformer(
-          goog.events.handleBrowserEvent_);
-    });
 
-// Copyright 2008 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Utilities for creating functions. Loosely inspired by the
- * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8.
+ * @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}.
  *
- * @author nicksantos@google.com (Nick Santos)
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @suppress {checkStructDictInheritance}
+ * @struct
+ * @api stable
  */
+ol.Observable = function() {
 
+  goog.base(this);
 
-goog.provide('goog.functions');
-
+  /**
+   * @private
+   * @type {number}
+   */
+  this.revision_ = 0;
 
-/**
- * Creates a function that always returns the same value.
- * @param {T} retValue The value to return.
- * @return {function():T} The new function.
- * @template T
- */
-goog.functions.constant = function(retValue) {
-  return function() {
-    return retValue;
-  };
 };
+goog.inherits(ol.Observable, goog.events.EventTarget);
 
 
 /**
- * Always returns false.
- * @type {function(...): boolean}
+ * Removes an event listener using the key returned by `on()` or `once()`.
+ * @param {goog.events.Key} key The key returned by `on()` or `once()`.
+ * @api stable
  */
-goog.functions.FALSE = goog.functions.constant(false);
+ol.Observable.unByKey = function(key) {
+  goog.events.unlistenByKey(key);
+};
 
 
 /**
- * Always returns true.
- * @type {function(...): boolean}
+ * Increases the revision counter and disptches a 'change' event.
+ * @fires change
+ * @api
  */
-goog.functions.TRUE = goog.functions.constant(true);
+ol.Observable.prototype.changed = function() {
+  ++this.revision_;
+  this.dispatchEvent(goog.events.EventType.CHANGE);
+};
 
 
 /**
- * Always returns NULL.
- * @type {function(...): null}
+ * @return {number} Revision.
+ * @api
  */
-goog.functions.NULL = goog.functions.constant(null);
+ol.Observable.prototype.getRevision = function() {
+  return this.revision_;
+};
 
 
 /**
- * A simple function that returns the first argument of whatever is passed
- * into it.
- * @param {T=} opt_returnValue The single value that will be returned.
- * @param {...*} var_args Optional trailing arguments. These are ignored.
- * @return {T} The first argument passed in, or undefined if nothing was passed.
- * @template T
+ * Listen for a certain type of event.
+ * @param {string|Array.<string>} type The event type or array of event types.
+ * @param {function(?): ?} listener The listener function.
+ * @param {Object=} opt_this The object to use as `this` in `listener`.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @api stable
  */
-goog.functions.identity = function(opt_returnValue, var_args) {
-  return opt_returnValue;
+ol.Observable.prototype.on = function(type, listener, opt_this) {
+  return goog.events.listen(this, type, listener, false, opt_this);
 };
 
 
 /**
- * Creates a function that always throws an error with the given message.
- * @param {string} message The error message.
- * @return {!Function} The error-throwing function.
+ * Listen once for a certain type of event.
+ * @param {string|Array.<string>} type The event type or array of event types.
+ * @param {function(?): ?} listener The listener function.
+ * @param {Object=} opt_this The object to use as `this` in `listener`.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @api stable
  */
-goog.functions.error = function(message) {
-  return function() {
-    throw Error(message);
-  };
+ol.Observable.prototype.once = function(type, listener, opt_this) {
+  return goog.events.listenOnce(this, type, listener, false, opt_this);
 };
 
 
 /**
- * Creates a function that throws the given object.
- * @param {*} err An object to be thrown.
- * @return {!Function} The error-throwing function.
+ * 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
  */
-goog.functions.fail = function(err) {
-  return function() {
-    throw err;
-  }
+ol.Observable.prototype.un = function(type, listener, opt_this) {
+  goog.events.unlisten(this, type, listener, false, opt_this);
 };
 
 
 /**
- * Given a function, create a function that keeps opt_numArgs arguments and
- * silently discards all additional arguments.
- * @param {Function} f The original function.
- * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0.
- * @return {!Function} A version of f that only keeps the first opt_numArgs
- *     arguments.
+ * Removes an event listener using the key returned by `on()` or `once()`.
+ * Note that using the {@link ol.Observable.unByKey} static function is to
+ * be preferred.
+ * @param {goog.events.Key} key The key returned by `on()` or `once()`.
+ * @function
+ * @api stable
  */
-goog.functions.lock = function(f, opt_numArgs) {
-  opt_numArgs = opt_numArgs || 0;
-  return function() {
-    return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs));
-  };
-};
-
+ol.Observable.prototype.unByKey = ol.Observable.unByKey;
 
 /**
- * Creates a function that returns its nth argument.
- * @param {number} n The position of the return argument.
- * @return {!Function} A new function.
+ * An implementation of Google Maps' MVCObject.
+ * @see https://developers.google.com/maps/articles/mvcfun
+ * @see https://developers.google.com/maps/documentation/javascript/reference
  */
-goog.functions.nth = function(n) {
-  return function() {
-    return arguments[n];
-  };
-};
+
+goog.provide('ol.Object');
+goog.provide('ol.ObjectEvent');
+goog.provide('ol.ObjectEventType');
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('ol.Observable');
 
 
 /**
- * Given a function, create a new function that swallows its return value
- * and replaces it with a new one.
- * @param {Function} f A function.
- * @param {T} retValue A new return value.
- * @return {function(...[?]):T} A new function.
- * @template T
+ * @enum {string}
  */
-goog.functions.withReturnValue = function(f, retValue) {
-  return goog.functions.sequence(f, goog.functions.constant(retValue));
+ol.ObjectEventType = {
+  /**
+   * Triggered when a property is changed.
+   * @event ol.ObjectEvent#propertychange
+   * @api
+   */
+  PROPERTYCHANGE: 'propertychange'
 };
 
 
+
 /**
- * Creates the composition of the functions passed in.
- * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).
- * @param {function(...[?]):T} fn The final function.
- * @param {...Function} var_args A list of functions.
- * @return {function(...[?]):T} The composition of all inputs.
- * @template T
+ * @classdesc
+ * Events emitted by {@link ol.Object} instances are instances of this type.
+ *
+ * @param {string} type The event type.
+ * @param {string} key The property name.
+ * @param {*} oldValue The old value for `key`.
+ * @extends {goog.events.Event}
+ * @implements {oli.ObjectEvent}
+ * @constructor
  */
-goog.functions.compose = function(fn, var_args) {
-  var functions = arguments;
-  var length = functions.length;
-  return function() {
-    var result;
-    if (length) {
-      result = functions[length - 1].apply(this, arguments);
-    }
+ol.ObjectEvent = function(type, key, oldValue) {
+  goog.base(this, type);
 
-    for (var i = length - 2; i >= 0; i--) {
-      result = functions[i].call(this, result);
-    }
-    return result;
-  };
-};
+  /**
+   * The name of the property whose value is changing.
+   * @type {string}
+   * @api
+   */
+  this.key = key;
 
+  /**
+   * The old value. To get the new value use `e.target.get(e.key)` where
+   * `e` is the event object.
+   * @type {*}
+   * @api
+   */
+  this.oldValue = oldValue;
 
-/**
- * Creates a function that calls the functions passed in in sequence, and
- * returns the value of the last function. For example,
- * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).
- * @param {...Function} var_args A list of functions.
- * @return {!Function} A function that calls all inputs in sequence.
- */
-goog.functions.sequence = function(var_args) {
-  var functions = arguments;
-  var length = functions.length;
-  return function() {
-    var result;
-    for (var i = 0; i < length; i++) {
-      result = functions[i].apply(this, arguments);
-    }
-    return result;
-  };
 };
+goog.inherits(ol.ObjectEvent, goog.events.Event);
 
 
-/**
- * Creates a function that returns true if each of its components evaluates
- * to true. The components are evaluated in order, and the evaluation will be
- * short-circuited as soon as a function returns false.
- * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).
- * @param {...Function} var_args A list of functions.
- * @return {function(...[?]):boolean} A function that ANDs its component
- *      functions.
- */
-goog.functions.and = function(var_args) {
-  var functions = arguments;
-  var length = functions.length;
-  return function() {
-    for (var i = 0; i < length; i++) {
-      if (!functions[i].apply(this, arguments)) {
-        return false;
-      }
-    }
-    return true;
-  };
-};
-
 
 /**
- * Creates a function that returns true if any of its components evaluates
- * to true. The components are evaluated in order, and the evaluation will be
- * short-circuited as soon as a function returns true.
- * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).
- * @param {...Function} var_args A list of functions.
- * @return {function(...[?]):boolean} A function that ORs its component
- *    functions.
+ * @constructor
+ * @param {ol.Object} source Source object.
+ * @param {ol.Object} target Target object.
+ * @param {string} sourceKey Source key.
+ * @param {string} targetKey Target key.
  */
-goog.functions.or = function(var_args) {
-  var functions = arguments;
-  var length = functions.length;
-  return function() {
-    for (var i = 0; i < length; i++) {
-      if (functions[i].apply(this, arguments)) {
-        return true;
-      }
-    }
-    return false;
-  };
-};
+ol.ObjectAccessor = function(source, target, sourceKey, targetKey) {
 
+  /**
+   * @type {ol.Object}
+   */
+  this.source = source;
 
-/**
- * Creates a function that returns the Boolean opposite of a provided function.
- * For example, (goog.functions.not(f))(x) is equivalent to !f(x).
- * @param {!Function} f The original function.
- * @return {function(...[?]):boolean} A function that delegates to f and returns
- * opposite.
- */
-goog.functions.not = function(f) {
-  return function() {
-    return !f.apply(this, arguments);
-  };
-};
+  /**
+   * @type {ol.Object}
+   */
+  this.target = target;
 
+  /**
+   * @type {string}
+   */
+  this.sourceKey = sourceKey;
 
-/**
- * Generic factory function to construct an object given the constructor
- * and the arguments. Intended to be bound to create object factories.
- *
- * Callers should cast the result to the appropriate type for proper type
- * checking by the compiler.
- * @param {!Function} constructor The constructor for the Object.
- * @param {...*} var_args The arguments to be passed to the constructor.
- * @return {!Object} A new instance of the class given in {@code constructor}.
- */
-goog.functions.create = function(constructor, var_args) {
   /**
- * @constructor
- * @final
- */
-  var temp = function() {};
-  temp.prototype = constructor.prototype;
+   * @type {string}
+   */
+  this.targetKey = targetKey;
 
-  // obj will have constructor's prototype in its chain and
-  // 'obj instanceof constructor' will be true.
-  var obj = new temp();
+  /**
+   * @type {function(?): ?}
+   */
+  this.from = goog.functions.identity;
 
-  // obj is initialized by constructor.
-  // arguments is only array-like so lacks shift(), but can be used with
-  // the Array prototype function.
-  constructor.apply(obj, Array.prototype.slice.call(arguments, 1));
-  return obj;
+  /**
+   * @type {function(?): ?}
+   */
+  this.to = goog.functions.identity;
 };
 
 
 /**
- * @define {boolean} Whether the return value cache should be used.
- *    This should only be used to disable caches when testing.
+ * @param {function(?): ?} from A function that transforms the source value
+ *     before it is set to the target.
+ * @param {function(?): ?} to A function that transforms the target value
+ *     before it is set to the source.
+ * @api
  */
-goog.define('goog.functions.CACHE_RETURN_VALUE', true);
+ol.ObjectAccessor.prototype.transform = function(from, to) {
+  var oldValue = ol.Object.getKeyValue_(this.source, this.sourceKey);
+  this.from = from;
+  this.to = to;
+  this.source.notify(this.sourceKey, oldValue);
+};
+
 
 
 /**
- * Gives a wrapper function that caches the return value of a parameterless
- * function when first called.
- *
- * When called for the first time, the given function is called and its
- * return value is cached (thus this is only appropriate for idempotent
- * functions).  Subsequent calls will return the cached return value. This
- * allows the evaluation of expensive functions to be delayed until first used.
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Most non-trivial classes inherit from this.
  *
- * To cache the return values of functions with parameters, see goog.memoize.
+ * This extends {@link ol.Observable} with observable properties, where each
+ * property is observable as well as the object as a whole.
  *
- * @param {!function():T} fn A function to lazily evaluate.
- * @return {!function():T} A wrapped version the function.
- * @template T
- */
-goog.functions.cacheReturnValue = function(fn) {
-  var called = false;
-  var value;
-
-  return function() {
-    if (!goog.functions.CACHE_RETURN_VALUE) {
-      return fn();
-    }
-
-    if (!called) {
-      value = fn();
-      called = true;
-    }
-
-    return value;
-  }
-};
-
-// Copyright 2005 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview A disposable implementation of a custom
- * listenable/event target. See also: documentation for
- * {@code goog.events.Listenable}.
+ * Classes that inherit from this have pre-defined properties, to which you can
+ * add your own. 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()`.
  *
- * @author arv@google.com (Erik Arvidsson) [Original implementation]
- * @author pupius@google.com (Daniel Pupius) [Port to use goog.events]
- * @see ../demos/eventtarget.html
- * @see goog.events.Listenable
- */
-
-goog.provide('goog.events.EventTarget');
-
-goog.require('goog.Disposable');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.events.Listenable');
-goog.require('goog.events.ListenerMap');
-goog.require('goog.object');
-
-
-
-/**
- * An implementation of {@code goog.events.Listenable} with full W3C
- * EventTarget-like support (capture/bubble mechanism, stopping event
- * propagation, preventing default actions).
+ * 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 may subclass this class to turn your class into a Listenable.
+ * 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()}.
  *
- * Unless propagation is stopped, an event dispatched by an
- * EventTarget will bubble to the parent returned by
- * {@code getParentEventTarget}. To set the parent, call
- * {@code setParentEventTarget}. Subclasses that don't support
- * changing the parent can override the setter to throw an error.
+ * 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.
  *
- * Example usage:
- * <pre>
- *   var source = new goog.events.EventTarget();
- *   function handleEvent(e) {
- *     alert('Type: ' + e.type + '; Target: ' + e.target);
- *   }
- *   source.listen('foo', handleEvent);
- *   // Or: goog.events.listen(source, 'foo', handleEvent);
- *   ...
- *   source.dispatchEvent('foo');  // will call handleEvent
- *   ...
- *   source.unlisten('foo', handleEvent);
- *   // Or: goog.events.unlisten(source, 'foo', handleEvent);
- * </pre>
+ * The observable properties also implement a form of Key Value Observing.
+ * Two objects can be bound together such that a change in one will
+ * automatically be reflected in the other. See `bindTo` method for more
+ * details, and see {@link ol.dom.Input} for the specific case of binding an
+ * object with an HTML element.
  *
  * @constructor
- * @extends {goog.Disposable}
- * @implements {goog.events.Listenable}
+ * @extends {ol.Observable}
+ * @param {Object.<string, *>=} opt_values An object with key-value pairs.
+ * @fires ol.ObjectEvent
+ * @api
  */
-goog.events.EventTarget = function() {
-  goog.Disposable.call(this);
+ol.Object = function(opt_values) {
+  goog.base(this);
+
+  // Call goog.getUid to ensure that the order of objects' ids is the same as
+  // the order in which they were created.  This also helps to ensure that
+  // object properties are always added in the same order, which helps many
+  // JavaScript engines generate faster code.
+  goog.getUid(this);
 
   /**
-   * Maps of event type to an array of listeners.
-   * @private {!goog.events.ListenerMap}
+   * @private
+   * @type {Object.<string, *>}
    */
-  this.eventTargetListeners_ = new goog.events.ListenerMap(this);
+  this.values_ = {};
 
   /**
-   * The object to use for event.target. Useful when mixing in an
-   * EventTarget to another object.
-   * @private {!Object}
+   * @private
+   * @type {Object.<string, ol.ObjectAccessor>}
    */
-  this.actualEventTarget_ = this;
+  this.accessors_ = {};
 
   /**
-   * Parent event target, used during event bubbling.
-   *
-   * TODO(user): Change this to goog.events.Listenable. This
-   * currently breaks people who expect getParentEventTarget to return
-   * goog.events.EventTarget.
-   *
-   * @private {goog.events.EventTarget}
+   * @private
+   * @type {Object.<string, goog.events.Key>}
    */
-  this.parentEventTarget_ = null;
+  this.listeners_ = {};
+
+  if (goog.isDef(opt_values)) {
+    this.setProperties(opt_values);
+  }
 };
-goog.inherits(goog.events.EventTarget, goog.Disposable);
-goog.events.Listenable.addImplementation(goog.events.EventTarget);
+goog.inherits(ol.Object, ol.Observable);
 
 
 /**
- * An artificial cap on the number of ancestors you can have. This is mainly
- * for loop detection.
- * @const {number}
  * @private
+ * @type {Object.<string, string>}
  */
-goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
+ol.Object.changeEventTypeCache_ = {};
 
 
 /**
- * Returns the parent of this event target to use for bubbling.
- *
- * @return {goog.events.EventTarget} The parent EventTarget or null if
- *     there is no parent.
- * @override
+ * @private
+ * @type {Object.<string, string>}
  */
-goog.events.EventTarget.prototype.getParentEventTarget = function() {
-  return this.parentEventTarget_;
-};
+ol.Object.getterNameCache_ = {};
 
 
 /**
- * Sets the parent of this event target to use for capture/bubble
- * mechanism.
- * @param {goog.events.EventTarget} parent Parent listenable (null if none).
+ * @private
+ * @type {Object.<string, string>}
  */
-goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
-  this.parentEventTarget_ = parent;
-};
+ol.Object.setterNameCache_ = {};
 
 
 /**
- * Adds an event listener to the event target. The same handler can only be
- * added once per the type. Even if you add the same handler multiple times
- * using the same type then it will only be called once when the event is
- * dispatched.
- *
- * @param {string} type The type of the event to listen for.
- * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
- *     to handle the event. The handler can also be an object that implements
- *     the handleEvent method which takes the event object as argument.
- * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
- *     whether the listener is fired during the capture or bubble phase
- *     of the event.
- * @param {Object=} opt_handlerScope Object in whose scope to call
- *     the listener.
- * @deprecated Use {@code #listen} instead, when possible. Otherwise, use
- *     {@code goog.events.listen} if you are passing Object
- *     (instead of Function) as handler.
+ * @param {string} key Key name.
+ * @return {string} Change name.
  */
-goog.events.EventTarget.prototype.addEventListener = function(
-    type, handler, opt_capture, opt_handlerScope) {
-  goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
+ol.Object.getChangeEventType = function(key) {
+  return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
+      ol.Object.changeEventTypeCache_[key] :
+      (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
 };
 
 
 /**
- * Removes an event listener from the event target. The handler must be the
- * same object as the one added. If the handler has not been added then
- * nothing is done.
- *
- * @param {string} type The type of the event to listen for.
- * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
- *     to handle the event. The handler can also be an object that implements
- *     the handleEvent method which takes the event object as argument.
- * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
- *     whether the listener is fired during the capture or bubble phase
- *     of the event.
- * @param {Object=} opt_handlerScope Object in whose scope to call
- *     the listener.
- * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use
- *     {@code goog.events.unlisten} if you are passing Object
- *     (instead of Function) as handler.
+ * @param {string} key String.
+ * @return {string} Getter name.
  */
-goog.events.EventTarget.prototype.removeEventListener = function(
-    type, handler, opt_capture, opt_handlerScope) {
-  goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
+ol.Object.getGetterName = function(key) {
+  return ol.Object.getterNameCache_.hasOwnProperty(key) ?
+      ol.Object.getterNameCache_[key] :
+      (ol.Object.getterNameCache_[key] = 'get' + goog.string.capitalize(key));
 };
 
 
-/** @override */
-goog.events.EventTarget.prototype.dispatchEvent = function(e) {
-  this.assertInitialized_();
+/**
+ * @param {string} key String.
+ * @return {string} Setter name.
+ */
+ol.Object.getSetterName = function(key) {
+  return ol.Object.setterNameCache_.hasOwnProperty(key) ?
+      ol.Object.setterNameCache_[key] :
+      (ol.Object.setterNameCache_[key] = 'set' + goog.string.capitalize(key));
+};
 
-  var ancestorsTree, ancestor = this.getParentEventTarget();
-  if (ancestor) {
-    ancestorsTree = [];
-    var ancestorCount = 1;
-    for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
-      ancestorsTree.push(ancestor);
-      goog.asserts.assert(
-          (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
-          'infinite loop');
-    }
-  }
 
-  return goog.events.EventTarget.dispatchEventInternal_(
-      this.actualEventTarget_, e, ancestorsTree);
+/**
+ * Get the value for an object and a key. Use the getter (`getX`) if it exists,
+ * otherwise use the generic `get` function.
+ * @param {ol.Object} obj Object.
+ * @param {string} key Key;
+ * @return {*} Value;
+ * @private
+ */
+ol.Object.getKeyValue_ = function(obj, key) {
+  var getterName = ol.Object.getGetterName(key);
+  var getter = /** @type {function(): *|undefined} */
+      (goog.object.get(obj, getterName));
+  return goog.isDef(getter) ? getter.call(obj) : obj.get(key);
 };
 
 
 /**
- * Removes listeners from this object.  Classes that extend EventTarget may
- * need to override this method in order to remove references to DOM Elements
- * and additional listeners.
- * @override
+ * Set the value for an object and a key. Use the setter (`setX`) if it exists,
+ * otherwise use the generic `set` function.
+ * @param {ol.Object} obj Object.
+ * @param {string} key Key.
+ * @param {*} value Value.
+ * @private
  */
-goog.events.EventTarget.prototype.disposeInternal = function() {
-  goog.events.EventTarget.superClass_.disposeInternal.call(this);
-
-  this.removeAllListeners();
-  this.parentEventTarget_ = null;
+ol.Object.setKeyValue_ = function(obj, key, value) {
+  var setterName = ol.Object.getSetterName(key);
+  var setter = /** @type {function(*)|undefined} */
+      (goog.object.get(obj, setterName));
+  if (goog.isDef(setter)) {
+    setter.call(obj, value);
+  } else {
+    obj.set(key, value);
+  }
 };
 
 
-/** @override */
-goog.events.EventTarget.prototype.listen = function(
-    type, listener, opt_useCapture, opt_listenerScope) {
-  this.assertInitialized_();
-  return this.eventTargetListeners_.add(
-      String(type), listener, false /* callOnce */, opt_useCapture,
-      opt_listenerScope);
-};
+/**
+ * The bindTo method allows you to set up a two-way binding between a
+ * `source` and `target` object. The method returns an object with a
+ * `transform` method that you can use to provide `from` and `to`
+ * functions to transform values on the way from the source to the
+ * target and on the way back.
+*
+ * For example, if you had two map views (sourceView and targetView)
+ * and you wanted the target view to have double the resolution of the
+ * source view, you could transform the resolution on the way to and
+ * from the target with the following:
+ *
+ *     sourceView.bindTo('resolution', targetView)
+ *       .transform(
+ *         function(sourceResolution) {
+ *           // from sourceView.resolution to targetView.resolution
+ *           return 2 * sourceResolution;
+ *         },
+ *         function(targetResolution) {
+ *           // from targetView.resolution to sourceView.resolution
+ *           return targetResolution / 2;
+ *         }
+ *       );
+ *
+ * @param {string} key Key name.
+ * @param {ol.Object} target Target.
+ * @param {string=} opt_targetKey Target key.
+ * @return {ol.ObjectAccessor}
+ * @api
+ */
+ol.Object.prototype.bindTo = function(key, target, opt_targetKey) {
+  var targetKey = opt_targetKey || key;
+  this.unbind(key);
 
+  // listen for change:targetkey events
+  var eventType = ol.Object.getChangeEventType(targetKey);
+  this.listeners_[key] = goog.events.listen(target, eventType,
+      /**
+       * @param {ol.ObjectEvent} e Event.
+       * @this {ol.Object}
+       */
+      function(e) {
+        this.notify(key, e.oldValue);
+      }, undefined, this);
 
-/** @override */
-goog.events.EventTarget.prototype.listenOnce = function(
-    type, listener, opt_useCapture, opt_listenerScope) {
-  return this.eventTargetListeners_.add(
-      String(type), listener, true /* callOnce */, opt_useCapture,
-      opt_listenerScope);
+  var accessor = new ol.ObjectAccessor(this, target, key, targetKey);
+  this.accessors_[key] = accessor;
+  this.notify(key, this.values_[key]);
+  return accessor;
 };
 
 
-/** @override */
-goog.events.EventTarget.prototype.unlisten = function(
-    type, listener, opt_useCapture, opt_listenerScope) {
-  return this.eventTargetListeners_.remove(
-      String(type), listener, opt_useCapture, opt_listenerScope);
-};
-
-
-/** @override */
-goog.events.EventTarget.prototype.unlistenByKey = function(key) {
-  return this.eventTargetListeners_.removeByKey(key);
-};
-
-
-/** @override */
-goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
-  // TODO(user): Previously, removeAllListeners can be called on
-  // uninitialized EventTarget, so we preserve that behavior. We
-  // should remove this when usages that rely on that fact are purged.
-  if (!this.eventTargetListeners_) {
-    return 0;
+/**
+ * Gets a value.
+ * @param {string} key Key name.
+ * @return {*} Value.
+ * @api
+ */
+ol.Object.prototype.get = function(key) {
+  var value;
+  var accessors = this.accessors_;
+  if (accessors.hasOwnProperty(key)) {
+    var accessor = accessors[key];
+    value = ol.Object.getKeyValue_(accessor.target, accessor.targetKey);
+    value = accessor.to(value);
+  } else if (this.values_.hasOwnProperty(key)) {
+    value = this.values_[key];
   }
-  return this.eventTargetListeners_.removeAll(opt_type);
+  return value;
 };
 
 
-/** @override */
-goog.events.EventTarget.prototype.fireListeners = function(
-    type, capture, eventObject) {
-  // TODO(user): Original code avoids array creation when there
-  // is no listener, so we do the same. If this optimization turns
-  // out to be not required, we can replace this with
-  // getListeners(type, capture) instead, which is simpler.
-  var listenerArray = this.eventTargetListeners_.listeners[String(type)];
-  if (!listenerArray) {
-    return true;
-  }
-  listenerArray = listenerArray.concat();
-
-  var rv = true;
-  for (var i = 0; i < listenerArray.length; ++i) {
-    var listener = listenerArray[i];
-    // We might not have a listener if the listener was removed.
-    if (listener && !listener.removed && listener.capture == capture) {
-      var listenerFn = listener.listener;
-      var listenerHandler = listener.handler || listener.src;
-
-      if (listener.callOnce) {
-        this.unlistenByKey(listener);
+/**
+ * Get a list of object property names.
+ * @return {Array.<string>} List of property names.
+ * @api
+ */
+ol.Object.prototype.getKeys = function() {
+  var accessors = this.accessors_;
+  var keysObject;
+  if (goog.object.isEmpty(this.values_)) {
+    if (goog.object.isEmpty(accessors)) {
+      return [];
+    } else {
+      keysObject = accessors;
+    }
+  } else {
+    if (goog.object.isEmpty(accessors)) {
+      keysObject = this.values_;
+    } else {
+      keysObject = {};
+      var key;
+      for (key in this.values_) {
+        keysObject[key] = true;
+      }
+      for (key in accessors) {
+        keysObject[key] = true;
       }
-      rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
     }
   }
-
-  return rv && eventObject.returnValue_ != false;
+  return goog.object.getKeys(keysObject);
 };
 
 
-/** @override */
-goog.events.EventTarget.prototype.getListeners = function(type, capture) {
-  return this.eventTargetListeners_.getListeners(String(type), capture);
+/**
+ * Get an object of all property names and values.
+ * @return {Object.<string, *>} Object.
+ * @api
+ */
+ol.Object.prototype.getProperties = function() {
+  var properties = {};
+  var key;
+  for (key in this.values_) {
+    properties[key] = this.values_[key];
+  }
+  for (key in this.accessors_) {
+    properties[key] = this.get(key);
+  }
+  return properties;
 };
 
 
-/** @override */
-goog.events.EventTarget.prototype.getListener = function(
-    type, listener, capture, opt_listenerScope) {
-  return this.eventTargetListeners_.getListener(
-      String(type), listener, capture, opt_listenerScope);
+/**
+ * @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));
 };
 
 
-/** @override */
-goog.events.EventTarget.prototype.hasListener = function(
-    opt_type, opt_capture) {
-  var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
-  return this.eventTargetListeners_.hasListener(id, opt_capture);
+/**
+ * Sets a value.
+ * @param {string} key Key name.
+ * @param {*} value Value.
+ * @api
+ */
+ol.Object.prototype.set = function(key, value) {
+  var accessors = this.accessors_;
+  if (accessors.hasOwnProperty(key)) {
+    var accessor = accessors[key];
+    value = accessor.from(value);
+    ol.Object.setKeyValue_(accessor.target, accessor.targetKey, value);
+  } else {
+    var oldValue = this.values_[key];
+    this.values_[key] = value;
+    this.notify(key, oldValue);
+  }
 };
 
 
 /**
- * Sets the target to be used for {@code event.target} when firing
- * event. Mainly used for testing. For example, see
- * {@code goog.testing.events.mixinListenable}.
- * @param {!Object} target The target.
+ * Sets a collection of key-value pairs.
+ * @param {Object.<string, *>} values Values.
+ * @api
  */
-goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
-  this.actualEventTarget_ = target;
+ol.Object.prototype.setProperties = function(values) {
+  var key;
+  for (key in values) {
+    this.set(key, values[key]);
+  }
 };
 
 
 /**
- * Asserts that the event target instance is initialized properly.
- * @private
+ * Removes a binding. Unbinding will set the unbound property to the current
+ *     value. The object will not be notified, as the value has not changed.
+ * @param {string} key Key name.
+ * @api
  */
-goog.events.EventTarget.prototype.assertInitialized_ = function() {
-  goog.asserts.assert(
-      this.eventTargetListeners_,
-      'Event target is not initialized. Did you call the superclass ' +
-      '(goog.events.EventTarget) constructor?');
+ol.Object.prototype.unbind = function(key) {
+  var listeners = this.listeners_;
+  var listener = listeners[key];
+  if (listener) {
+    delete listeners[key];
+    goog.events.unlistenByKey(listener);
+    var value = this.get(key);
+    delete this.accessors_[key];
+    this.values_[key] = value;
+  }
 };
 
 
 /**
- * Dispatches the given event on the ancestorsTree.
- *
- * @param {!Object} target The target to dispatch on.
- * @param {goog.events.Event|Object|string} e The event object.
- * @param {Array.<goog.events.Listenable>=} opt_ancestorsTree The ancestors
- *     tree of the target, in reverse order from the closest ancestor
- *     to the root event target. May be null if the target has no ancestor.
- * @return {boolean} If anyone called preventDefault on the event object (or
- *     if any of the listeners returns false) this will also return false.
- * @private
+ * Removes all bindings.
+ * @api
  */
-goog.events.EventTarget.dispatchEventInternal_ = function(
-    target, e, opt_ancestorsTree) {
-  var type = e.type || /** @type {string} */ (e);
-
-  // If accepting a string or object, create a custom event object so that
-  // preventDefault and stopPropagation work with the event.
-  if (goog.isString(e)) {
-    e = new goog.events.Event(e, target);
-  } else if (!(e instanceof goog.events.Event)) {
-    var oldEvent = e;
-    e = new goog.events.Event(type, target);
-    goog.object.extend(e, oldEvent);
-  } else {
-    e.target = e.target || target;
+ol.Object.prototype.unbindAll = function() {
+  for (var key in this.listeners_) {
+    this.unbind(key);
   }
+};
 
-  var rv = true, currentTarget;
+goog.provide('ol.Size');
+goog.provide('ol.size');
 
-  // Executes all capture listeners on the ancestors, if any.
-  if (opt_ancestorsTree) {
-    for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
-         i--) {
-      currentTarget = e.currentTarget = opt_ancestorsTree[i];
-      rv = currentTarget.fireListeners(type, true, e) && rv;
-    }
-  }
 
-  // Executes capture and bubble listeners on the target.
-  if (!e.propagationStopped_) {
-    currentTarget = e.currentTarget = target;
-    rv = currentTarget.fireListeners(type, true, e) && rv;
-    if (!e.propagationStopped_) {
-      rv = currentTarget.fireListeners(type, false, e) && rv;
-    }
-  }
+/**
+ * An array of numbers representing a size: `[width, height]`.
+ * @typedef {Array.<number>}
+ * @api stable
+ */
+ol.Size;
 
-  // Executes all bubble listeners on the ancestors, if any.
-  if (opt_ancestorsTree) {
-    for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
-      currentTarget = e.currentTarget = opt_ancestorsTree[i];
-      rv = currentTarget.fireListeners(type, false, e) && rv;
-    }
-  }
 
-  return rv;
+/**
+ * Compares sizes for equality.
+ * @param {ol.Size} a Size.
+ * @param {ol.Size} b Size.
+ * @return {boolean} Equals.
+ */
+ol.size.equals = function(a, b) {
+  return a[0] == b[0] && a[1] == b[1];
 };
 
-goog.provide('ol.Observable');
-
-goog.require('goog.events');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
+goog.provide('ol.Coordinate');
+goog.provide('ol.CoordinateFormatType');
+goog.provide('ol.coordinate');
 
+goog.require('goog.math');
 
 
 /**
- * @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#dispatchChangeEvent}.
+ * A function that takes a {@link ol.Coordinate} and transforms it into a
+ * `{string}`.
  *
- * @constructor
- * @extends {goog.events.EventTarget}
- * @suppress {checkStructDictInheritance}
- * @struct
+ * @typedef {function((ol.Coordinate|undefined)): string}
  * @api stable
  */
-ol.Observable = function() {
-
-  goog.base(this);
+ol.CoordinateFormatType;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.revision_ = 0;
 
-};
-goog.inherits(ol.Observable, goog.events.EventTarget);
+/**
+ * An array of numbers representing an xy coordinate. Example: `[16, 48]`.
+ * @typedef {Array.<number>} ol.Coordinate
+ * @api stable
+ */
+ol.Coordinate;
 
 
 /**
- * Dispatches a `change` event.
- * @fires change
- * @api
+ * 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.Observable.prototype.dispatchChangeEvent = function() {
-  ++this.revision_;
-  this.dispatchEvent(goog.events.EventType.CHANGE);
+ol.coordinate.add = function(coordinate, delta) {
+  coordinate[0] += delta[0];
+  coordinate[1] += delta[1];
+  return coordinate;
 };
 
 
 /**
- * @return {number} Revision.
- * @api
+ * 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.Observable.prototype.getRevision = function() {
-  return this.revision_;
+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];
 };
 
 
 /**
- * Listen for a certain type of event.
- * @param {string|Array.<string>} type The event type or array of event types.
- * @param {function(?): ?} listener The listener function.
- * @param {Object=} opt_this The object to use as `this` in `listener`.
- * @return {goog.events.Key} Unique key for the listener.
+ * 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.Observable.prototype.on = function(type, listener, opt_this) {
-  return goog.events.listen(this, type, listener, false, opt_this);
+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);
+      });
 };
 
 
 /**
- * Listen once for a certain type of event.
- * @param {string|Array.<string>} type The event type or array of event types.
- * @param {function(?): ?} listener The listener function.
- * @param {Object=} opt_this The object to use as `this` in `listener`.
- * @return {goog.events.Key} Unique key for the listener.
- * @api stable
+ * @private
+ * @param {number} degrees Degrees.
+ * @param {string} hemispheres Hemispheres.
+ * @return {string} String.
  */
-ol.Observable.prototype.once = function(type, listener, opt_this) {
-  return goog.events.listenOnce(this, type, listener, false, opt_this);
+ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres) {
+  var normalizedDegrees = goog.math.modulo(degrees + 180, 360) - 180;
+  var x = Math.abs(Math.round(3600 * normalizedDegrees));
+  return Math.floor(x / 3600) + '\u00b0 ' +
+      Math.floor((x / 60) % 60) + '\u2032 ' +
+      Math.floor(x % 60) + '\u2033 ' +
+      hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0);
 };
 
 
 /**
- * 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`.
+ * 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} Formated coordinate.
  * @api stable
  */
-ol.Observable.prototype.un = function(type, listener, opt_this) {
-  goog.events.unlisten(this, type, listener, false, opt_this);
+ol.coordinate.format = function(coordinate, template, opt_fractionDigits) {
+  if (goog.isDef(coordinate)) {
+    return template
+      .replace('{x}', coordinate[0].toFixed(opt_fractionDigits))
+      .replace('{y}', coordinate[1].toFixed(opt_fractionDigits));
+  } else {
+    return '';
+  }
 };
 
 
 /**
- * Removes an event listener using the key returned by `on()` or `once()`.
- * @param {goog.events.Key} key Key.
- * @api stable
+ * @param {ol.Coordinate} coordinate1 First coordinate.
+ * @param {ol.Coordinate} coordinate2 Second coordinate.
+ * @return {boolean} Whether the passed coordinates are equal.
  */
-ol.Observable.prototype.unByKey = function(key) {
-  goog.events.unlistenByKey(key);
+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;
 };
 
+
 /**
- * An implementation of Google Maps' MVCObject.
- * @see https://developers.google.com/maps/articles/mvcfun
- * @see https://developers.google.com/maps/documentation/javascript/reference
+ * 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
  */
-
-goog.provide('ol.Object');
-goog.provide('ol.ObjectEvent');
-goog.provide('ol.ObjectEventType');
-
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.functions');
-goog.require('goog.object');
-goog.require('ol.Observable');
+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;
+};
 
 
 /**
- * @enum {string}
+ * 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.ObjectEventType = {
-  /**
-   * Triggered before a property is changed.
-   * @event ol.ObjectEvent#beforepropertychange
-   * @api
-   */
-  BEFOREPROPERTYCHANGE: 'beforepropertychange',
-  /**
-   * Triggered when a property is changed.
-   * @event ol.ObjectEvent#propertychange
-   * @api
-   */
-  PROPERTYCHANGE: 'propertychange'
+ol.coordinate.scale = function(coordinate, scale) {
+  coordinate[0] *= scale;
+  coordinate[1] *= scale;
+  return coordinate;
 };
 
 
-
 /**
- * @classdesc
- * Events emitted by {@link ol.Object} instances are instances of this type.
+ * Subtract `delta` to `coordinate`. `coordinate` is modified in place and
+ * returned by the function.
  *
- * @param {string} type The event type.
- * @param {string} key The property name.
- * @extends {goog.events.Event}
- * @implements {oli.ObjectEvent}
- * @constructor
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Coordinate} delta Delta.
+ * @return {ol.Coordinate} Coordinate.
  */
-ol.ObjectEvent = function(type, key) {
-  goog.base(this, type);
-
-  /**
-   * The name of the property whose value is changing.
-   * @type {string}
-   * @api
-   */
-  this.key = key;
-
+ol.coordinate.sub = function(coordinate, delta) {
+  coordinate[0] -= delta[0];
+  coordinate[1] -= delta[1];
+  return coordinate;
 };
-goog.inherits(ol.ObjectEvent, goog.events.Event);
-
 
 
 /**
- * @constructor
- * @param {ol.Object} source Source object.
- * @param {ol.Object} target Target object.
- * @param {string} sourceKey Source key.
- * @param {string} targetKey Target key.
+ * @param {ol.Coordinate} coord1 First coordinate.
+ * @param {ol.Coordinate} coord2 Second coordinate.
+ * @return {number} Squared distance between coord1 and coord2.
  */
-ol.ObjectAccessor = function(source, target, sourceKey, targetKey) {
-
-  /**
-   * @type {ol.Object}
-   */
-  this.source = source;
-
-  /**
-   * @type {ol.Object}
-   */
-  this.target = target;
-
-  /**
-   * @type {string}
-   */
-  this.sourceKey = sourceKey;
-
-  /**
-   * @type {string}
-   */
-  this.targetKey = targetKey;
+ol.coordinate.squaredDistance = function(coord1, coord2) {
+  var dx = coord1[0] - coord2[0];
+  var dy = coord1[1] - coord2[1];
+  return dx * dx + dy * dy;
+};
 
-  /**
-   * @type {function(?): ?}
-   */
-  this.from = goog.functions.identity;
 
-  /**
-   * @type {function(?): ?}
-   */
-  this.to = goog.functions.identity;
+/**
+ * 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));
 };
 
 
 /**
- * @param {function(?): ?} from A function that transforms the source value
- *     before it is set to the target.
- * @param {function(?): ?} to A function that transforms the target value
- *     before it is set to the source.
- * @api
+ * Example:
+ *
+ *     var coord = [7.85, 47.983333];
+ *     var out = ol.coordinate.toStringHDMS(coord);
+ *     // out is now '47° 59′ 0″ N 7° 51′ 0″ E'
+ *
+ * @param {ol.Coordinate|undefined} coordinate Coordinate.
+ * @return {string} Hemisphere, degrees, minutes and seconds.
+ * @api stable
  */
-ol.ObjectAccessor.prototype.transform = function(from, to) {
-  this.from = from;
-  this.to = to;
-  this.source.notify(this.sourceKey);
+ol.coordinate.toStringHDMS = function(coordinate) {
+  if (goog.isDef(coordinate)) {
+    return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS') + ' ' +
+        ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW');
+  } else {
+    return '';
+  }
 };
 
 
-
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Most non-trivial classes inherit from this.
+ * Example without specifying fractional digits:
  *
- * This extends {@link ol.Observable} with observable properties, where each
- * property is observable as well as the object as a whole.
+ *     var coord = [7.85, 47.983333];
+ *     var out = ol.coordinate.toStringXY(coord);
+ *     // out is now '8, 48'
  *
- * Classes that inherit from this have pre-defined properties, to which you can
- * add your own. 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()`.
+ * Example explicitly specifying 1 fractional digit:
  *
- * 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.
+ *     var coord = [7.85, 47.983333];
+ *     var out = ol.coordinate.toStringXY(coord, 1);
+ *     // out is now '7.8, 48.0'
+ *
+ * @param {ol.Coordinate|undefined} coordinate Coordinate.
+ * @param {number=} opt_fractionDigits The number of digits to include
+ *    after the decimal point. Default is `0`.
+ * @return {string} XY.
+ * @api stable
+ */
+ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
+  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
+};
+
+
+/**
+ * Create an ol.Coordinate from an Array and take into account axis order.
  *
- * You can add your own observable properties with `set('myProp', 'new value')`,
- * and retrieve that with `get('myProp')`. A change listener can then be
- * registered with `on('change:myProp', ...)`. And a change can be triggered
- * with `dispatchEvent('change:myProp')`. You can get a list of all properties
- * with `getProperties()`.
+ * Examples:
  *
- * 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.
+ *     var northCoord = ol.coordinate.fromProjectedArray([1, 2], 'n');
+ *     // northCoord is now [2, 1]
  *
- * The observable properties also implement a form of Key Value Observing.
- * Two objects can be bound together such that a change in one will
- * automatically be reflected in the other. See `bindTo` method for more
- * details, and see {@link ol.dom.Input} for the specific case of binding an
- * object with an HTML element.
+ *     var eastCoord = ol.coordinate.fromProjectedArray([1, 2], 'e');
+ *     // eastCoord is now [1, 2]
  *
- * @constructor
- * @extends {ol.Observable}
- * @param {Object.<string, *>=} opt_values An object with key-value pairs.
- * @fires ol.ObjectEvent
- * @api
+ * @param {Array} array The array with coordinates.
+ * @param {string} axis the axis info.
+ * @return {ol.Coordinate} The coordinate created.
  */
-ol.Object = function(opt_values) {
-  goog.base(this);
-
-  // Call goog.getUid to ensure that the order of objects' ids is the same as
-  // the order in which they were created.  This also helps to ensure that
-  // object properties are always added in the same order, which helps many
-  // JavaScript engines generate faster code.
-  goog.getUid(this);
-
-  /**
-   * @private
-   * @type {Object.<string, *>}
-   */
-  this.values_ = {};
+ol.coordinate.fromProjectedArray = function(array, axis) {
+  var firstAxis = axis.charAt(0);
+  if (firstAxis === 'n' || firstAxis === 's') {
+    return [array[1], array[0]];
+  } else {
+    return array;
+  }
+};
 
-  /**
-   * @private
-   * @type {Object.<string, ol.ObjectAccessor>}
-   */
-  this.accessors_ = {};
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-  /**
-   * Lookup of beforechange listener keys.
-   * @type {Object.<string, goog.events.Key>}
-   * @private
-   */
-  this.beforeChangeListeners_ = {};
 
-  /**
-   * @private
-   * @type {Object.<string, goog.events.Key>}
-   */
-  this.listeners_ = {};
+/**
+ * @fileoverview Supplies a Float32Array implementation that implements
+ *     most of the Float32Array spec and that can be used when a built-in
+ *     implementation is not available.
+ *
+ *     Note that if no existing Float32Array implementation is found then
+ *     this class and all its public properties are exported as Float32Array.
+ *
+ *     Adding support for the other TypedArray classes here does not make sense
+ *     since this vector math library only needs Float32Array.
+ *
+ */
+goog.provide('goog.vec.Float32Array');
 
-  if (goog.isDef(opt_values)) {
-    this.setProperties(opt_values);
-  }
-};
-goog.inherits(ol.Object, ol.Observable);
 
 
 /**
- * @private
- * @type {Object.<string, string>}
+ * Constructs a new Float32Array. The new array is initialized to all zeros.
+ *
+ * @param {goog.vec.Float32Array|Array|ArrayBuffer|number} p0
+ *     The length of the array, or an array to initialize the contents of the
+ *     new Float32Array.
+ * @constructor
+ * @final
  */
-ol.Object.changeEventTypeCache_ = {};
+goog.vec.Float32Array = function(p0) {
+  this.length = /** @type {number} */ (p0.length || p0);
+  for (var i = 0; i < this.length; i++) {
+    this[i] = p0[i] || 0;
+  }
+};
 
 
 /**
- * @private
- * @type {Object.<string, string>}
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
  */
-ol.Object.getterNameCache_ = {};
+goog.vec.Float32Array.BYTES_PER_ELEMENT = 4;
 
 
 /**
- * @private
- * @type {Object.<string, string>}
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
  */
-ol.Object.setterNameCache_ = {};
+goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT = 4;
 
 
 /**
- * @param {string} str String.
- * @return {string} Capitalized string.
+ * Sets elements of the array.
+ * @param {Array<number>|Float32Array} values The array of values.
+ * @param {number=} opt_offset The offset in this array to start.
  */
-ol.Object.capitalize = function(str) {
-  return str.substr(0, 1).toUpperCase() + str.substr(1);
+goog.vec.Float32Array.prototype.set = function(values, opt_offset) {
+  opt_offset = opt_offset || 0;
+  for (var i = 0; i < values.length && opt_offset + i < this.length; i++) {
+    this[opt_offset + i] = values[i];
+  }
 };
 
 
 /**
- * @param {string} key Key name.
- * @return {string} Change name.
+ * Creates a string representation of this array.
+ * @return {string} The string version of this array.
+ * @override
  */
-ol.Object.getChangeEventType = function(key) {
-  return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
-      ol.Object.changeEventTypeCache_[key] :
-      (ol.Object.changeEventTypeCache_[key] = 'change:' + key.toLowerCase());
-};
+goog.vec.Float32Array.prototype.toString = Array.prototype.join;
 
 
 /**
- * @param {string} key String.
- * @return {string} Getter name.
+ * Note that we cannot implement the subarray() or (deprecated) slice()
+ * methods properly since doing so would require being able to overload
+ * the [] operator which is not possible in javascript.  So we leave
+ * them unimplemented.  Any attempt to call these methods will just result
+ * in a javascript error since we leave them undefined.
  */
-ol.Object.getGetterName = function(key) {
-  return ol.Object.getterNameCache_.hasOwnProperty(key) ?
-      ol.Object.getterNameCache_[key] :
-      (ol.Object.getterNameCache_[key] = 'get' + ol.Object.capitalize(key));
-};
 
 
 /**
- * @param {string} key String.
- * @return {string} Setter name.
+ * If no existing Float32Array implementation is found then we export
+ * goog.vec.Float32Array as Float32Array.
  */
-ol.Object.getSetterName = function(key) {
-  return ol.Object.setterNameCache_.hasOwnProperty(key) ?
-      ol.Object.setterNameCache_[key] :
-      (ol.Object.setterNameCache_[key] = 'set' + ol.Object.capitalize(key));
-};
+if (typeof Float32Array == 'undefined') {
+  goog.exportProperty(goog.vec.Float32Array, 'BYTES_PER_ELEMENT',
+                      goog.vec.Float32Array.BYTES_PER_ELEMENT);
+  goog.exportProperty(goog.vec.Float32Array.prototype, 'BYTES_PER_ELEMENT',
+                      goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT);
+  goog.exportProperty(goog.vec.Float32Array.prototype, 'set',
+                      goog.vec.Float32Array.prototype.set);
+  goog.exportProperty(goog.vec.Float32Array.prototype, 'toString',
+                      goog.vec.Float32Array.prototype.toString);
+  goog.exportSymbol('Float32Array', goog.vec.Float32Array);
+}
+
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 
 /**
- * The bindTo method allows you to set up a two-way binding between a
- * `source` and `target` object. The method returns an object with a
- * `transform` method that you can use to provide `from` and `to`
- * functions to transform values on the way from the source to the
- * target and on the way back.
-*
- * For example, if you had two map views (sourceView and targetView)
- * and you wanted the target view to have double the resolution of the
- * source view, you could transform the resolution on the way to and
- * from the target with the following:
+ * @fileoverview Supplies a Float64Array implementation that implements
+ * most of the Float64Array spec and that can be used when a built-in
+ * implementation is not available.
  *
- *     sourceView.bindTo('resolution', targetView)
- *       .transform(
- *         function(sourceResolution) {
- *           // from sourceView.resolution to targetView.resolution
- *           return 2 * sourceResolution;
- *         },
- *         function(targetResolution) {
- *           // from targetView.resolution to sourceView.resolution
- *           return targetResolution / 2;
- *         }
- *       );
+ * Note that if no existing Float64Array implementation is found then this
+ * class and all its public properties are exported as Float64Array.
+ *
+ * Adding support for the other TypedArray classes here does not make sense
+ * since this vector math library only needs Float32Array and Float64Array.
  *
- * @param {string} key Key name.
- * @param {ol.Object} target Target.
- * @param {string=} opt_targetKey Target key.
- * @return {ol.ObjectAccessor}
- * @api
  */
-ol.Object.prototype.bindTo = function(key, target, opt_targetKey) {
-  var targetKey = opt_targetKey || key;
-  this.unbind(key);
-
-  // listen for change:targetkey events
-  var eventType = ol.Object.getChangeEventType(targetKey);
-  this.listeners_[key] = goog.events.listen(target, eventType,
-      /**
-       * @this {ol.Object}
-       */
-      function() {
-        this.notify(key);
-      }, undefined, this);
-
-  // listen for beforechange events and relay if key matches
-  this.beforeChangeListeners_[key] = goog.events.listen(target,
-      ol.ObjectEventType.BEFOREPROPERTYCHANGE,
-      this.createBeforeChangeListener_(key, targetKey),
-      undefined, this);
+goog.provide('goog.vec.Float64Array');
 
-  var accessor = new ol.ObjectAccessor(this, target, key, targetKey);
-  this.accessors_[key] = accessor;
-  this.notify(key);
-  return accessor;
-};
 
 
 /**
- * Create a listener for beforechange events on a target object.  This listener
- * will relay events on this object if the event key matches the provided target
- * key.
- * @param {string} key The key on this object whose value will be changing.
- * @param {string} targetKey The key on the target object.
- * @return {function(this: ol.Object, ol.ObjectEvent)} Listener.
- * @private
+ * Constructs a new Float64Array. The new array is initialized to all zeros.
+ *
+ * @param {goog.vec.Float64Array|Array|ArrayBuffer|number} p0
+ *     The length of the array, or an array to initialize the contents of the
+ *     new Float64Array.
+ * @constructor
+ * @final
  */
-ol.Object.prototype.createBeforeChangeListener_ = function(key, targetKey) {
-  /**
-   * Conditionally relay beforechange events if event key matches target key.
-   * @param {ol.ObjectEvent} event The beforechange event from the target.
-   * @this {ol.Object}
-   */
-  return function(event) {
-    if (event.key === targetKey) {
-      this.dispatchEvent(
-          new ol.ObjectEvent(ol.ObjectEventType.BEFOREPROPERTYCHANGE, key));
-    }
-  };
+goog.vec.Float64Array = function(p0) {
+  this.length = /** @type {number} */ (p0.length || p0);
+  for (var i = 0; i < this.length; i++) {
+    this[i] = p0[i] || 0;
+  }
 };
 
 
 /**
- * Gets a value.
- * @param {string} key Key name.
- * @return {*} Value.
- * @api
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
  */
-ol.Object.prototype.get = function(key) {
-  var value;
-  var accessors = this.accessors_;
-  if (accessors.hasOwnProperty(key)) {
-    var accessor = accessors[key];
-    var target = accessor.target;
-    var targetKey = accessor.targetKey;
-    var getterName = ol.Object.getGetterName(targetKey);
-    var getter = /** @type {function(): *|undefined} */
-        (goog.object.get(target, getterName));
-    if (goog.isDef(getter)) {
-      value = getter.call(target);
-    } else {
-      value = target.get(targetKey);
-    }
-    value = accessor.to(value);
-  } else if (this.values_.hasOwnProperty(key)) {
-    value = this.values_[key];
-  }
-  return value;
-};
+goog.vec.Float64Array.BYTES_PER_ELEMENT = 8;
 
 
 /**
- * Get a list of object property names.
- * @return {Array.<string>} List of property names.
- * @api
+ * The number of bytes in an element (as defined by the Typed Array
+ * specification).
+ *
+ * @type {number}
  */
-ol.Object.prototype.getKeys = function() {
-  var accessors = this.accessors_;
-  var keysObject;
-  if (goog.object.isEmpty(this.values_)) {
-    if (goog.object.isEmpty(accessors)) {
-      return [];
-    } else {
-      keysObject = accessors;
-    }
-  } else {
-    if (goog.object.isEmpty(accessors)) {
-      keysObject = this.values_;
-    } else {
-      keysObject = {};
-      var key;
-      for (key in this.values_) {
-        keysObject[key] = true;
-      }
-      for (key in accessors) {
-        keysObject[key] = true;
-      }
-    }
-  }
-  return goog.object.getKeys(keysObject);
-};
+goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT = 8;
 
 
 /**
- * Get an object of all property names and values.
- * @return {Object.<string, *>} Object.
- * @api
+ * Sets elements of the array.
+ * @param {Array<number>|Float64Array} values The array of values.
+ * @param {number=} opt_offset The offset in this array to start.
  */
-ol.Object.prototype.getProperties = function() {
-  var properties = {};
-  var key;
-  for (key in this.values_) {
-    properties[key] = this.values_[key];
-  }
-  for (key in this.accessors_) {
-    properties[key] = this.get(key);
+goog.vec.Float64Array.prototype.set = function(values, opt_offset) {
+  opt_offset = opt_offset || 0;
+  for (var i = 0; i < values.length && opt_offset + i < this.length; i++) {
+    this[opt_offset + i] = values[i];
   }
-  return properties;
 };
 
 
 /**
- * @param {string} key Key name.
+ * Creates a string representation of this array.
+ * @return {string} The string version of this array.
+ * @override
  */
-ol.Object.prototype.notify = function(key) {
-  var eventType = ol.Object.getChangeEventType(key);
-  this.dispatchEvent(eventType);
-  this.dispatchEvent(
-      new ol.ObjectEvent(ol.ObjectEventType.PROPERTYCHANGE, key));
-};
+goog.vec.Float64Array.prototype.toString = Array.prototype.join;
 
 
 /**
- * Sets a value.
- * @param {string} key Key name.
- * @param {*} value Value.
- * @api
+ * Note that we cannot implement the subarray() or (deprecated) slice()
+ * methods properly since doing so would require being able to overload
+ * the [] operator which is not possible in javascript.  So we leave
+ * them unimplemented.  Any attempt to call these methods will just result
+ * in a javascript error since we leave them undefined.
  */
-ol.Object.prototype.set = function(key, value) {
-  this.dispatchEvent(
-      new ol.ObjectEvent(ol.ObjectEventType.BEFOREPROPERTYCHANGE, key));
-  var accessors = this.accessors_;
-  if (accessors.hasOwnProperty(key)) {
-    var accessor = accessors[key];
-    var target = accessor.target;
-    var targetKey = accessor.targetKey;
-    value = accessor.from(value);
-    var setterName = ol.Object.getSetterName(targetKey);
-    var setter = /** @type {function(*)|undefined} */
-        (goog.object.get(target, setterName));
-    if (goog.isDef(setter)) {
-      setter.call(target, value);
-    } else {
-      target.set(targetKey, value);
-    }
-  } else {
-    this.values_[key] = value;
-    this.notify(key);
-  }
-};
 
 
 /**
- * Sets a collection of key-value pairs.
- * @param {Object.<string, *>} values Values.
- * @api
+ * If no existing Float64Array implementation is found then we export
+ * goog.vec.Float64Array as Float64Array.
  */
-ol.Object.prototype.setProperties = function(values) {
-  var key;
-  for (key in values) {
-    this.set(key, values[key]);
+if (typeof Float64Array == 'undefined') {
+  try {
+    goog.exportProperty(goog.vec.Float64Array, 'BYTES_PER_ELEMENT',
+                        goog.vec.Float64Array.BYTES_PER_ELEMENT);
+  } catch (float64ArrayError) {
+    // Do nothing.  This code is in place to fix b/7225850, in which an error
+    // is incorrectly thrown for Google TV on an old Chrome.
+    // TODO(user): remove after that version is retired.
   }
-};
 
+  goog.exportProperty(goog.vec.Float64Array.prototype, 'BYTES_PER_ELEMENT',
+                      goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT);
+  goog.exportProperty(goog.vec.Float64Array.prototype, 'set',
+                      goog.vec.Float64Array.prototype.set);
+  goog.exportProperty(goog.vec.Float64Array.prototype, 'toString',
+                      goog.vec.Float64Array.prototype.toString);
+  goog.exportSymbol('Float64Array', goog.vec.Float64Array);
+}
 
-/**
- * Removes a binding. Unbinding will set the unbound property to the current
- *     value. The object will not be notified, as the value has not changed.
- * @param {string} key Key name.
- * @api
- */
-ol.Object.prototype.unbind = function(key) {
-  var listeners = this.listeners_;
-  var listener = listeners[key];
-  if (listener) {
-    delete listeners[key];
-    goog.events.unlistenByKey(listener);
-    var value = this.get(key);
-    delete this.accessors_[key];
-    this.values_[key] = value;
-  }
-
-  // unregister any beforechange listener
-  var listenerKey = this.beforeChangeListeners_[key];
-  if (listenerKey) {
-    goog.events.unlistenByKey(listenerKey);
-    delete this.beforeChangeListeners_[key];
-  }
-};
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 
 /**
- * Removes all bindings.
- * @api
+ * @fileoverview Supplies global data types and constants for the vector math
+ *     library.
  */
-ol.Object.prototype.unbindAll = function() {
-  for (var key in this.listeners_) {
-    this.unbind(key);
-  }
-};
-
-goog.provide('ol.Size');
-goog.provide('ol.size');
+goog.provide('goog.vec');
+goog.provide('goog.vec.AnyType');
+goog.provide('goog.vec.ArrayType');
+goog.provide('goog.vec.Float32');
+goog.provide('goog.vec.Float64');
+goog.provide('goog.vec.Number');
 
 
 /**
- * An array of numbers representing a size: `[width, height]`.
- * @typedef {Array.<number>}
- * @api stable
+ * On platforms that don't have native Float32Array or Float64Array support we
+ * use a javascript implementation so that this math library can be used on all
+ * platforms.
+ * @suppress {extraRequire}
  */
-ol.Size;
+goog.require('goog.vec.Float32Array');
+/** @suppress {extraRequire} */
+goog.require('goog.vec.Float64Array');
 
+// All vector and matrix operations are based upon arrays of numbers using
+// either Float32Array, Float64Array, or a standard Javascript Array of
+// Numbers.
 
-/**
- * Compares sizes for equality.
- * @param {ol.Size} a Size.
- * @param {ol.Size} b Size.
- * @return {boolean} Equals.
- */
-ol.size.equals = function(a, b) {
-  return a[0] == b[0] && a[1] == b[1];
-};
 
-goog.provide('ol.Coordinate');
-goog.provide('ol.CoordinateFormatType');
-goog.provide('ol.coordinate');
+/** @typedef {!Float32Array} */
+goog.vec.Float32;
 
-goog.require('goog.math');
+
+/** @typedef {!Float64Array} */
+goog.vec.Float64;
+
+
+/** @typedef {!Array<number>} */
+goog.vec.Number;
+
+
+/** @typedef {!goog.vec.Float32|!goog.vec.Float64|!goog.vec.Number} */
+goog.vec.AnyType;
 
 
 /**
- * A function that takes a {@link ol.Coordinate} and transforms it into a
- * `{string}`.
- *
- * @typedef {function((ol.Coordinate|undefined)): string}
- * @api stable
+ * @deprecated Use AnyType.
+ * @typedef {!Float32Array|!Array<number>}
  */
-ol.CoordinateFormatType;
+goog.vec.ArrayType;
 
 
 /**
- * An array of numbers representing an xy coordinate. Example: `[16, 48]`.
- * @typedef {Array.<number>} ol.Coordinate
- * @api stable
+ * For graphics work, 6 decimal places of accuracy are typically all that is
+ * required.
+ *
+ * @type {number}
+ * @const
  */
-ol.Coordinate;
+goog.vec.EPSILON = 1e-6;
+
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 
 /**
- * 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]
+ * @fileoverview Supplies 3 element vectors that are compatible with WebGL.
+ * Each element is a float32 since that is typically the desired size of a
+ * 3-vector in the GPU.  The API is structured to avoid unnecessary memory
+ * allocations.  The last parameter will typically be the output vector and
+ * an object can be both an input and output parameter to all methods except
+ * where noted.
  *
- * @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;
-};
+goog.provide('goog.vec.Vec3');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+/** @typedef {goog.vec.Float32} */ goog.vec.Vec3.Float32;
+/** @typedef {goog.vec.Float64} */ goog.vec.Vec3.Float64;
+/** @typedef {goog.vec.Number} */ goog.vec.Vec3.Number;
+/** @typedef {goog.vec.AnyType} */ goog.vec.Vec3.AnyType;
+
+// The following two types are deprecated - use the above types instead.
+/** @typedef {Float32Array} */ goog.vec.Vec3.Type;
+/** @typedef {goog.vec.ArrayType} */ goog.vec.Vec3.Vec3Like;
 
 
 /**
- * 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.
+ * Creates a 3 element vector of Float32. The array is initialized to zero.
  *
- * @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.
+ * @return {!goog.vec.Vec3.Float32} The new 3 element array.
  */
-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];
+goog.vec.Vec3.createFloat32 = function() {
+  return new Float32Array(3);
 };
 
 
 /**
- * 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'
+ * Creates a 3 element vector of Float64. The array is initialized to zero.
  *
- * @param {number=} opt_fractionDigits The number of digits to include
- *    after the decimal point. Default is `0`.
- * @return {ol.CoordinateFormatType} Coordinate format.
- * @api stable
+ * @return {!goog.vec.Vec3.Float64} The new 3 element array.
  */
-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);
-      });
+goog.vec.Vec3.createFloat64 = function() {
+  return new Float64Array(3);
 };
 
 
 /**
- * @private
- * @param {number} degrees Degrees.
- * @param {string} hemispheres Hemispheres.
- * @return {string} String.
+ * Creates a 3 element vector of Number. The array is initialized to zero.
+ *
+ * @return {!goog.vec.Vec3.Number} The new 3 element array.
  */
-ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres) {
-  var normalizedDegrees = goog.math.modulo(degrees + 180, 360) - 180;
-  var x = Math.abs(Math.round(3600 * normalizedDegrees));
-  return Math.floor(x / 3600) + '\u00b0 ' +
-      Math.floor((x / 60) % 60) + '\u2032 ' +
-      Math.floor(x % 60) + '\u2033 ' +
-      hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0);
+goog.vec.Vec3.createNumber = function() {
+  var a = new Array(3);
+  goog.vec.Vec3.setFromValues(a, 0, 0, 0);
+  return a;
 };
 
 
 /**
- * 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).'
+ * Creates a 3 element vector of Float32Array. The array is initialized to zero.
  *
- * @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} Formated coordinate.
- * @api stable
+ * @deprecated Use createFloat32.
+ * @return {!goog.vec.Vec3.Type} The new 3 element array.
  */
-ol.coordinate.format = function(coordinate, template, opt_fractionDigits) {
-  if (goog.isDef(coordinate)) {
-    return template
-      .replace('{x}', coordinate[0].toFixed(opt_fractionDigits))
-      .replace('{y}', coordinate[1].toFixed(opt_fractionDigits));
-  } else {
-    return '';
-  }
+goog.vec.Vec3.create = function() {
+  return new Float32Array(3);
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate1 First coordinate.
- * @param {ol.Coordinate} coordinate2 Second coordinate.
- * @return {boolean} Whether the passed coordinates are equal.
+ * Creates a new 3 element FLoat32 vector initialized with the value from the
+ * given array.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec The source 3 element array.
+ * @return {!goog.vec.Vec3.Float32} The new 3 element array.
  */
-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;
+goog.vec.Vec3.createFloat32FromArray = function(vec) {
+  var newVec = goog.vec.Vec3.createFloat32();
+  goog.vec.Vec3.setFromArray(newVec, vec);
+  return newVec;
 };
 
 
 /**
- * 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]
+ * Creates a new 3 element Float32 vector initialized with the supplied values.
  *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} angle Angle in radian.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.Vec3.Float32} The new vector.
  */
-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;
+goog.vec.Vec3.createFloat32FromValues = function(v0, v1, v2) {
+  var a = goog.vec.Vec3.createFloat32();
+  goog.vec.Vec3.setFromValues(a, v0, v1, v2);
+  return a;
 };
 
 
 /**
- * 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]
+ * Creates a clone of the given 3 element Float32 vector.
  *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} scale Scale factor.
- * @return {ol.Coordinate} Coordinate.
+ * @param {goog.vec.Vec3.Float32} vec The source 3 element vector.
+ * @return {!goog.vec.Vec3.Float32} The new cloned vector.
  */
-ol.coordinate.scale = function(coordinate, scale) {
-  coordinate[0] *= scale;
-  coordinate[1] *= scale;
-  return coordinate;
-};
+goog.vec.Vec3.cloneFloat32 = goog.vec.Vec3.createFloat32FromArray;
 
 
 /**
- * Subtract `delta` to `coordinate`. `coordinate` is modified in place and
- * returned by the function.
+ * Creates a new 3 element Float64 vector initialized with the value from the
+ * given array.
  *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.Coordinate} delta Delta.
- * @return {ol.Coordinate} Coordinate.
+ * @param {goog.vec.Vec3.AnyType} vec The source 3 element array.
+ * @return {!goog.vec.Vec3.Float64} The new 3 element array.
  */
-ol.coordinate.sub = function(coordinate, delta) {
-  coordinate[0] -= delta[0];
-  coordinate[1] -= delta[1];
-  return coordinate;
+goog.vec.Vec3.createFloat64FromArray = function(vec) {
+  var newVec = goog.vec.Vec3.createFloat64();
+  goog.vec.Vec3.setFromArray(newVec, vec);
+  return newVec;
 };
 
 
 /**
- * @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;
+* Creates a new 3 element Float64 vector initialized with the supplied values.
+*
+* @param {number} v0 The value for element at index 0.
+* @param {number} v1 The value for element at index 1.
+* @param {number} v2 The value for element at index 2.
+* @return {!goog.vec.Vec3.Float64} The new vector.
+*/
+goog.vec.Vec3.createFloat64FromValues = function(v0, v1, v2) {
+  var vec = goog.vec.Vec3.createFloat64();
+  goog.vec.Vec3.setFromValues(vec, v0, v1, v2);
+  return vec;
 };
 
 
 /**
- * Calculate the squared distance from a coordinate to a line segment.
+ * Creates a clone of the given 3 element vector.
  *
- * @param {ol.Coordinate} coordinate Coordinate of the point.
- * @param {Array.<ol.Coordinate>} segment Line segment (2 coordinates).
- * @return {number} Squared distance from the point to the line segment.
+ * @param {goog.vec.Vec3.Float64} vec The source 3 element vector.
+ * @return {!goog.vec.Vec3.Float64} The new cloned vector.
  */
-ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
-  return ol.coordinate.squaredDistance(coordinate,
-      ol.coordinate.closestOnSegment(coordinate, segment));
-};
+goog.vec.Vec3.cloneFloat64 = goog.vec.Vec3.createFloat64FromArray;
 
 
 /**
- * Example:
- *
- *     var coord = [7.85, 47.983333];
- *     var out = ol.coordinate.toStringHDMS(coord);
- *     // out is now '47° 59′ 0″ N 7° 51′ 0″ E'
+ * Creates a new 3 element vector initialized with the value from the given
+ * array.
  *
- * @param {ol.Coordinate|undefined} coordinate Coordinate.
- * @return {string} Hemisphere, degrees, minutes and seconds.
- * @api stable
+ * @deprecated Use createFloat32FromArray.
+ * @param {goog.vec.Vec3.Vec3Like} vec The source 3 element array.
+ * @return {!goog.vec.Vec3.Type} The new 3 element array.
  */
-ol.coordinate.toStringHDMS = function(coordinate) {
-  if (goog.isDef(coordinate)) {
-    return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS') + ' ' +
-        ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW');
-  } else {
-    return '';
-  }
+goog.vec.Vec3.createFromArray = function(vec) {
+  var newVec = goog.vec.Vec3.create();
+  goog.vec.Vec3.setFromArray(newVec, vec);
+  return newVec;
 };
 
 
 /**
- * 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'
+ * Creates a new 3 element vector initialized with the supplied values.
  *
- * @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
+ * @deprecated Use createFloat32FromValues.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.Vec3.Type} The new vector.
  */
-ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
-  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
+goog.vec.Vec3.createFromValues = function(v0, v1, v2) {
+  var vec = goog.vec.Vec3.create();
+  goog.vec.Vec3.setFromValues(vec, v0, v1, v2);
+  return vec;
 };
 
 
 /**
- * Create an ol.Coordinate from an Array and take into account axis order.
- *
- * Examples:
- *
- *     var northCoord = ol.coordinate.fromProjectedArray([1, 2], 'n');
- *     // northCoord is now [2, 1]
- *
- *     var eastCoord = ol.coordinate.fromProjectedArray([1, 2], 'e');
- *     // eastCoord is now [1, 2]
+ * Creates a clone of the given 3 element vector.
  *
- * @param {Array} array The array with coordinates.
- * @param {string} axis the axis info.
- * @return {ol.Coordinate} The coordinate created.
+ * @deprecated Use cloneFloat32.
+ * @param {goog.vec.Vec3.Vec3Like} vec The source 3 element vector.
+ * @return {!goog.vec.Vec3.Type} The new cloned vector.
  */
-ol.coordinate.fromProjectedArray = function(array, axis) {
-  var firstAxis = axis.charAt(0);
-  if (firstAxis === 'n' || firstAxis === 's') {
-    return [array[1], array[0]];
-  } else {
-    return array;
-  }
+goog.vec.Vec3.clone = function(vec) {
+  var newVec = goog.vec.Vec3.create();
+  goog.vec.Vec3.setFromArray(newVec, vec);
+  return newVec;
 };
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
 
 /**
- * @fileoverview Supplies a Float32Array implementation that implements
- *     most of the Float32Array spec and that can be used when a built-in
- *     implementation is not available.
- *
- *     Note that if no existing Float32Array implementation is found then
- *     this class and all its public properties are exported as Float32Array.
- *
- *     Adding support for the other TypedArray classes here does not make sense
- *     since this vector math library only needs Float32Array.
- *
- */
-goog.provide('goog.vec.Float32Array');
-
-
-
-/**
- * Constructs a new Float32Array. The new array is initialized to all zeros.
- *
- * @param {goog.vec.Float32Array|Array|ArrayBuffer|number} p0
- *     The length of the array, or an array to initialize the contents of the
- *     new Float32Array.
- * @constructor
- * @final
- */
-goog.vec.Float32Array = function(p0) {
-  this.length = /** @type {number} */ (p0.length || p0);
-  for (var i = 0; i < this.length; i++) {
-    this[i] = p0[i] || 0;
-  }
-};
-
-
-/**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
- *
- * @type {number}
- */
-goog.vec.Float32Array.BYTES_PER_ELEMENT = 4;
-
-
-/**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
- *
- * @type {number}
- */
-goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT = 4;
-
-
-/**
- * Sets elements of the array.
- * @param {Array.<number>|Float32Array} values The array of values.
- * @param {number=} opt_offset The offset in this array to start.
- */
-goog.vec.Float32Array.prototype.set = function(values, opt_offset) {
-  opt_offset = opt_offset || 0;
-  for (var i = 0; i < values.length && opt_offset + i < this.length; i++) {
-    this[opt_offset + i] = values[i];
-  }
-};
-
-
-/**
- * Creates a string representation of this array.
- * @return {string} The string version of this array.
- * @override
- */
-goog.vec.Float32Array.prototype.toString = Array.prototype.join;
-
-
-/**
- * Note that we cannot implement the subarray() or (deprecated) slice()
- * methods properly since doing so would require being able to overload
- * the [] operator which is not possible in javascript.  So we leave
- * them unimplemented.  Any attempt to call these methods will just result
- * in a javascript error since we leave them undefined.
- */
-
-
-/**
- * If no existing Float32Array implementation is found then we export
- * goog.vec.Float32Array as Float32Array.
- */
-if (typeof Float32Array == 'undefined') {
-  goog.exportProperty(goog.vec.Float32Array, 'BYTES_PER_ELEMENT',
-                      goog.vec.Float32Array.BYTES_PER_ELEMENT);
-  goog.exportProperty(goog.vec.Float32Array.prototype, 'BYTES_PER_ELEMENT',
-                      goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT);
-  goog.exportProperty(goog.vec.Float32Array.prototype, 'set',
-                      goog.vec.Float32Array.prototype.set);
-  goog.exportProperty(goog.vec.Float32Array.prototype, 'toString',
-                      goog.vec.Float32Array.prototype.toString);
-  goog.exportSymbol('Float32Array', goog.vec.Float32Array);
-}
-
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-/**
- * @fileoverview Supplies a Float64Array implementation that implements
- * most of the Float64Array spec and that can be used when a built-in
- * implementation is not available.
- *
- * Note that if no existing Float64Array implementation is found then this
- * class and all its public properties are exported as Float64Array.
- *
- * Adding support for the other TypedArray classes here does not make sense
- * since this vector math library only needs Float32Array and Float64Array.
- *
- */
-goog.provide('goog.vec.Float64Array');
-
-
-
-/**
- * Constructs a new Float64Array. The new array is initialized to all zeros.
- *
- * @param {goog.vec.Float64Array|Array|ArrayBuffer|number} p0
- *     The length of the array, or an array to initialize the contents of the
- *     new Float64Array.
- * @constructor
- * @final
- */
-goog.vec.Float64Array = function(p0) {
-  this.length = /** @type {number} */ (p0.length || p0);
-  for (var i = 0; i < this.length; i++) {
-    this[i] = p0[i] || 0;
-  }
-};
-
-
-/**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
- *
- * @type {number}
- */
-goog.vec.Float64Array.BYTES_PER_ELEMENT = 8;
-
-
-/**
- * The number of bytes in an element (as defined by the Typed Array
- * specification).
- *
- * @type {number}
- */
-goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT = 8;
-
-
-/**
- * Sets elements of the array.
- * @param {Array.<number>|Float64Array} values The array of values.
- * @param {number=} opt_offset The offset in this array to start.
- */
-goog.vec.Float64Array.prototype.set = function(values, opt_offset) {
-  opt_offset = opt_offset || 0;
-  for (var i = 0; i < values.length && opt_offset + i < this.length; i++) {
-    this[opt_offset + i] = values[i];
-  }
-};
-
-
-/**
- * Creates a string representation of this array.
- * @return {string} The string version of this array.
- * @override
- */
-goog.vec.Float64Array.prototype.toString = Array.prototype.join;
-
-
-/**
- * Note that we cannot implement the subarray() or (deprecated) slice()
- * methods properly since doing so would require being able to overload
- * the [] operator which is not possible in javascript.  So we leave
- * them unimplemented.  Any attempt to call these methods will just result
- * in a javascript error since we leave them undefined.
- */
-
-
-/**
- * If no existing Float64Array implementation is found then we export
- * goog.vec.Float64Array as Float64Array.
- */
-if (typeof Float64Array == 'undefined') {
-  try {
-    goog.exportProperty(goog.vec.Float64Array, 'BYTES_PER_ELEMENT',
-                        goog.vec.Float64Array.BYTES_PER_ELEMENT);
-  } catch (float64ArrayError) {
-    // Do nothing.  This code is in place to fix b/7225850, in which an error
-    // is incorrectly thrown for Google TV on an old Chrome.
-    // TODO(user): remove after that version is retired.
-  }
-
-  goog.exportProperty(goog.vec.Float64Array.prototype, 'BYTES_PER_ELEMENT',
-                      goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT);
-  goog.exportProperty(goog.vec.Float64Array.prototype, 'set',
-                      goog.vec.Float64Array.prototype.set);
-  goog.exportProperty(goog.vec.Float64Array.prototype, 'toString',
-                      goog.vec.Float64Array.prototype.toString);
-  goog.exportSymbol('Float64Array', goog.vec.Float64Array);
-}
-
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-/**
- * @fileoverview Supplies global data types and constants for the vector math
- *     library.
- */
-goog.provide('goog.vec');
-goog.provide('goog.vec.AnyType');
-goog.provide('goog.vec.ArrayType');
-goog.provide('goog.vec.Float32');
-goog.provide('goog.vec.Float64');
-goog.provide('goog.vec.Number');
-
-
-/**
- * On platforms that don't have native Float32Array or Float64Array support we
- * use a javascript implementation so that this math library can be used on all
- * platforms.
- * @suppress {extraRequire}
- */
-goog.require('goog.vec.Float32Array');
-/** @suppress {extraRequire} */
-goog.require('goog.vec.Float64Array');
-
-// All vector and matrix operations are based upon arrays of numbers using
-// either Float32Array, Float64Array, or a standard Javascript Array of
-// Numbers.
-
-
-/** @typedef {!Float32Array} */
-goog.vec.Float32;
-
-
-/** @typedef {!Float64Array} */
-goog.vec.Float64;
-
-
-/** @typedef {!Array.<number>} */
-goog.vec.Number;
-
-
-/** @typedef {!goog.vec.Float32|!goog.vec.Float64|!goog.vec.Number} */
-goog.vec.AnyType;
-
-
-/**
- * @deprecated Use AnyType.
- * @typedef {!Float32Array|!Array.<number>}
- */
-goog.vec.ArrayType;
-
-
-/**
- * For graphics work, 6 decimal places of accuracy are typically all that is
- * required.
- *
- * @type {number}
- * @const
- */
-goog.vec.EPSILON = 1e-6;
-
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-/**
- * @fileoverview Supplies 3 element vectors that are compatible with WebGL.
- * Each element is a float32 since that is typically the desired size of a
- * 3-vector in the GPU.  The API is structured to avoid unnecessary memory
- * allocations.  The last parameter will typically be the output vector and
- * an object can be both an input and output parameter to all methods except
- * where noted.
- *
- */
-goog.provide('goog.vec.Vec3');
-
-/** @suppress {extraRequire} */
-goog.require('goog.vec');
-
-/** @typedef {goog.vec.Float32} */ goog.vec.Vec3.Float32;
-/** @typedef {goog.vec.Float64} */ goog.vec.Vec3.Float64;
-/** @typedef {goog.vec.Number} */ goog.vec.Vec3.Number;
-/** @typedef {goog.vec.AnyType} */ goog.vec.Vec3.AnyType;
-
-// The following two types are deprecated - use the above types instead.
-/** @typedef {Float32Array} */ goog.vec.Vec3.Type;
-/** @typedef {goog.vec.ArrayType} */ goog.vec.Vec3.Vec3Like;
-
-
-/**
- * Creates a 3 element vector of Float32. The array is initialized to zero.
- *
- * @return {!goog.vec.Vec3.Float32} The new 3 element array.
- */
-goog.vec.Vec3.createFloat32 = function() {
-  return new Float32Array(3);
-};
-
-
-/**
- * Creates a 3 element vector of Float64. The array is initialized to zero.
- *
- * @return {!goog.vec.Vec3.Float64} The new 3 element array.
- */
-goog.vec.Vec3.createFloat64 = function() {
-  return new Float64Array(3);
-};
-
-
-/**
- * Creates a 3 element vector of Number. The array is initialized to zero.
- *
- * @return {!goog.vec.Vec3.Number} The new 3 element array.
- */
-goog.vec.Vec3.createNumber = function() {
-  var a = new Array(3);
-  goog.vec.Vec3.setFromValues(a, 0, 0, 0);
-  return a;
-};
-
-
-/**
- * Creates a 3 element vector of Float32Array. The array is initialized to zero.
- *
- * @deprecated Use createFloat32.
- * @return {!goog.vec.Vec3.Type} The new 3 element array.
- */
-goog.vec.Vec3.create = function() {
-  return new Float32Array(3);
-};
-
-
-/**
- * Creates a new 3 element FLoat32 vector initialized with the value from the
- * given array.
- *
- * @param {goog.vec.Vec3.AnyType} vec The source 3 element array.
- * @return {!goog.vec.Vec3.Float32} The new 3 element array.
- */
-goog.vec.Vec3.createFloat32FromArray = function(vec) {
-  var newVec = goog.vec.Vec3.createFloat32();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
-};
-
-
-/**
- * Creates a new 3 element Float32 vector initialized with the supplied values.
- *
- * @param {number} v0 The value for element at index 0.
- * @param {number} v1 The value for element at index 1.
- * @param {number} v2 The value for element at index 2.
- * @return {!goog.vec.Vec3.Float32} The new vector.
- */
-goog.vec.Vec3.createFloat32FromValues = function(v0, v1, v2) {
-  var a = goog.vec.Vec3.createFloat32();
-  goog.vec.Vec3.setFromValues(a, v0, v1, v2);
-  return a;
-};
-
-
-/**
- * Creates a clone of the given 3 element Float32 vector.
- *
- * @param {goog.vec.Vec3.Float32} vec The source 3 element vector.
- * @return {!goog.vec.Vec3.Float32} The new cloned vector.
- */
-goog.vec.Vec3.cloneFloat32 = goog.vec.Vec3.createFloat32FromArray;
-
-
-/**
- * Creates a new 3 element Float64 vector initialized with the value from the
- * given array.
- *
- * @param {goog.vec.Vec3.AnyType} vec The source 3 element array.
- * @return {!goog.vec.Vec3.Float64} The new 3 element array.
- */
-goog.vec.Vec3.createFloat64FromArray = function(vec) {
-  var newVec = goog.vec.Vec3.createFloat64();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
-};
-
-
-/**
-* Creates a new 3 element Float64 vector initialized with the supplied values.
-*
-* @param {number} v0 The value for element at index 0.
-* @param {number} v1 The value for element at index 1.
-* @param {number} v2 The value for element at index 2.
-* @return {!goog.vec.Vec3.Float64} The new vector.
-*/
-goog.vec.Vec3.createFloat64FromValues = function(v0, v1, v2) {
-  var vec = goog.vec.Vec3.createFloat64();
-  goog.vec.Vec3.setFromValues(vec, v0, v1, v2);
-  return vec;
-};
-
-
-/**
- * Creates a clone of the given 3 element vector.
- *
- * @param {goog.vec.Vec3.Float64} vec The source 3 element vector.
- * @return {!goog.vec.Vec3.Float64} The new cloned vector.
- */
-goog.vec.Vec3.cloneFloat64 = goog.vec.Vec3.createFloat64FromArray;
-
-
-/**
- * Creates a new 3 element vector initialized with the value from the given
- * array.
- *
- * @deprecated Use createFloat32FromArray.
- * @param {goog.vec.Vec3.Vec3Like} vec The source 3 element array.
- * @return {!goog.vec.Vec3.Type} The new 3 element array.
- */
-goog.vec.Vec3.createFromArray = function(vec) {
-  var newVec = goog.vec.Vec3.create();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
-};
-
-
-/**
- * Creates a new 3 element vector initialized with the supplied values.
- *
- * @deprecated Use createFloat32FromValues.
- * @param {number} v0 The value for element at index 0.
- * @param {number} v1 The value for element at index 1.
- * @param {number} v2 The value for element at index 2.
- * @return {!goog.vec.Vec3.Type} The new vector.
- */
-goog.vec.Vec3.createFromValues = function(v0, v1, v2) {
-  var vec = goog.vec.Vec3.create();
-  goog.vec.Vec3.setFromValues(vec, v0, v1, v2);
-  return vec;
-};
-
-
-/**
- * Creates a clone of the given 3 element vector.
- *
- * @deprecated Use cloneFloat32.
- * @param {goog.vec.Vec3.Vec3Like} vec The source 3 element vector.
- * @return {!goog.vec.Vec3.Type} The new cloned vector.
- */
-goog.vec.Vec3.clone = function(vec) {
-  var newVec = goog.vec.Vec3.create();
-  goog.vec.Vec3.setFromArray(newVec, vec);
-  return newVec;
-};
-
-
-/**
- * Initializes the vector with the given values.
+ * Initializes the vector with the given values.
  *
  * @param {goog.vec.Vec3.AnyType} vec The vector to receive the values.
  * @param {number} v0 The value for element at index 0.
@@ -16293,7 +17387,7 @@ goog.vec.Mat4.getTranslation = function(mat, translation) {
 
 
 /**
- * @type {!Array.<!goog.vec.Vec3.Type>}
+ * @type {!Array<!goog.vec.Vec3.Type>}
  * @private
  */
 goog.vec.Mat4.tmpVec3_ = [
@@ -16303,7 +17397,7 @@ goog.vec.Mat4.tmpVec3_ = [
 
 
 /**
- * @type {!Array.<!goog.vec.Vec4.Type>}
+ * @type {!Array<!goog.vec.Vec4.Type>}
  * @private
  */
 goog.vec.Mat4.tmpVec4_ = [
@@ -16314,7 +17408,7 @@ goog.vec.Mat4.tmpVec4_ = [
 
 
 /**
- * @type {!Array.<!goog.vec.Mat4.Type>}
+ * @type {!Array<!goog.vec.Mat4.Type>}
  * @private
  */
 goog.vec.Mat4.tmpMat4_ = [
@@ -16496,8 +17590,7 @@ ol.extent.closestSquaredDistanceXY = function(extent, x, y) {
  * @api stable
  */
 ol.extent.containsCoordinate = function(extent, coordinate) {
-  return extent[0] <= coordinate[0] && coordinate[0] <= extent[2] &&
-      extent[1] <= coordinate[1] && coordinate[1] <= extent[3];
+  return ol.extent.containsXY(extent, coordinate[0], coordinate[1]);
 };
 
 
@@ -16515,6 +17608,20 @@ ol.extent.containsExtent = function(extent1, extent2) {
 };
 
 
+/**
+ * Checks 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} Contains.
+ * @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.
@@ -16763,6 +17870,38 @@ ol.extent.extendXY = function(extent, x, 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.getBottomRight(extent));
+  if (val) {
+    return val;
+  }
+  return false;
+};
+
+
 /**
  * @param {ol.Extent} extent Extent.
  * @return {number} Area.
@@ -16900,6 +18039,7 @@ ol.extent.getIntersectionArea = function(extent1, extent2) {
  * @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 = goog.isDef(opt_extent) ?
@@ -17066,7 +18206,7 @@ ol.extent.scaleFromCenter = function(extent, value) {
  * @param {ol.Coordinate} end Segment end coordinate.
  * @return {boolean} The segment intersects the extent.
  */
-ol.extent.segmentIntersects = function(extent, start, end) {
+ol.extent.intersectsSegment = function(extent, start, end) {
   var intersects = false;
   var startRel = ol.extent.coordinateRelationship(extent, start);
   var endRel = ol.extent.coordinateRelationship(extent, end);
@@ -17396,12 +18536,13 @@ ol.Sphere.prototype.midpoint = function(c1, c2) {
 
 
 /**
- * Returns the coordinate at the given distance and bearing from c.
+ * Returns the coordinate at the given distance and bearing from `c1`.
  *
- * @param {ol.Coordinate} c1 Coordinate.
- * @param {number} distance Distance.
- * @param {number} bearing Bearing.
- * @return {ol.Coordinate} Coordinate.
+ * @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 = goog.math.toRadians(c1[1]);
@@ -17682,7 +18823,8 @@ ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) {
  * @return {number} Point resolution.
  */
 ol.proj.Projection.prototype.getPointResolution = function(resolution, point) {
-  if (this.getUnits() == ol.proj.Units.DEGREES) {
+  var units = this.getUnits();
+  if (units == ol.proj.Units.DEGREES) {
     return resolution;
   } else {
     // Estimate point resolution by transforming the center pixel to EPSG:4326,
@@ -17702,10 +18844,9 @@ ol.proj.Projection.prototype.getPointResolution = function(resolution, point) {
     var height = ol.sphere.NORMAL.haversineDistance(
         vertices.slice(4, 6), vertices.slice(6, 8));
     var pointResolution = (width + height) / 2;
-    if (this.getUnits() == ol.proj.Units.FEET) {
-      // The radius of the normal sphere is defined in meters, so we must
-      // convert back to feet.
-      pointResolution /= 0.3048;
+    var metersPerUnit = this.getMetersPerUnit();
+    if (goog.isDef(metersPerUnit)) {
+      pointResolution /= metersPerUnit;
     }
     return pointResolution;
   }
@@ -18108,7 +19249,7 @@ ol.proj.cloneTransform = function(input, opt_output, opt_dimension) {
  */
 ol.proj.transform = function(coordinate, source, destination) {
   var transformFn = ol.proj.getTransform(source, destination);
-  return transformFn(coordinate);
+  return transformFn(coordinate, undefined, coordinate.length);
 };
 
 
@@ -18222,14 +19363,19 @@ ol.ViewHint = {
  * the "next" resolution. And releasing the fingers after pinch-zooming
  * snaps to the closest resolution (with an animation).
  *
- * So the *resolution constraint* snaps to specific resolutions. It is
+ * 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* is currently not configurable. It snaps the
- * rotation value to zero when approaching the horizontal.
+ * 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}
@@ -18599,7 +19745,7 @@ ol.View.prototype.fitExtent = function(extent, size) {
  * Take care on the map angle.
  * @param {ol.geom.SimpleGeometry} geometry Geometry.
  * @param {ol.Size} size Box pixel size.
- * @param {olx.View.fitGeometryOptions=} opt_options Options.
+ * @param {olx.view.FitGeometryOptions=} opt_options Options.
  * @api
  */
 ol.View.prototype.fitGeometry = function(geometry, size, opt_options) {
@@ -18928,6 +20074,7 @@ ol.View.createRotationConstraint_ = function(options) {
 /**
  * @fileoverview Easing functions for animations.
  *
+ * @author arv@google.com (Erik Arvidsson)
  */
 
 goog.provide('goog.fx.easing');
@@ -19286,15 +20433,15 @@ ol.tilecoord.createFromString = function(str) {
  * @param {number} z Z.
  * @param {number} x X.
  * @param {number} y Y.
- * @param {ol.TileCoord|undefined} tileCoord Tile coordinate.
+ * @param {ol.TileCoord=} opt_tileCoord Tile coordinate.
  * @return {ol.TileCoord} Tile coordinate.
  */
-ol.tilecoord.createOrUpdate = function(z, x, y, tileCoord) {
-  if (goog.isDef(tileCoord)) {
-    tileCoord[0] = z;
-    tileCoord[1] = x;
-    tileCoord[2] = y;
-    return tileCoord;
+ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) {
+  if (goog.isDef(opt_tileCoord)) {
+    opt_tileCoord[0] = z;
+    opt_tileCoord[1] = x;
+    opt_tileCoord[2] = y;
+    return opt_tileCoord;
   } else {
     return [z, x, y];
   }
@@ -19556,7 +20703,7 @@ goog.require('ol.TileRange');
  *           html: 'All maps &copy; ' +
  *               '<a href="http://www.opencyclemap.org/">OpenCycleMap</a>'
  *         }),
- *         ol.source.OSM.DATA_ATTRIBUTION
+ *         ol.source.OSM.ATTRIBUTION
  *       ],
  *     ..
  *
@@ -19848,7 +20995,7 @@ goog.dom.TagName = {
   WBR: 'WBR'
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+// Copyright 2014 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -19863,260 +21010,267 @@ goog.dom.TagName = {
 // limitations under the License.
 
 /**
- * @fileoverview A utility class for representing two-dimensional positions.
+ * @fileoverview Utilities for HTML element tag names.
  */
+goog.provide('goog.dom.tags');
 
-
-goog.provide('goog.math.Coordinate');
-
-goog.require('goog.math');
-
+goog.require('goog.object');
 
 
 /**
- * Class for representing coordinates and positions.
- * @param {number=} opt_x Left, defaults to 0.
- * @param {number=} opt_y Top, defaults to 0.
- * @constructor
+ * The void elements specified by
+ * http://www.w3.org/TR/html-markup/syntax.html#void-elements.
+ * @const
+ * @type {!Object}
+ * @private
  */
-goog.math.Coordinate = function(opt_x, opt_y) {
-  /**
-   * X-value
-   * @type {number}
-   */
-  this.x = goog.isDef(opt_x) ? opt_x : 0;
-
-  /**
-   * Y-value
-   * @type {number}
-   */
-  this.y = goog.isDef(opt_y) ? opt_y : 0;
-};
+goog.dom.tags.VOID_TAGS_ = goog.object.createSet(('area,base,br,col,command,' +
+    'embed,hr,img,input,keygen,link,meta,param,source,track,wbr').split(','));
 
 
 /**
- * Returns a new copy of the coordinate.
- * @return {!goog.math.Coordinate} A clone of this coordinate.
+ * Checks whether the tag is void (with no contents allowed and no legal end
+ * tag), for example 'br'.
+ * @param {string} tagName The tag name in lower case.
+ * @return {boolean}
  */
-goog.math.Coordinate.prototype.clone = function() {
-  return new goog.math.Coordinate(this.x, this.y);
+goog.dom.tags.isVoidTag = function(tagName) {
+  return goog.dom.tags.VOID_TAGS_[tagName] === true;
 };
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-if (goog.DEBUG) {
-  /**
-   * Returns a nice string representing the coordinate.
-   * @return {string} In the form (50, 73).
-   * @override
-   */
-  goog.math.Coordinate.prototype.toString = function() {
-    return '(' + this.x + ', ' + this.y + ')';
-  };
-}
-
+goog.provide('goog.string.TypedString');
 
-/**
- * Compares coordinates for equality.
- * @param {goog.math.Coordinate} a A Coordinate.
- * @param {goog.math.Coordinate} b A Coordinate.
- * @return {boolean} True iff the coordinates are equal, or if both are null.
- */
-goog.math.Coordinate.equals = function(a, b) {
-  if (a == b) {
-    return true;
-  }
-  if (!a || !b) {
-    return false;
-  }
-  return a.x == b.x && a.y == b.y;
-};
 
 
 /**
- * Returns the distance between two coordinates.
- * @param {!goog.math.Coordinate} a A Coordinate.
- * @param {!goog.math.Coordinate} b A Coordinate.
- * @return {number} The distance between {@code a} and {@code b}.
+ * Wrapper for strings that conform to a data type or language.
+ *
+ * Implementations of this interface are wrappers for strings, and typically
+ * associate a type contract with the wrapped string.  Concrete implementations
+ * of this interface may choose to implement additional run-time type checking,
+ * see for example {@code goog.html.SafeHtml}. If available, client code that
+ * needs to ensure type membership of an object should use the type's function
+ * to assert type membership, such as {@code goog.html.SafeHtml.unwrap}.
+ * @interface
  */
-goog.math.Coordinate.distance = function(a, b) {
-  var dx = a.x - b.x;
-  var dy = a.y - b.y;
-  return Math.sqrt(dx * dx + dy * dy);
-};
+goog.string.TypedString = function() {};
 
 
 /**
- * Returns the magnitude of a coordinate.
- * @param {!goog.math.Coordinate} a A Coordinate.
- * @return {number} The distance between the origin and {@code a}.
+ * Interface marker of the TypedString interface.
+ *
+ * This property can be used to determine at runtime whether or not an object
+ * implements this interface.  All implementations of this interface set this
+ * property to {@code true}.
+ * @type {boolean}
  */
-goog.math.Coordinate.magnitude = function(a) {
-  return Math.sqrt(a.x * a.x + a.y * a.y);
-};
+goog.string.TypedString.prototype.implementsGoogStringTypedString;
 
 
 /**
- * Returns the angle from the origin to a coordinate.
- * @param {!goog.math.Coordinate} a A Coordinate.
- * @return {number} The angle, in degrees, clockwise from the positive X
- *     axis to {@code a}.
+ * Retrieves this wrapped string's value.
+ * @return {!string} The wrapped string's value.
  */
-goog.math.Coordinate.azimuth = function(a) {
-  return goog.math.angle(0, 0, a.x, a.y);
-};
+goog.string.TypedString.prototype.getTypedStringValue;
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-/**
- * Returns the squared distance between two coordinates. Squared distances can
- * be used for comparisons when the actual value is not required.
- *
- * Performance note: eliminating the square root is an optimization often used
- * in lower-level languages, but the speed difference is not nearly as
- * pronounced in JavaScript (only a few percent.)
- *
- * @param {!goog.math.Coordinate} a A Coordinate.
- * @param {!goog.math.Coordinate} b A Coordinate.
- * @return {number} The squared distance between {@code a} and {@code b}.
- */
-goog.math.Coordinate.squaredDistance = function(a, b) {
-  var dx = a.x - b.x;
-  var dy = a.y - b.y;
-  return dx * dx + dy * dy;
-};
+goog.provide('goog.string.Const');
 
+goog.require('goog.asserts');
+goog.require('goog.string.TypedString');
 
-/**
- * Returns the difference between two coordinates as a new
- * goog.math.Coordinate.
- * @param {!goog.math.Coordinate} a A Coordinate.
- * @param {!goog.math.Coordinate} b A Coordinate.
- * @return {!goog.math.Coordinate} A Coordinate representing the difference
- *     between {@code a} and {@code b}.
- */
-goog.math.Coordinate.difference = function(a, b) {
-  return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
-};
 
 
 /**
- * Returns the sum of two coordinates as a new goog.math.Coordinate.
- * @param {!goog.math.Coordinate} a A Coordinate.
- * @param {!goog.math.Coordinate} b A Coordinate.
- * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
- *     coordinates.
+ * Wrapper for compile-time-constant strings.
+ *
+ * Const is a wrapper for strings that can only be created from program
+ * constants (i.e., string literals).  This property relies on a custom Closure
+ * compiler check that {@code goog.string.Const.from} is only invoked on
+ * compile-time-constant expressions.
+ *
+ * Const is useful in APIs whose correct and secure use requires that certain
+ * arguments are not attacker controlled: Compile-time constants are inherently
+ * under the control of the application and not under control of external
+ * attackers, and hence are safe to use in such contexts.
+ *
+ * Instances of this type must be created via its factory method
+ * {@code goog.string.Const.from} and not by invoking its constructor.  The
+ * constructor intentionally takes no parameters and the type is immutable;
+ * hence only a default instance corresponding to the empty string can be
+ * obtained via constructor invocation.
+ *
+ * @see goog.string.Const#from
+ * @constructor
+ * @final
+ * @struct
+ * @implements {goog.string.TypedString}
  */
-goog.math.Coordinate.sum = function(a, b) {
-  return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
-};
-
+goog.string.Const = function() {
+  /**
+   * The wrapped value of this Const object.  The field has a purposely ugly
+   * name to make (non-compiled) code that attempts to directly access this
+   * field stand out.
+   * @private {string}
+   */
+  this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = '';
 
-/**
- * Rounds the x and y fields to the next larger integer values.
- * @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
- */
-goog.math.Coordinate.prototype.ceil = function() {
-  this.x = Math.ceil(this.x);
-  this.y = Math.ceil(this.y);
-  return this;
+  /**
+   * A type marker used to implement additional run-time type checking.
+   * @see goog.string.Const#unwrap
+   * @const
+   * @private
+   */
+  this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ =
+      goog.string.Const.TYPE_MARKER_;
 };
 
 
 /**
- * Rounds the x and y fields to the next smaller integer values.
- * @return {!goog.math.Coordinate} This coordinate with floored fields.
+ * @override
+ * @const
  */
-goog.math.Coordinate.prototype.floor = function() {
-  this.x = Math.floor(this.x);
-  this.y = Math.floor(this.y);
-  return this;
-};
+goog.string.Const.prototype.implementsGoogStringTypedString = true;
 
 
 /**
- * Rounds the x and y fields to the nearest integer values.
- * @return {!goog.math.Coordinate} This coordinate with rounded fields.
+ * Returns this Const's value a string.
+ *
+ * IMPORTANT: In code where it is security-relevant that an object's type is
+ * indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap}
+ * instead of this method.
+ *
+ * @see goog.string.Const#unwrap
+ * @override
  */
-goog.math.Coordinate.prototype.round = function() {
-  this.x = Math.round(this.x);
-  this.y = Math.round(this.y);
-  return this;
+goog.string.Const.prototype.getTypedStringValue = function() {
+  return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_;
 };
 
 
 /**
- * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
- * is given, then the x and y values are translated by the coordinate's x and y.
- * Otherwise, x and y are translated by {@code tx} and {@code opt_ty}
- * respectively.
- * @param {number|goog.math.Coordinate} tx The value to translate x by or the
- *     the coordinate to translate this coordinate by.
- * @param {number=} opt_ty The value to translate y by.
- * @return {!goog.math.Coordinate} This coordinate after translating.
+ * Returns a debug-string representation of this value.
+ *
+ * To obtain the actual string value wrapped inside an object of this type,
+ * use {@code goog.string.Const.unwrap}.
+ *
+ * @see goog.string.Const#unwrap
+ * @override
  */
-goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
-  if (tx instanceof goog.math.Coordinate) {
-    this.x += tx.x;
-    this.y += tx.y;
+goog.string.Const.prototype.toString = function() {
+  return 'Const{' +
+         this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ +
+         '}';
+};
+
+
+/**
+ * Performs a runtime check that the provided object is indeed an instance
+ * of {@code goog.string.Const}, and returns its value.
+ * @param {!goog.string.Const} stringConst The object to extract from.
+ * @return {string} The Const object's contained string, unless the run-time
+ *     type check fails. In that case, {@code unwrap} returns an innocuous
+ *     string, or, if assertions are enabled, throws
+ *     {@code goog.asserts.AssertionError}.
+ */
+goog.string.Const.unwrap = function(stringConst) {
+  // Perform additional run-time type-checking to ensure that stringConst is
+  // indeed an instance of the expected type.  This provides some additional
+  // protection against security bugs due to application code that disables type
+  // checks.
+  if (stringConst instanceof goog.string.Const &&
+      stringConst.constructor === goog.string.Const &&
+      stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ ===
+          goog.string.Const.TYPE_MARKER_) {
+    return stringConst.
+        stringConstValueWithSecurityContract__googStringSecurityPrivate_;
   } else {
-    this.x += tx;
-    if (goog.isNumber(opt_ty)) {
-      this.y += opt_ty;
-    }
+    goog.asserts.fail('expected object of type Const, got \'' +
+                      stringConst + '\'');
+    return 'type_error:Const';
   }
-  return this;
 };
 
 
 /**
- * Scales this coordinate by the given scale factors. The x and y values are
- * scaled by {@code sx} and {@code opt_sy} respectively.  If {@code opt_sy}
- * is not given, then {@code sx} is used for both x and y.
- * @param {number} sx The scale factor to use for the x dimension.
- * @param {number=} opt_sy The scale factor to use for the y dimension.
- * @return {!goog.math.Coordinate} This coordinate after scaling.
+ * Creates a Const object from a compile-time constant string.
+ *
+ * It is illegal to invoke this function on an expression whose
+ * compile-time-contant value cannot be determined by the Closure compiler.
+ *
+ * Correct invocations include,
+ * <pre>
+ *   var s = goog.string.Const.from('hello');
+ *   var t = goog.string.Const.from('hello' + 'world');
+ * </pre>
+ *
+ * In contrast, the following are illegal:
+ * <pre>
+ *   var s = goog.string.Const.from(getHello());
+ *   var t = goog.string.Const.from('hello' + world);
+ * </pre>
+ *
+ * TODO(user): Compile-time checks that this function is only called
+ * with compile-time constant expressions.
+ *
+ * @param {string} s A constant string from which to create a Const.
+ * @return {!goog.string.Const} A Const object initialized to stringConst.
  */
-goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
-  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
-  this.x *= sx;
-  this.y *= sy;
-  return this;
+goog.string.Const.from = function(s) {
+  return goog.string.Const.create__googStringSecurityPrivate_(s);
 };
 
 
 /**
- * Rotates this coordinate clockwise about the origin (or, optionally, the given
- * center) by the given angle, in radians.
- * @param {number} radians The angle by which to rotate this coordinate
- *     clockwise about the given center, in radians.
- * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
- *     to (0, 0) if not given.
+ * Type marker for the Const type, used to implement additional run-time
+ * type checking.
+ * @const
+ * @private
  */
-goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) {
-  var center = opt_center || new goog.math.Coordinate(0, 0);
-
-  var x = this.x;
-  var y = this.y;
-  var cos = Math.cos(radians);
-  var sin = Math.sin(radians);
-
-  this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
-  this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
-};
+goog.string.Const.TYPE_MARKER_ = {};
 
 
 /**
- * Rotates this coordinate clockwise about the origin (or, optionally, the given
- * center) by the given angle, in degrees.
- * @param {number} degrees The angle by which to rotate this coordinate
- *     clockwise about the given center, in degrees.
- * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
- *     to (0, 0) if not given.
+ * Utility method to create Const instances.
+ * @param {string} s The string to initialize the Const object with.
+ * @return {!goog.string.Const} The initialized Const object.
+ * @private
  */
-goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
-  this.rotateRadians(goog.math.toRadians(degrees), opt_center);
+goog.string.Const.create__googStringSecurityPrivate_ = function(s) {
+  var stringConst = new goog.string.Const();
+  stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ =
+      s;
+  return stringConst;
 };
 
-// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+// Copyright 2014 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -20131,199 +21285,386 @@ goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
 // limitations under the License.
 
 /**
- * @fileoverview A utility class for representing two-dimensional sizes.
+ * @fileoverview The SafeStyle type and its builders.
+ *
+ * TODO(user): Link to document stating type contract.
  */
 
+goog.provide('goog.html.SafeStyle');
 
-goog.provide('goog.math.Size');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.string');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
 
 
 
 /**
- * Class for representing sizes consisting of a width and height. Undefined
- * width and height support is deprecated and results in compiler warning.
- * @param {number} width Width.
- * @param {number} height Height.
+ * A string-like object which represents a sequence of CSS declarations
+ * ({@code propertyName1: propertyvalue1; propertyName2: propertyValue2; ...})
+ * and that carries the security type contract that its value, as a string,
+ * will not cause untrusted script execution (XSS) when evaluated as CSS in a
+ * browser.
+ *
+ * Instances of this type must be created via the factory methods
+ * ({@code goog.html.SafeStyle.create} or
+ * {@code goog.html.SafeStyle.fromConstant}) and not by invoking its
+ * constructor. The constructor intentionally takes no parameters and the type
+ * is immutable; hence only a default instance corresponding to the empty string
+ * can be obtained via constructor invocation.
+ *
+ * A SafeStyle's string representation ({@link #getSafeStyleString()}) can
+ * safely:
+ * <ul>
+ *   <li>Be interpolated as the entire content of a *quoted* HTML style
+ *       attribute, or before already existing properties. The SafeStyle string
+ *       *must be HTML-attribute-escaped* (where " and ' are escaped) before
+ *       interpolation.
+ *   <li>Be interpolated as the entire content of a {}-wrapped block within a
+ *       stylesheet, or before already existing properties. The SafeStyle string
+ *       should not be escaped before interpolation. SafeStyle's contract also
+ *       guarantees that the string will not be able to introduce new properties
+ *       or elide existing ones.
+ *   <li>Be assigned to the style property of a DOM node. The SafeStyle string
+ *       should not be escaped before being assigned to the property.
+ * </ul>
+ *
+ * A SafeStyle may never contain literal angle brackets. Otherwise, it could
+ * be unsafe to place a SafeStyle into a &lt;style&gt; tag (where it can't
+ * be HTML escaped). For example, if the SafeStyle containing
+ * "{@code font: 'foo &lt;style/&gt;&lt;script&gt;evil&lt;/script&gt;'}" were
+ * interpolated within a &lt;style&gt; tag, this would then break out of the
+ * style context into HTML.
+ *
+ * A SafeStyle may contain literal single or double quotes, and as such the
+ * entire style string must be escaped when used in a style attribute (if
+ * this were not the case, the string could contain a matching quote that
+ * would escape from the style attribute).
+ *
+ * Values of this type must be composable, i.e. for any two values
+ * {@code style1} and {@code style2} of this type,
+ * {@code goog.html.SafeStyle.unwrap(style1) +
+ * goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies
+ * the SafeStyle type constraint. This requirement implies that for any value
+ * {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must
+ * not end in a "property value" or "property name" context. For example,
+ * a value of {@code background:url("} or {@code font-} would not satisfy the
+ * SafeStyle contract. This is because concatenating such strings with a
+ * second value that itself does not contain unsafe CSS can result in an
+ * overall string that does. For example, if {@code javascript:evil())"} is
+ * appended to {@code background:url("}, the resulting string may result in
+ * the execution of a malicious script.
+ *
+ * TODO(user): Consider whether we should implement UTF-8 interchange
+ * validity checks and blacklisting of newlines (including Unicode ones) and
+ * other whitespace characters (\t, \f). Document here if so and also update
+ * SafeStyle.fromConstant().
+ *
+ * The following example values comply with this type's contract:
+ * <ul>
+ *   <li><pre>width: 1em;</pre>
+ *   <li><pre>height:1em;</pre>
+ *   <li><pre>width: 1em;height: 1em;</pre>
+ *   <li><pre>background:url('http://url');</pre>
+ * </ul>
+ * In addition, the empty string is safe for use in a CSS attribute.
+ *
+ * The following example values do NOT comply with this type's contract:
+ * <ul>
+ *   <li><pre>background: red</pre> (missing a trailing semi-colon)
+ *   <li><pre>background:</pre> (missing a value and a trailing semi-colon)
+ *   <li><pre>1em</pre> (missing an attribute name, which provides context for
+ *       the value)
+ * </ul>
+ *
+ * @see goog.html.SafeStyle#create
+ * @see goog.html.SafeStyle#fromConstant
+ * @see http://www.w3.org/TR/css3-syntax/
  * @constructor
+ * @final
+ * @struct
+ * @implements {goog.string.TypedString}
  */
-goog.math.Size = function(width, height) {
+goog.html.SafeStyle = function() {
   /**
-   * Width
-   * @type {number}
+   * The contained value of this SafeStyle.  The field has a purposely
+   * ugly name to make (non-compiled) code that attempts to directly access this
+   * field stand out.
+   * @private {string}
    */
-  this.width = width;
+  this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = '';
 
   /**
-   * Height
-   * @type {number}
+   * A type marker used to implement additional run-time type checking.
+   * @see goog.html.SafeStyle#unwrap
+   * @const
+   * @private
    */
-  this.height = height;
+  this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
+      goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
 };
 
 
 /**
- * Compares sizes for equality.
- * @param {goog.math.Size} a A Size.
- * @param {goog.math.Size} b A Size.
- * @return {boolean} True iff the sizes have equal widths and equal
- *     heights, or if both are null.
+ * @override
+ * @const
  */
-goog.math.Size.equals = function(a, b) {
-  if (a == b) {
-    return true;
-  }
-  if (!a || !b) {
-    return false;
-  }
-  return a.width == b.width && a.height == b.height;
-};
+goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true;
 
 
 /**
- * @return {!goog.math.Size} A new copy of the Size.
+ * Type marker for the SafeStyle type, used to implement additional
+ * run-time type checking.
+ * @const
+ * @private
  */
-goog.math.Size.prototype.clone = function() {
-  return new goog.math.Size(this.width, this.height);
-};
-
-
-if (goog.DEBUG) {
-  /**
-   * Returns a nice string representing size.
-   * @return {string} In the form (50 x 73).
-   * @override
-   */
-  goog.math.Size.prototype.toString = function() {
-    return '(' + this.width + ' x ' + this.height + ')';
-  };
-}
+goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
 
 
 /**
- * @return {number} The longer of the two dimensions in the size.
+ * Creates a SafeStyle object from a compile-time constant string.
+ *
+ * {@code style} should be in the format
+ * {@code name: value; [name: value; ...]} and must not have any < or >
+ * characters in it. This is so that SafeStyle's contract is preserved,
+ * allowing the SafeStyle to correctly be interpreted as a sequence of CSS
+ * declarations and without affecting the syntactic structure of any
+ * surrounding CSS and HTML.
+ *
+ * This method performs basic sanity checks on the format of {@code style}
+ * but does not constrain the format of {@code name} and {@code value}, except
+ * for disallowing tag characters.
+ *
+ * @param {!goog.string.Const} style A compile-time-constant string from which
+ *     to create a SafeStyle.
+ * @return {!goog.html.SafeStyle} A SafeStyle object initialized to
+ *     {@code style}.
  */
-goog.math.Size.prototype.getLongest = function() {
-  return Math.max(this.width, this.height);
+goog.html.SafeStyle.fromConstant = function(style) {
+  var styleString = goog.string.Const.unwrap(style);
+  if (styleString.length === 0) {
+    return goog.html.SafeStyle.EMPTY;
+  }
+  goog.html.SafeStyle.checkStyle_(styleString);
+  goog.asserts.assert(goog.string.endsWith(styleString, ';'),
+      'Last character of style string is not \';\': ' + styleString);
+  goog.asserts.assert(goog.string.contains(styleString, ':'),
+      'Style string must contain at least one \':\', to ' +
+      'specify a "name: value" pair: ' + styleString);
+  return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
+      styleString);
 };
 
 
 /**
- * @return {number} The shorter of the two dimensions in the size.
+ * Checks if the style definition is valid.
+ * @param {string} style
+ * @private
  */
-goog.math.Size.prototype.getShortest = function() {
-  return Math.min(this.width, this.height);
+goog.html.SafeStyle.checkStyle_ = function(style) {
+  goog.asserts.assert(!/[<>]/.test(style),
+      'Forbidden characters in style string: ' + style);
 };
 
 
 /**
- * @return {number} The area of the size (width * height).
+ * Returns this SafeStyle's value as a string.
+ *
+ * IMPORTANT: In code where it is security relevant that an object's type is
+ * indeed {@code SafeStyle}, use {@code goog.html.SafeStyle.unwrap} instead of
+ * this method. If in doubt, assume that it's security relevant. In particular,
+ * note that goog.html functions which return a goog.html type do not guarantee
+ * the returned instance is of the right type. For example:
+ *
+ * <pre>
+ * var fakeSafeHtml = new String('fake');
+ * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
+ * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
+ * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
+ * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
+ * // instanceof goog.html.SafeHtml.
+ * </pre>
+ *
+ * @see goog.html.SafeStyle#unwrap
+ * @override
  */
-goog.math.Size.prototype.area = function() {
-  return this.width * this.height;
+goog.html.SafeStyle.prototype.getTypedStringValue = function() {
+  return this.privateDoNotAccessOrElseSafeStyleWrappedValue_;
 };
 
 
-/**
- * @return {number} The perimeter of the size (width + height) * 2.
- */
-goog.math.Size.prototype.perimeter = function() {
-  return (this.width + this.height) * 2;
-};
+if (goog.DEBUG) {
+  /**
+   * Returns a debug string-representation of this value.
+   *
+   * To obtain the actual string value wrapped in a SafeStyle, use
+   * {@code goog.html.SafeStyle.unwrap}.
+   *
+   * @see goog.html.SafeStyle#unwrap
+   * @override
+   */
+  goog.html.SafeStyle.prototype.toString = function() {
+    return 'SafeStyle{' +
+        this.privateDoNotAccessOrElseSafeStyleWrappedValue_ + '}';
+  };
+}
 
 
 /**
- * @return {number} The ratio of the size's width to its height.
- */
-goog.math.Size.prototype.aspectRatio = function() {
-  return this.width / this.height;
+ * Performs a runtime check that the provided object is indeed a
+ * SafeStyle object, and returns its value.
+ *
+ * @param {!goog.html.SafeStyle} safeStyle The object to extract from.
+ * @return {string} The safeStyle object's contained string, unless
+ *     the run-time type check fails. In that case, {@code unwrap} returns an
+ *     innocuous string, or, if assertions are enabled, throws
+ *     {@code goog.asserts.AssertionError}.
+ */
+goog.html.SafeStyle.unwrap = function(safeStyle) {
+  // Perform additional Run-time type-checking to ensure that
+  // safeStyle is indeed an instance of the expected type.  This
+  // provides some additional protection against security bugs due to
+  // application code that disables type checks.
+  // Specifically, the following checks are performed:
+  // 1. The object is an instance of the expected type.
+  // 2. The object is not an instance of a subclass.
+  // 3. The object carries a type marker for the expected type. "Faking" an
+  // object requires a reference to the type marker, which has names intended
+  // to stand out in code reviews.
+  if (safeStyle instanceof goog.html.SafeStyle &&
+      safeStyle.constructor === goog.html.SafeStyle &&
+      safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
+          goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
+    return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
+  } else {
+    goog.asserts.fail(
+        'expected object of type SafeStyle, got \'' + safeStyle + '\'');
+    return 'type_error:SafeStyle';
+  }
 };
 
 
 /**
- * @return {boolean} True if the size has zero area, false if both dimensions
- *     are non-zero numbers.
+ * Package-internal utility method to create SafeStyle instances.
+ *
+ * @param {string} style The string to initialize the SafeStyle object with.
+ * @return {!goog.html.SafeStyle} The initialized SafeStyle object.
+ * @package
  */
-goog.math.Size.prototype.isEmpty = function() {
-  return !this.area();
+goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse =
+    function(style) {
+  var safeStyle = new goog.html.SafeStyle();
+  safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style;
+  return safeStyle;
 };
 
 
 /**
- * Clamps the width and height parameters upward to integer values.
- * @return {!goog.math.Size} This size with ceil'd components.
+ * A SafeStyle instance corresponding to the empty string.
+ * @const {!goog.html.SafeStyle}
  */
-goog.math.Size.prototype.ceil = function() {
-  this.width = Math.ceil(this.width);
-  this.height = Math.ceil(this.height);
-  return this;
-};
+goog.html.SafeStyle.EMPTY =
+    goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse('');
 
 
 /**
- * @param {!goog.math.Size} target The target size.
- * @return {boolean} True if this Size is the same size or smaller than the
- *     target size in both dimensions.
+ * The innocuous string generated by goog.html.SafeUrl.create when passed
+ * an unsafe value.
+ * @const {string}
  */
-goog.math.Size.prototype.fitsInside = function(target) {
-  return this.width <= target.width && this.height <= target.height;
-};
+goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez';
 
 
 /**
- * Clamps the width and height parameters downward to integer values.
- * @return {!goog.math.Size} This size with floored components.
+ * Mapping of property names to their values.
+ * @typedef {!Object<string, goog.string.Const|string>}
  */
-goog.math.Size.prototype.floor = function() {
-  this.width = Math.floor(this.width);
-  this.height = Math.floor(this.height);
-  return this;
-};
+goog.html.SafeStyle.PropertyMap;
 
 
 /**
- * Rounds the width and height parameters to integer values.
- * @return {!goog.math.Size} This size with rounded components.
+ * Creates a new SafeStyle object from the properties specified in the map.
+ * @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to
+ *     their values, for example {'margin': '1px'}. Names must consist of
+ *     [-_a-zA-Z0-9]. Values might be strings consisting of [-.%_!# a-zA-Z0-9].
+ *     Other values must be wrapped in goog.string.Const. Null value causes
+ *     skipping the property.
+ * @return {!goog.html.SafeStyle}
+ * @throws {Error} If invalid name is provided.
+ * @throws {goog.asserts.AssertionError} If invalid value is provided. With
+ *     disabled assertions, invalid value is replaced by
+ *     goog.html.SafeStyle.INNOCUOUS_STRING.
  */
-goog.math.Size.prototype.round = function() {
-  this.width = Math.round(this.width);
-  this.height = Math.round(this.height);
-  return this;
+goog.html.SafeStyle.create = function(map) {
+  var style = '';
+  for (var name in map) {
+    if (!/^[-_a-zA-Z0-9]+$/.test(name)) {
+      throw Error('Name allows only [-_a-zA-Z0-9], got: ' + name);
+    }
+    var value = map[name];
+    if (value == null) {
+      continue;
+    }
+    if (value instanceof goog.string.Const) {
+      value = goog.string.Const.unwrap(value);
+      // These characters can be used to change context and we don't want that
+      // even with const values.
+      goog.asserts.assert(!/[{;}]/.test(value), 'Value does not allow [{;}].');
+    } else if (!goog.html.SafeStyle.VALUE_RE_.test(value)) {
+      goog.asserts.fail(
+          'String value allows only [-.%_!# a-zA-Z0-9], got: ' + value);
+      value = goog.html.SafeStyle.INNOCUOUS_STRING;
+    }
+    style += name + ':' + value + ';';
+  }
+  if (!style) {
+    return goog.html.SafeStyle.EMPTY;
+  }
+  goog.html.SafeStyle.checkStyle_(style);
+  return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
+      style);
 };
 
 
+// Keep in sync with the error string in create().
 /**
- * Scales this size by the given scale factors. The width and height are scaled
- * by {@code sx} and {@code opt_sy} respectively.  If {@code opt_sy} is not
- * given, then {@code sx} is used for both the width and height.
- * @param {number} sx The scale factor to use for the width.
- * @param {number=} opt_sy The scale factor to use for the height.
- * @return {!goog.math.Size} This Size object after scaling.
+ * Regular expression for safe values.
+ * @const {!RegExp}
+ * @private
  */
-goog.math.Size.prototype.scale = function(sx, opt_sy) {
-  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
-  this.width *= sx;
-  this.height *= sy;
-  return this;
-};
+goog.html.SafeStyle.VALUE_RE_ = /^[-.%_!# a-zA-Z0-9]+$/;
 
 
 /**
- * Uniformly scales the size to fit inside the dimensions of a given size. The
- * original aspect ratio will be preserved.
- *
- * This function assumes that both Sizes contain strictly positive dimensions.
- * @param {!goog.math.Size} target The target size.
- * @return {!goog.math.Size} This Size object, after optional scaling.
+ * Creates a new SafeStyle object by concatenating the values.
+ * @param {...(!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>)} var_args
+ *     SafeStyles to concatenate.
+ * @return {!goog.html.SafeStyle}
  */
-goog.math.Size.prototype.scaleToFit = function(target) {
-  var s = this.aspectRatio() > target.aspectRatio() ?
-      target.width / this.width :
-      target.height / this.height;
+goog.html.SafeStyle.concat = function(var_args) {
+  var style = '';
 
-  return this.scale(s);
+  /**
+   * @param {!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>} argument
+   */
+  var addArgument = function(argument) {
+    if (goog.isArray(argument)) {
+      goog.array.forEach(argument, addArgument);
+    } else {
+      style += goog.html.SafeStyle.unwrap(argument);
+    }
+  };
+
+  goog.array.forEach(arguments, addArgument);
+  if (!style) {
+    return goog.html.SafeStyle.EMPTY;
+  }
+  return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
+      style);
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -20338,5964 +21679,5791 @@ goog.math.Size.prototype.scaleToFit = function(target) {
 // limitations under the License.
 
 /**
- * @fileoverview Utilities for manipulating the browser's Document Object Model
- * Inspiration taken *heavily* from mochikit (http://mochikit.com/).
- *
- * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer
- * to a different document object.  This is useful if you are working with
- * frames or multiple windows.
- *
+ * @fileoverview Utility functions for supporting Bidi issues.
  */
 
 
-// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem
-// is that getTextContent should mimic the DOM3 textContent. We should add a
-// getInnerText (or getText) which tries to return the visible text, innerText.
-
+/**
+ * Namespace for bidi supporting functions.
+ */
+goog.provide('goog.i18n.bidi');
+goog.provide('goog.i18n.bidi.Dir');
+goog.provide('goog.i18n.bidi.DirectionalString');
+goog.provide('goog.i18n.bidi.Format');
 
-goog.provide('goog.dom');
-goog.provide('goog.dom.Appendable');
-goog.provide('goog.dom.DomHelper');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.BrowserFeature');
-goog.require('goog.dom.NodeType');
-goog.require('goog.dom.TagName');
-goog.require('goog.functions');
-goog.require('goog.math.Coordinate');
-goog.require('goog.math.Size');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('goog.userAgent');
+/**
+ * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant
+ * to say that the current locale is a RTL locale.  This should only be used
+ * if you want to override the default behavior for deciding whether the
+ * current locale is RTL or not.
+ *
+ * {@see goog.i18n.bidi.IS_RTL}
+ */
+goog.define('goog.i18n.bidi.FORCE_RTL', false);
 
 
 /**
- * @define {boolean} Whether we know at compile time that the browser is in
- * quirks mode.
- */
-goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);
+ * Constant that defines whether or not the current locale is a RTL locale.
+ * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default
+ * to check that {@link goog.LOCALE} is one of a few major RTL locales.
+ *
+ * <p>This is designed to be a maximally efficient compile-time constant. For
+ * example, for the default goog.LOCALE, compiling
+ * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It
+ * is this design consideration that limits the implementation to only
+ * supporting a few major RTL locales, as opposed to the broader repertoire of
+ * something like goog.i18n.bidi.isRtlLanguage.
+ *
+ * <p>Since this constant refers to the directionality of the locale, it is up
+ * to the caller to determine if this constant should also be used for the
+ * direction of the UI.
+ *
+ * {@see goog.LOCALE}
+ *
+ * @type {boolean}
+ *
+ * TODO(user): write a test that checks that this is a compile-time constant.
+ */
+goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL ||
+    (
+        (goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'he' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' ||
+         goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') &&
+        (goog.LOCALE.length == 2 ||
+         goog.LOCALE.substring(2, 3) == '-' ||
+         goog.LOCALE.substring(2, 3) == '_')
+    ) || (
+        goog.LOCALE.length >= 3 &&
+        goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' &&
+        (goog.LOCALE.length == 3 ||
+         goog.LOCALE.substring(3, 4) == '-' ||
+         goog.LOCALE.substring(3, 4) == '_')
+    );
 
 
 /**
- * @define {boolean} Whether we know at compile time that the browser is in
- * standards compliance mode.
+ * Unicode formatting characters and directionality string constants.
+ * @enum {string}
  */
-goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);
+goog.i18n.bidi.Format = {
+  /** Unicode "Left-To-Right Embedding" (LRE) character. */
+  LRE: '\u202A',
+  /** Unicode "Right-To-Left Embedding" (RLE) character. */
+  RLE: '\u202B',
+  /** Unicode "Pop Directional Formatting" (PDF) character. */
+  PDF: '\u202C',
+  /** Unicode "Left-To-Right Mark" (LRM) character. */
+  LRM: '\u200E',
+  /** Unicode "Right-To-Left Mark" (RLM) character. */
+  RLM: '\u200F'
+};
 
 
 /**
- * Whether we know the compatibility mode at compile time.
- * @type {boolean}
- * @private
+ * Directionality enum.
+ * @enum {number}
  */
-goog.dom.COMPAT_MODE_KNOWN_ =
-    goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
+goog.i18n.bidi.Dir = {
+  /**
+   * Left-to-right.
+   */
+  LTR: 1,
+
+  /**
+   * Right-to-left.
+   */
+  RTL: -1,
+
+  /**
+   * Neither left-to-right nor right-to-left.
+   */
+  NEUTRAL: 0
+};
 
 
 /**
- * Gets the DomHelper object for the document where the element resides.
- * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this
- *     element.
- * @return {!goog.dom.DomHelper} The DomHelper.
+ * 'right' string constant.
+ * @type {string}
  */
-goog.dom.getDomHelper = function(opt_element) {
-  return opt_element ?
-      new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) :
-      (goog.dom.defaultDomHelper_ ||
-          (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper()));
-};
+goog.i18n.bidi.RIGHT = 'right';
 
 
 /**
- * Cached default DOM helper.
- * @type {goog.dom.DomHelper}
- * @private
+ * 'left' string constant.
+ * @type {string}
  */
-goog.dom.defaultDomHelper_;
+goog.i18n.bidi.LEFT = 'left';
 
 
 /**
- * Gets the document object being used by the dom library.
- * @return {!Document} Document object.
+ * 'left' if locale is RTL, 'right' if not.
+ * @type {string}
  */
-goog.dom.getDocument = function() {
-  return document;
-};
+goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT :
+    goog.i18n.bidi.RIGHT;
 
 
 /**
- * Gets an element from the current document by element id.
- *
- * If an Element is passed in, it is returned.
- *
- * @param {string|Element} element Element ID or a DOM node.
- * @return {Element} The element with the given ID, or the node passed in.
+ * 'right' if locale is RTL, 'left' if not.
+ * @type {string}
  */
-goog.dom.getElement = function(element) {
-  return goog.dom.getElementHelper_(document, element);
+goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT :
+    goog.i18n.bidi.LEFT;
+
+
+/**
+ * Convert a directionality given in various formats to a goog.i18n.bidi.Dir
+ * constant. Useful for interaction with different standards of directionality
+ * representation.
+ *
+ * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given
+ *     in one of the following formats:
+ *     1. A goog.i18n.bidi.Dir constant.
+ *     2. A number (positive = LTR, negative = RTL, 0 = neutral).
+ *     3. A boolean (true = RTL, false = LTR).
+ *     4. A null for unknown directionality.
+ * @param {boolean=} opt_noNeutral Whether a givenDir of zero or
+ *     goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in
+ *     order to preserve legacy behavior.
+ * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the
+ *     given directionality. If given null, returns null (i.e. unknown).
+ */
+goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) {
+  if (typeof givenDir == 'number') {
+    // This includes the non-null goog.i18n.bidi.Dir case.
+    return givenDir > 0 ? goog.i18n.bidi.Dir.LTR :
+        givenDir < 0 ? goog.i18n.bidi.Dir.RTL :
+        opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL;
+  } else if (givenDir == null) {
+    return null;
+  } else {
+    // Must be typeof givenDir == 'boolean'.
+    return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR;
+  }
 };
 
 
 /**
- * Gets an element by id from the given document (if present).
- * If an element is given, it is returned.
- * @param {!Document} doc
- * @param {string|Element} element Element ID or a DOM node.
- * @return {Element} The resulting element.
+ * A practical pattern to identify strong LTR characters. This pattern is not
+ * theoretically correct according to the Unicode standard. It is simplified for
+ * performance and small code size.
+ * @type {string}
  * @private
  */
-goog.dom.getElementHelper_ = function(doc, element) {
-  return goog.isString(element) ?
-      doc.getElementById(element) :
-      element;
-};
+goog.i18n.bidi.ltrChars_ =
+    'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
+    '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF';
 
 
 /**
- * Gets an element by id, asserting that the element is found.
- *
- * This is used when an element is expected to exist, and should fail with
- * an assertion error if it does not (if assertions are enabled).
- *
- * @param {string} id Element ID.
- * @return {!Element} The element with the given ID, if it exists.
+ * A practical pattern to identify strong RTL character. This pattern is not
+ * theoretically correct according to the Unicode standard. It is simplified
+ * for performance and small code size.
+ * @type {string}
+ * @private
  */
-goog.dom.getRequiredElement = function(id) {
-  return goog.dom.getRequiredElementHelper_(document, id);
-};
+goog.i18n.bidi.rtlChars_ = '\u0591-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC';
 
 
 /**
- * Helper function for getRequiredElementHelper functions, both static and
- * on DomHelper.  Asserts the element with the given id exists.
- * @param {!Document} doc
- * @param {string} id
- * @return {!Element} The element with the given ID, if it exists.
+ * Simplified regular expression for an HTML tag (opening or closing) or an HTML
+ * escape. We might want to skip over such expressions when estimating the text
+ * directionality.
+ * @type {RegExp}
  * @private
  */
-goog.dom.getRequiredElementHelper_ = function(doc, id) {
-  // To prevent users passing in Elements as is permitted in getElement().
-  goog.asserts.assertString(id);
-  var element = goog.dom.getElementHelper_(doc, id);
-  element = goog.asserts.assertElement(element,
-      'No element found with id: ' + id);
-  return element;
+goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g;
+
+
+/**
+ * Returns the input text with spaces instead of HTML tags or HTML escapes, if
+ * opt_isStripNeeded is true. Else returns the input as is.
+ * Useful for text directionality estimation.
+ * Note: the function should not be used in other contexts; it is not 100%
+ * correct, but rather a good-enough implementation for directionality
+ * estimation purposes.
+ * @param {string} str The given string.
+ * @param {boolean=} opt_isStripNeeded Whether to perform the stripping.
+ *     Default: false (to retain consistency with calling functions).
+ * @return {string} The given string cleaned of HTML tags / escapes.
+ * @private
+ */
+goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) {
+  return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') :
+      str;
 };
 
 
 /**
- * Alias for getElement.
- * @param {string|Element} element Element ID or a DOM node.
- * @return {Element} The element with the given ID, or the node passed in.
- * @deprecated Use {@link goog.dom.getElement} instead.
+ * Regular expression to check for RTL characters.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.$ = goog.dom.getElement;
+goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
 
 
 /**
- * Looks up elements by both tag and class name, using browser native functions
- * ({@code querySelectorAll}, {@code getElementsByTagName} or
- * {@code getElementsByClassName}) where possible. This function
- * is a useful, if limited, way of collecting a list of DOM elements
- * with certain characteristics.  {@code goog.dom.query} offers a
- * more powerful and general solution which allows matching on CSS3
- * selector expressions, but at increased cost in code size. If all you
- * need is particular tags belonging to a single class, this function
- * is fast and sleek.
- *
- * Note that tag names are case sensitive in the SVG namespace, and this
- * function converts opt_tag to uppercase for comparisons. For queries in the
- * SVG namespace you should use querySelector or querySelectorAll instead.
- * https://bugzilla.mozilla.org/show_bug.cgi?id=963870
- * https://bugs.webkit.org/show_bug.cgi?id=83438
- *
- * @see {goog.dom.query}
- *
- * @param {?string=} opt_tag Element tag name.
- * @param {?string=} opt_class Optional class name.
- * @param {(Document|Element)=} opt_el Optional element to look in.
- * @return { {length: number} } Array-like list of elements (only a length
- *     property and numerical indices are guaranteed to exist).
+ * Regular expression to check for LTR characters.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
-  return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class,
-                                                opt_el);
-};
+goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']');
 
 
 /**
- * Returns a static, array-like list of the elements with the provided
- * className.
- * @see {goog.dom.query}
- * @param {string} className the name of the class to look for.
- * @param {(Document|Element)=} opt_el Optional element to look in.
- * @return { {length: number} } The items found with the class name provided.
+ * Test whether the given string has any RTL characters in it.
+ * @param {string} str The given string that need to be tested.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether the string contains RTL characters.
  */
-goog.dom.getElementsByClass = function(className, opt_el) {
-  var parent = opt_el || document;
-  if (goog.dom.canUseQuerySelector_(parent)) {
-    return parent.querySelectorAll('.' + className);
-  }
-  return goog.dom.getElementsByTagNameAndClass_(
-      document, '*', className, opt_el);
+goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) {
+  return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+      str, opt_isHtml));
 };
 
 
 /**
- * Returns the first element with the provided className.
- * @see {goog.dom.query}
- * @param {string} className the name of the class to look for.
- * @param {Element|Document=} opt_el Optional element to look in.
- * @return {Element} The first item with the class name provided.
+ * Test whether the given string has any RTL characters in it.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the string contains RTL characters.
+ * @deprecated Use hasAnyRtl.
  */
-goog.dom.getElementByClass = function(className, opt_el) {
-  var parent = opt_el || document;
-  var retVal = null;
-  if (goog.dom.canUseQuerySelector_(parent)) {
-    retVal = parent.querySelector('.' + className);
-  } else {
-    retVal = goog.dom.getElementsByTagNameAndClass_(
-        document, '*', className, opt_el)[0];
-  }
-  return retVal || null;
-};
+goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl;
 
 
 /**
- * Ensures an element with the given className exists, and then returns the
- * first element with the provided className.
- * @see {goog.dom.query}
- * @param {string} className the name of the class to look for.
- * @param {!Element|!Document=} opt_root Optional element or document to look
- *     in.
- * @return {!Element} The first item with the class name provided.
- * @throws {goog.asserts.AssertionError} Thrown if no element is found.
+ * Test whether the given string has any LTR characters in it.
+ * @param {string} str The given string that need to be tested.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether the string contains LTR characters.
  */
-goog.dom.getRequiredElementByClass = function(className, opt_root) {
-  var retValue = goog.dom.getElementByClass(className, opt_root);
-  return goog.asserts.assert(retValue,
-      'No element found with className: ' + className);
+goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) {
+  return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+      str, opt_isHtml));
 };
 
 
 /**
- * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and
- * fast W3C Selectors API.
- * @param {!(Element|Document)} parent The parent document object.
- * @return {boolean} whether or not we can use parent.querySelector* APIs.
+ * Regular expression pattern to check if the first character in the string
+ * is LTR.
+ * @type {RegExp}
  * @private
  */
-goog.dom.canUseQuerySelector_ = function(parent) {
-  return !!(parent.querySelectorAll && parent.querySelector);
-};
+goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']');
 
 
 /**
- * Helper for {@code getElementsByTagNameAndClass}.
- * @param {!Document} doc The document to get the elements in.
- * @param {?string=} opt_tag Element tag name.
- * @param {?string=} opt_class Optional class name.
- * @param {(Document|Element)=} opt_el Optional element to look in.
- * @return { {length: number} } Array-like list of elements (only a length
- *     property and numerical indices are guaranteed to exist).
+ * Regular expression pattern to check if the first character in the string
+ * is RTL.
+ * @type {RegExp}
  * @private
  */
-goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class,
-                                                  opt_el) {
-  var parent = opt_el || doc;
-  var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : '';
-
-  if (goog.dom.canUseQuerySelector_(parent) &&
-      (tagName || opt_class)) {
-    var query = tagName + (opt_class ? '.' + opt_class : '');
-    return parent.querySelectorAll(query);
-  }
+goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']');
 
-  // Use the native getElementsByClassName if available, under the assumption
-  // that even when the tag name is specified, there will be fewer elements to
-  // filter through when going by class than by tag name
-  if (opt_class && parent.getElementsByClassName) {
-    var els = parent.getElementsByClassName(opt_class);
 
-    if (tagName) {
-      var arrayLike = {};
-      var len = 0;
+/**
+ * Check if the first character in the string is RTL or not.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the first character in str is an RTL char.
+ */
+goog.i18n.bidi.isRtlChar = function(str) {
+  return goog.i18n.bidi.rtlRe_.test(str);
+};
 
-      // Filter for specific tags if requested.
-      for (var i = 0, el; el = els[i]; i++) {
-        if (tagName == el.nodeName) {
-          arrayLike[len++] = el;
-        }
-      }
-      arrayLike.length = len;
 
-      return arrayLike;
-    } else {
-      return els;
-    }
-  }
+/**
+ * Check if the first character in the string is LTR or not.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the first character in str is an LTR char.
+ */
+goog.i18n.bidi.isLtrChar = function(str) {
+  return goog.i18n.bidi.ltrRe_.test(str);
+};
 
-  var els = parent.getElementsByTagName(tagName || '*');
 
-  if (opt_class) {
-    var arrayLike = {};
-    var len = 0;
-    for (var i = 0, el; el = els[i]; i++) {
-      var className = el.className;
-      // Check if className has a split function since SVG className does not.
-      if (typeof className.split == 'function' &&
-          goog.array.contains(className.split(/\s+/), opt_class)) {
-        arrayLike[len++] = el;
-      }
-    }
-    arrayLike.length = len;
-    return arrayLike;
-  } else {
-    return els;
-  }
+/**
+ * Check if the first character in the string is neutral or not.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the first character in str is a neutral char.
+ */
+goog.i18n.bidi.isNeutralChar = function(str) {
+  return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str);
 };
 
 
 /**
- * Alias for {@code getElementsByTagNameAndClass}.
- * @param {?string=} opt_tag Element tag name.
- * @param {?string=} opt_class Optional class name.
- * @param {Element=} opt_el Optional element to look in.
- * @return { {length: number} } Array-like list of elements (only a length
- *     property and numerical indices are guaranteed to exist).
- * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead.
+ * Regular expressions to check if a piece of text is of LTR directionality
+ * on first character with strong directionality.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;
+goog.i18n.bidi.ltrDirCheckRe_ = new RegExp(
+    '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']');
 
 
 /**
- * Sets multiple properties on a node.
- * @param {Element} element DOM node to set properties on.
- * @param {Object} properties Hash of property:value pairs.
+ * Regular expressions to check if a piece of text is of RTL directionality
+ * on first character with strong directionality.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.setProperties = function(element, properties) {
-  goog.object.forEach(properties, function(val, key) {
-    if (key == 'style') {
-      element.style.cssText = val;
-    } else if (key == 'class') {
-      element.className = val;
-    } else if (key == 'for') {
-      element.htmlFor = val;
-    } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) {
-      element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val);
-    } else if (goog.string.startsWith(key, 'aria-') ||
-        goog.string.startsWith(key, 'data-')) {
-      element.setAttribute(key, val);
-    } else {
-      element[key] = val;
-    }
-  });
-};
+goog.i18n.bidi.rtlDirCheckRe_ = new RegExp(
+    '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']');
 
 
 /**
- * Map of attributes that should be set using
- * element.setAttribute(key, val) instead of element[key] = val.  Used
- * by goog.dom.setProperties.
- *
- * @type {Object}
- * @private
+ * Check whether the first strongly directional character (if any) is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether RTL directionality is detected using the first
+ *     strongly-directional character method.
  */
-goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
-  'cellpadding': 'cellPadding',
-  'cellspacing': 'cellSpacing',
-  'colspan': 'colSpan',
-  'frameborder': 'frameBorder',
-  'height': 'height',
-  'maxlength': 'maxLength',
-  'role': 'role',
-  'rowspan': 'rowSpan',
-  'type': 'type',
-  'usemap': 'useMap',
-  'valign': 'vAlign',
-  'width': 'width'
+goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) {
+  return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+      str, opt_isHtml));
 };
 
 
 /**
- * Gets the dimensions of the viewport.
- *
- * Gecko Standards mode:
- * docEl.clientWidth  Width of viewport excluding scrollbar.
- * win.innerWidth     Width of viewport including scrollbar.
- * body.clientWidth   Width of body element.
- *
- * docEl.clientHeight Height of viewport excluding scrollbar.
- * win.innerHeight    Height of viewport including scrollbar.
- * body.clientHeight  Height of document.
- *
- * Gecko Backwards compatible mode:
- * docEl.clientWidth  Width of viewport excluding scrollbar.
- * win.innerWidth     Width of viewport including scrollbar.
- * body.clientWidth   Width of viewport excluding scrollbar.
- *
- * docEl.clientHeight Height of document.
- * win.innerHeight    Height of viewport including scrollbar.
- * body.clientHeight  Height of viewport excluding scrollbar.
- *
- * IE6/7 Standards mode:
- * docEl.clientWidth  Width of viewport excluding scrollbar.
- * win.innerWidth     Undefined.
- * body.clientWidth   Width of body element.
- *
- * docEl.clientHeight Height of viewport excluding scrollbar.
- * win.innerHeight    Undefined.
- * body.clientHeight  Height of document element.
- *
- * IE5 + IE6/7 Backwards compatible mode:
- * docEl.clientWidth  0.
- * win.innerWidth     Undefined.
- * body.clientWidth   Width of viewport excluding scrollbar.
- *
- * docEl.clientHeight 0.
- * win.innerHeight    Undefined.
- * body.clientHeight  Height of viewport excluding scrollbar.
- *
- * Opera 9 Standards and backwards compatible mode:
- * docEl.clientWidth  Width of viewport excluding scrollbar.
- * win.innerWidth     Width of viewport including scrollbar.
- * body.clientWidth   Width of viewport excluding scrollbar.
- *
- * docEl.clientHeight Height of document.
- * win.innerHeight    Height of viewport including scrollbar.
- * body.clientHeight  Height of viewport excluding scrollbar.
- *
- * WebKit:
- * Safari 2
- * docEl.clientHeight Same as scrollHeight.
- * docEl.clientWidth  Same as innerWidth.
- * win.innerWidth     Width of viewport excluding scrollbar.
- * win.innerHeight    Height of the viewport including scrollbar.
- * frame.innerHeight  Height of the viewport exluding scrollbar.
- *
- * Safari 3 (tested in 522)
- *
- * docEl.clientWidth  Width of viewport excluding scrollbar.
- * docEl.clientHeight Height of viewport excluding scrollbar in strict mode.
- * body.clientHeight  Height of viewport excluding scrollbar in quirks mode.
- *
- * @param {Window=} opt_window Optional window element to test.
- * @return {!goog.math.Size} Object with values 'width' and 'height'.
+ * Check whether the first strongly directional character (if any) is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether RTL directionality is detected using the first
+ *     strongly-directional character method.
+ * @deprecated Use startsWithRtl.
  */
-goog.dom.getViewportSize = function(opt_window) {
-  // TODO(arv): This should not take an argument
-  return goog.dom.getViewportSize_(opt_window || window);
-};
+goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl;
 
 
 /**
- * Helper for {@code getViewportSize}.
- * @param {Window} win The window to get the view port size for.
- * @return {!goog.math.Size} Object with values 'width' and 'height'.
- * @private
+ * Check whether the first strongly directional character (if any) is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether LTR directionality is detected using the first
+ *     strongly-directional character method.
  */
-goog.dom.getViewportSize_ = function(win) {
-  var doc = win.document;
-  var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
-  return new goog.math.Size(el.clientWidth, el.clientHeight);
+goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) {
+  return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+      str, opt_isHtml));
 };
 
 
 /**
- * Calculates the height of the document.
- *
- * @return {number} The height of the current document.
+ * Check whether the first strongly directional character (if any) is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether LTR directionality is detected using the first
+ *     strongly-directional character method.
+ * @deprecated Use startsWithLtr.
  */
-goog.dom.getDocumentHeight = function() {
-  return goog.dom.getDocumentHeight_(window);
-};
+goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr;
 
 
 /**
- * Calculates the height of the document of the given window.
- *
- * Function code copied from the opensocial gadget api:
- *   gadgets.window.adjustHeight(opt_height)
- *
+ * Regular expression to check if a string looks like something that must
+ * always be LTR even in RTL text, e.g. a URL. When estimating the
+ * directionality of text containing these, we treat these as weakly LTR,
+ * like numbers.
+ * @type {RegExp}
  * @private
- * @param {Window} win The window whose document height to retrieve.
- * @return {number} The height of the document of the given window.
  */
-goog.dom.getDocumentHeight_ = function(win) {
-  // NOTE(eae): This method will return the window size rather than the document
-  // size in webkit quirks mode.
-  var doc = win.document;
-  var height = 0;
+goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/;
 
-  if (doc) {
-    // Calculating inner content height is hard and different between
-    // browsers rendering in Strict vs. Quirks mode.  We use a combination of
-    // three properties within document.body and document.documentElement:
-    // - scrollHeight
-    // - offsetHeight
-    // - clientHeight
-    // These values differ significantly between browsers and rendering modes.
-    // But there are patterns.  It just takes a lot of time and persistence
-    // to figure out.
 
-    // If the window has no contents, it has no height. (In IE10,
-    // document.body & document.documentElement are null in an empty iFrame.)
-    var body = doc.body;
-    var docEl = doc.documentElement;
-    if (!body && !docEl) {
-      return 0;
-    }
+/**
+ * Check whether the input string either contains no strongly directional
+ * characters or looks like a url.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether neutral directionality is detected.
+ */
+goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) {
+  str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml);
+  return goog.i18n.bidi.isRequiredLtrRe_.test(str) ||
+      !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str);
+};
 
-    // Get the height of the viewport
-    var vh = goog.dom.getViewportSize_(win).height;
-    if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) {
-      // In Strict mode:
-      // The inner content height is contained in either:
-      //    document.documentElement.scrollHeight
-      //    document.documentElement.offsetHeight
-      // Based on studying the values output by different browsers,
-      // use the value that's NOT equal to the viewport height found above.
-      height = docEl.scrollHeight != vh ?
-          docEl.scrollHeight : docEl.offsetHeight;
-    } else {
-      // In Quirks mode:
-      // documentElement.clientHeight is equal to documentElement.offsetHeight
-      // except in IE.  In most browsers, document.documentElement can be used
-      // to calculate the inner content height.
-      // However, in other browsers (e.g. IE), document.body must be used
-      // instead.  How do we know which one to use?
-      // If document.documentElement.clientHeight does NOT equal
-      // document.documentElement.offsetHeight, then use document.body.
-      var sh = docEl.scrollHeight;
-      var oh = docEl.offsetHeight;
-      if (docEl.clientHeight != oh) {
-        sh = body.scrollHeight;
-        oh = body.offsetHeight;
-      }
 
-      // Detect whether the inner content height is bigger or smaller
-      // than the bounding box (viewport).  If bigger, take the larger
-      // value.  If smaller, take the smaller value.
-      if (sh > vh) {
-        // Content is larger
-        height = sh > oh ? sh : oh;
-      } else {
-        // Content is smaller
-        height = sh < oh ? sh : oh;
-      }
-    }
-  }
+/**
+ * Regular expressions to check if the last strongly-directional character in a
+ * piece of text is LTR.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp(
+    '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$');
 
-  return height;
-};
+
+/**
+ * Regular expressions to check if the last strongly-directional character in a
+ * piece of text is RTL.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp(
+    '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$');
 
 
 /**
- * Gets the page scroll distance as a coordinate object.
- *
- * @param {Window=} opt_window Optional window element to test.
- * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
- * @deprecated Use {@link goog.dom.getDocumentScroll} instead.
+ * Check if the exit directionality a piece of text is LTR, i.e. if the last
+ * strongly-directional character in the string is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether LTR exit directionality was detected.
  */
-goog.dom.getPageScroll = function(opt_window) {
-  var win = opt_window || goog.global || window;
-  return goog.dom.getDomHelper(win.document).getDocumentScroll();
+goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) {
+  return goog.i18n.bidi.ltrExitDirCheckRe_.test(
+      goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
 };
 
 
 /**
- * Gets the document scroll distance as a coordinate object.
- *
- * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
+ * Check if the exit directionality a piece of text is LTR, i.e. if the last
+ * strongly-directional character in the string is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether LTR exit directionality was detected.
+ * @deprecated Use endsWithLtr.
  */
-goog.dom.getDocumentScroll = function() {
-  return goog.dom.getDocumentScroll_(document);
-};
+goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr;
 
 
 /**
- * Helper for {@code getDocumentScroll}.
- *
- * @param {!Document} doc The document to get the scroll for.
- * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
- * @private
+ * Check if the exit directionality a piece of text is RTL, i.e. if the last
+ * strongly-directional character in the string is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether RTL exit directionality was detected.
  */
-goog.dom.getDocumentScroll_ = function(doc) {
-  var el = goog.dom.getDocumentScrollElement_(doc);
-  var win = goog.dom.getWindow_(doc);
-  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') &&
-      win.pageYOffset != el.scrollTop) {
-    // The keyboard on IE10 touch devices shifts the page using the pageYOffset
-    // without modifying scrollTop. For this case, we want the body scroll
-    // offsets.
-    return new goog.math.Coordinate(el.scrollLeft, el.scrollTop);
-  }
-  return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft,
-      win.pageYOffset || el.scrollTop);
+goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) {
+  return goog.i18n.bidi.rtlExitDirCheckRe_.test(
+      goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
 };
 
 
 /**
- * Gets the document scroll element.
- * @return {!Element} Scrolling element.
+ * Check if the exit directionality a piece of text is RTL, i.e. if the last
+ * strongly-directional character in the string is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether RTL exit directionality was detected.
+ * @deprecated Use endsWithRtl.
  */
-goog.dom.getDocumentScrollElement = function() {
-  return goog.dom.getDocumentScrollElement_(document);
-};
+goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl;
 
 
 /**
- * Helper for {@code getDocumentScrollElement}.
- * @param {!Document} doc The document to get the scroll element for.
- * @return {!Element} Scrolling element.
+ * A regular expression for matching right-to-left language codes.
+ * See {@link #isRtlLanguage} for the design.
+ * @type {RegExp}
  * @private
  */
-goog.dom.getDocumentScrollElement_ = function(doc) {
-  // WebKit needs body.scrollLeft in both quirks mode and strict mode. We also
-  // default to the documentElement if the document does not have a body (e.g.
-  // a SVG document).
-  if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) {
-    return doc.documentElement;
-  }
-  return doc.body || doc.documentElement;
-};
+goog.i18n.bidi.rtlLocalesRe_ = new RegExp(
+    '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' +
+    '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' +
+    '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)',
+    'i');
 
 
 /**
- * Gets the window object associated with the given document.
- *
- * @param {Document=} opt_doc  Document object to get window for.
- * @return {!Window} The window associated with the given document.
+ * Check if a BCP 47 / III language code indicates an RTL language, i.e. either:
+ * - a language code explicitly specifying one of the right-to-left scripts,
+ *   e.g. "az-Arab", or<p>
+ * - a language code specifying one of the languages normally written in a
+ *   right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying
+ *   Latin or Cyrillic script (which are the usual LTR alternatives).<p>
+ * The list of right-to-left scripts appears in the 100-199 range in
+ * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and
+ * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and
+ * Tifinagh, which also have significant modern usage. The rest (Syriac,
+ * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage
+ * and are not recognized to save on code size.
+ * The languages usually written in a right-to-left script are taken as those
+ * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng  in
+ * http://www.iana.org/assignments/language-subtag-registry,
+ * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug).
+ * Other subtags of the language code, e.g. regions like EG (Egypt), are
+ * ignored.
+ * @param {string} lang BCP 47 (a.k.a III) language code.
+ * @return {boolean} Whether the language code is an RTL language.
  */
-goog.dom.getWindow = function(opt_doc) {
-  // TODO(arv): This should not take an argument.
-  return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
+goog.i18n.bidi.isRtlLanguage = function(lang) {
+  return goog.i18n.bidi.rtlLocalesRe_.test(lang);
 };
 
 
 /**
- * Helper for {@code getWindow}.
- *
- * @param {!Document} doc  Document object to get window for.
- * @return {!Window} The window associated with the given document.
+ * Regular expression for bracket guard replacement in html.
+ * @type {RegExp}
  * @private
  */
-goog.dom.getWindow_ = function(doc) {
-  return doc.parentWindow || doc.defaultView;
-};
+goog.i18n.bidi.bracketGuardHtmlRe_ =
+    /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)/g;
 
 
 /**
- * Returns a dom node with a set of attributes.  This function accepts varargs
- * for subsequent nodes to be added.  Subsequent nodes will be added to the
- * first node as childNodes.
- *
- * So:
- * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
- * would return a div with two child paragraphs
- *
- * @param {string} tagName Tag to create.
- * @param {(Object|Array.<string>|string)=} opt_attributes If object, then a map
- *     of name-value pairs for attributes. If a string, then this is the
- *     className of the new element. If an array, the elements will be joined
- *     together as the className of the new element.
- * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
- *     strings for text nodes. If one of the var_args is an array or NodeList,i
- *     its elements will be added as childNodes instead.
- * @return {!Element} Reference to a DOM node.
+ * Regular expression for bracket guard replacement in text.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.createDom = function(tagName, opt_attributes, var_args) {
-  return goog.dom.createDom_(document, arguments);
-};
+goog.i18n.bidi.bracketGuardTextRe_ =
+    /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
 
 
 /**
- * Helper for {@code createDom}.
- * @param {!Document} doc The document to create the DOM in.
- * @param {!Arguments} args Argument object passed from the callers. See
- *     {@code goog.dom.createDom} for details.
- * @return {!Element} Reference to a DOM node.
- * @private
+ * Apply bracket guard using html span tag. This is to address the problem of
+ * messy bracket display frequently happens in RTL layout.
+ * @param {string} s The string that need to be processed.
+ * @param {boolean=} opt_isRtlContext specifies default direction (usually
+ *     direction of the UI).
+ * @return {string} The processed string, with all bracket guarded.
  */
-goog.dom.createDom_ = function(doc, args) {
-  var tagName = args[0];
-  var attributes = args[1];
+goog.i18n.bidi.guardBracketInHtml = function(s, opt_isRtlContext) {
+  var useRtl = opt_isRtlContext === undefined ?
+      goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
+  if (useRtl) {
+    return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
+        '<span dir=rtl>$&</span>');
+  }
+  return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
+      '<span dir=ltr>$&</span>');
+};
 
-  // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/
-  //                            dhtml/reference/properties/name_2.asp
-  // Also does not allow setting of 'type' attribute on 'input' or 'button'.
-  if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes &&
-      (attributes.name || attributes.type)) {
-    var tagNameArr = ['<', tagName];
-    if (attributes.name) {
-      tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name),
-                      '"');
-    }
-    if (attributes.type) {
-      tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type),
-                      '"');
 
-      // Clone attributes map to remove 'type' without mutating the input.
-      var clone = {};
-      goog.object.extend(clone, attributes);
+/**
+ * Apply bracket guard using LRM and RLM. This is to address the problem of
+ * messy bracket display frequently happens in RTL layout.
+ * This version works for both plain text and html. But it does not work as
+ * good as guardBracketInHtml in some cases.
+ * @param {string} s The string that need to be processed.
+ * @param {boolean=} opt_isRtlContext specifies default direction (usually
+ *     direction of the UI).
+ * @return {string} The processed string, with all bracket guarded.
+ */
+goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) {
+  var useRtl = opt_isRtlContext === undefined ?
+      goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
+  var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM;
+  return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark);
+};
 
-      // JSCompiler can't see how goog.object.extend added this property,
-      // because it was essentially added by reflection.
-      // So it needs to be quoted.
-      delete clone['type'];
 
-      attributes = clone;
-    }
-    tagNameArr.push('>');
-    tagName = tagNameArr.join('');
+/**
+ * Enforce the html snippet in RTL directionality regardless overall context.
+ * If the html piece was enclosed by tag, dir will be applied to existing
+ * tag, otherwise a span tag will be added as wrapper. For this reason, if
+ * html snippet start with with tag, this tag must enclose the whole piece. If
+ * the tag already has a dir specified, this new one will override existing
+ * one in behavior (tested on FF and IE).
+ * @param {string} html The string that need to be processed.
+ * @return {string} The processed string, with directionality enforced to RTL.
+ */
+goog.i18n.bidi.enforceRtlInHtml = function(html) {
+  if (html.charAt(0) == '<') {
+    return html.replace(/<\w+/, '$& dir=rtl');
   }
+  // '\n' is important for FF so that it won't incorrectly merge span groups
+  return '\n<span dir=rtl>' + html + '</span>';
+};
 
-  var element = doc.createElement(tagName);
 
-  if (attributes) {
-    if (goog.isString(attributes)) {
-      element.className = attributes;
-    } else if (goog.isArray(attributes)) {
-      element.className = attributes.join(' ');
-    } else {
-      goog.dom.setProperties(element, attributes);
-    }
-  }
+/**
+ * Enforce RTL on both end of the given text piece using unicode BiDi formatting
+ * characters RLE and PDF.
+ * @param {string} text The piece of text that need to be wrapped.
+ * @return {string} The wrapped string after process.
+ */
+goog.i18n.bidi.enforceRtlInText = function(text) {
+  return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
+};
 
-  if (args.length > 2) {
-    goog.dom.append_(doc, element, args, 2);
+
+/**
+ * Enforce the html snippet in RTL directionality regardless overall context.
+ * If the html piece was enclosed by tag, dir will be applied to existing
+ * tag, otherwise a span tag will be added as wrapper. For this reason, if
+ * html snippet start with with tag, this tag must enclose the whole piece. If
+ * the tag already has a dir specified, this new one will override existing
+ * one in behavior (tested on FF and IE).
+ * @param {string} html The string that need to be processed.
+ * @return {string} The processed string, with directionality enforced to RTL.
+ */
+goog.i18n.bidi.enforceLtrInHtml = function(html) {
+  if (html.charAt(0) == '<') {
+    return html.replace(/<\w+/, '$& dir=ltr');
   }
+  // '\n' is important for FF so that it won't incorrectly merge span groups
+  return '\n<span dir=ltr>' + html + '</span>';
+};
 
-  return element;
+
+/**
+ * Enforce LTR on both end of the given text piece using unicode BiDi formatting
+ * characters LRE and PDF.
+ * @param {string} text The piece of text that need to be wrapped.
+ * @return {string} The wrapped string after process.
+ */
+goog.i18n.bidi.enforceLtrInText = function(text) {
+  return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF;
 };
 
 
 /**
- * Appends a node with text or other nodes.
- * @param {!Document} doc The document to create new nodes in.
- * @param {!Node} parent The node to append nodes to.
- * @param {!Arguments} args The values to add. See {@code goog.dom.append}.
- * @param {number} startIndex The index of the array to start from.
+ * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;"
+ * @type {RegExp}
  * @private
  */
-goog.dom.append_ = function(doc, parent, args, startIndex) {
-  function childHandler(child) {
-    // TODO(user): More coercion, ala MochiKit?
-    if (child) {
-      parent.appendChild(goog.isString(child) ?
-          doc.createTextNode(child) : child);
-    }
-  }
+goog.i18n.bidi.dimensionsRe_ =
+    /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;
 
-  for (var i = startIndex; i < args.length; i++) {
-    var arg = args[i];
-    // TODO(attila): Fix isArrayLike to return false for a text node.
-    if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
-      // If the argument is a node list, not a real array, use a clone,
-      // because forEach can't be used to mutate a NodeList.
-      goog.array.forEach(goog.dom.isNodeList(arg) ?
-          goog.array.toArray(arg) : arg,
-          childHandler);
-    } else {
-      childHandler(arg);
-    }
-  }
-};
+
+/**
+ * Regular expression for left.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.leftRe_ = /left/gi;
 
 
 /**
- * Alias for {@code createDom}.
- * @param {string} tagName Tag to create.
- * @param {(string|Object)=} opt_attributes If object, then a map of name-value
- *     pairs for attributes. If a string, then this is the className of the new
- *     element.
- * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
- *     strings for text nodes. If one of the var_args is an array, its
- *     children will be added as childNodes instead.
- * @return {!Element} Reference to a DOM node.
- * @deprecated Use {@link goog.dom.createDom} instead.
+ * Regular expression for right.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.$dom = goog.dom.createDom;
+goog.i18n.bidi.rightRe_ = /right/gi;
 
 
 /**
- * Creates a new element.
- * @param {string} name Tag name.
- * @return {!Element} The new element.
+ * Placeholder regular expression for swapping.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.createElement = function(name) {
-  return document.createElement(name);
-};
+goog.i18n.bidi.tempRe_ = /%%%%/g;
 
 
 /**
- * Creates a new text node.
- * @param {number|string} content Content.
- * @return {!Text} The new text node.
+ * Swap location parameters and 'left'/'right' in CSS specification. The
+ * processed string will be suited for RTL layout. Though this function can
+ * cover most cases, there are always exceptions. It is suggested to put
+ * those exceptions in separate group of CSS string.
+ * @param {string} cssStr CSS spefication string.
+ * @return {string} Processed CSS specification string.
  */
-goog.dom.createTextNode = function(content) {
-  return document.createTextNode(String(content));
+goog.i18n.bidi.mirrorCSS = function(cssStr) {
+  return cssStr.
+      // reverse dimensions
+      replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2').
+      replace(goog.i18n.bidi.leftRe_, '%%%%').          // swap left and right
+      replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT).
+      replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT);
 };
 
 
 /**
- * Create a table.
- * @param {number} rows The number of rows in the table.  Must be >= 1.
- * @param {number} columns The number of columns in the table.  Must be >= 1.
- * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps.
- * @return {!Element} The created table.
+ * Regular expression for hebrew double quote substitution, finding quote
+ * directly after hebrew characters.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) {
-  return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp);
-};
+goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
 
 
 /**
- * Create a table.
- * @param {!Document} doc Document object to use to create the table.
- * @param {number} rows The number of rows in the table.  Must be >= 1.
- * @param {number} columns The number of columns in the table.  Must be >= 1.
- * @param {boolean} fillWithNbsp If true, fills table entries with nsbps.
- * @return {!Element} The created table.
+ * Regular expression for hebrew single quote substitution, finding quote
+ * directly after hebrew characters.
+ * @type {RegExp}
  * @private
  */
-goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) {
-  var rowHtml = ['<tr>'];
-  for (var i = 0; i < columns; i++) {
-    rowHtml.push(fillWithNbsp ? '<td>&nbsp;</td>' : '<td></td>');
-  }
-  rowHtml.push('</tr>');
-  rowHtml = rowHtml.join('');
-  var totalHtml = ['<table>'];
-  for (i = 0; i < rows; i++) {
-    totalHtml.push(rowHtml);
-  }
-  totalHtml.push('</table>');
+goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
+
 
-  var elem = doc.createElement(goog.dom.TagName.DIV);
-  elem.innerHTML = totalHtml.join('');
-  return /** @type {!Element} */ (elem.removeChild(elem.firstChild));
+/**
+ * Replace the double and single quote directly after a Hebrew character with
+ * GERESH and GERSHAYIM. In such case, most likely that's user intention.
+ * @param {string} str String that need to be processed.
+ * @return {string} Processed string with double/single quote replaced.
+ */
+goog.i18n.bidi.normalizeHebrewQuote = function(str) {
+  return str.
+      replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4').
+      replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3');
 };
 
 
 /**
- * Converts an HTML string into a document fragment. The string must be
- * sanitized in order to avoid cross-site scripting. For example
- * {@code goog.dom.htmlToDocumentFragment('&lt;img src=x onerror=alert(0)&gt;')}
- * triggers an alert in all browsers, even if the returned document fragment
- * is thrown away immediately.
- *
- * @param {string} htmlString The HTML string to convert.
- * @return {!Node} The resulting document fragment.
+ * Regular expression to split a string into "words" for directionality
+ * estimation based on relative word counts.
+ * @type {RegExp}
+ * @private
  */
-goog.dom.htmlToDocumentFragment = function(htmlString) {
-  return goog.dom.htmlToDocumentFragment_(document, htmlString);
-};
+goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
 
 
 /**
- * Helper for {@code htmlToDocumentFragment}.
- *
- * @param {!Document} doc The document.
- * @param {string} htmlString The HTML string to convert.
- * @return {!Node} The resulting document fragment.
+ * Regular expression to check if a string contains any numerals. Used to
+ * differentiate between completely neutral strings and those containing
+ * numbers, which are weakly LTR.
+ * @type {RegExp}
  * @private
  */
-goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
-  var tempDiv = doc.createElement('div');
-  if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
-    tempDiv.innerHTML = '<br>' + htmlString;
-    tempDiv.removeChild(tempDiv.firstChild);
-  } else {
-    tempDiv.innerHTML = htmlString;
-  }
-  if (tempDiv.childNodes.length == 1) {
-    return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild));
-  } else {
-    var fragment = doc.createDocumentFragment();
-    while (tempDiv.firstChild) {
-      fragment.appendChild(tempDiv.firstChild);
+goog.i18n.bidi.hasNumeralsRe_ = /\d/;
+
+
+/**
+ * This constant controls threshold of RTL directionality.
+ * @type {number}
+ * @private
+ */
+goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
+
+
+/**
+ * Estimates the directionality of a string based on relative word counts.
+ * If the number of RTL words is above a certain percentage of the total number
+ * of strongly directional words, returns RTL.
+ * Otherwise, if any words are strongly or weakly LTR, returns LTR.
+ * Otherwise, returns UNKNOWN, which is used to mean "neutral".
+ * Numbers are counted as weakly LTR.
+ * @param {string} str The string to be checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}.
+ */
+goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) {
+  var rtlCount = 0;
+  var totalCount = 0;
+  var hasWeaklyLtr = false;
+  var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml).
+      split(goog.i18n.bidi.wordSeparatorRe_);
+  for (var i = 0; i < tokens.length; i++) {
+    var token = tokens[i];
+    if (goog.i18n.bidi.startsWithRtl(token)) {
+      rtlCount++;
+      totalCount++;
+    } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) {
+      hasWeaklyLtr = true;
+    } else if (goog.i18n.bidi.hasAnyLtr(token)) {
+      totalCount++;
+    } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) {
+      hasWeaklyLtr = true;
     }
-    return fragment;
   }
+
+  return totalCount == 0 ?
+      (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) :
+      (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ?
+          goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR);
 };
 
 
 /**
- * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
- * mode, false otherwise.
- * @return {boolean} True if in CSS1-compatible mode.
+ * Check the directionality of a piece of text, return true if the piece of
+ * text should be laid out in RTL direction.
+ * @param {string} str The piece of text that need to be detected.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether this piece of text should be laid out in RTL.
  */
-goog.dom.isCss1CompatMode = function() {
-  return goog.dom.isCss1CompatMode_(document);
+goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) {
+  return goog.i18n.bidi.estimateDirection(str, opt_isHtml) ==
+      goog.i18n.bidi.Dir.RTL;
 };
 
 
 /**
- * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
- * mode, false otherwise.
- * @param {Document} doc The document to check.
- * @return {boolean} True if in CSS1-compatible mode.
- * @private
+ * Sets text input element's directionality and text alignment based on a
+ * given directionality. Does nothing if the given directionality is unknown or
+ * neutral.
+ * @param {Element} element Input field element to set directionality to.
+ * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality,
+ *     given in one of the following formats:
+ *     1. A goog.i18n.bidi.Dir constant.
+ *     2. A number (positive = LRT, negative = RTL, 0 = neutral).
+ *     3. A boolean (true = RTL, false = LTR).
+ *     4. A null for unknown directionality.
  */
-goog.dom.isCss1CompatMode_ = function(doc) {
-  if (goog.dom.COMPAT_MODE_KNOWN_) {
-    return goog.dom.ASSUME_STANDARDS_MODE;
+goog.i18n.bidi.setElementDirAndAlign = function(element, dir) {
+  if (element) {
+    dir = goog.i18n.bidi.toDir(dir);
+    if (dir) {
+      element.style.textAlign =
+          dir == goog.i18n.bidi.Dir.RTL ?
+          goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT;
+      element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr';
+    }
   }
-
-  return doc.compatMode == 'CSS1Compat';
 };
 
 
+
 /**
- * Determines if the given node can contain children, intended to be used for
- * HTML generation.
- *
- * IE natively supports node.canHaveChildren but has inconsistent behavior.
- * Prior to IE8 the base tag allows children and in IE9 all nodes return true
- * for canHaveChildren.
- *
- * In practice all non-IE browsers allow you to add children to any node, but
- * the behavior is inconsistent:
- *
- * <pre>
- *   var a = document.createElement('br');
- *   a.appendChild(document.createTextNode('foo'));
- *   a.appendChild(document.createTextNode('bar'));
- *   console.log(a.childNodes.length);  // 2
- *   console.log(a.innerHTML);  // Chrome: "", IE9: "foobar", FF3.5: "foobar"
- * </pre>
+ * Strings that have an (optional) known direction.
  *
- * For more information, see:
- * http://dev.w3.org/html5/markup/syntax.html#syntax-elements
- *
- * TODO(user): Rename shouldAllowChildren() ?
+ * Implementations of this interface are string-like objects that carry an
+ * attached direction, if known.
+ * @interface
+ */
+goog.i18n.bidi.DirectionalString = function() {};
+
+
+/**
+ * Interface marker of the DirectionalString interface.
  *
- * @param {Node} node The node to check.
- * @return {boolean} Whether the node can contain children.
+ * This property can be used to determine at runtime whether or not an object
+ * implements this interface.  All implementations of this interface set this
+ * property to {@code true}.
+ * @type {boolean}
  */
-goog.dom.canHaveChildren = function(node) {
-  if (node.nodeType != goog.dom.NodeType.ELEMENT) {
-    return false;
-  }
-  switch (node.tagName) {
-    case goog.dom.TagName.APPLET:
-    case goog.dom.TagName.AREA:
-    case goog.dom.TagName.BASE:
-    case goog.dom.TagName.BR:
-    case goog.dom.TagName.COL:
-    case goog.dom.TagName.COMMAND:
-    case goog.dom.TagName.EMBED:
-    case goog.dom.TagName.FRAME:
-    case goog.dom.TagName.HR:
-    case goog.dom.TagName.IMG:
-    case goog.dom.TagName.INPUT:
-    case goog.dom.TagName.IFRAME:
-    case goog.dom.TagName.ISINDEX:
-    case goog.dom.TagName.KEYGEN:
-    case goog.dom.TagName.LINK:
-    case goog.dom.TagName.NOFRAMES:
-    case goog.dom.TagName.NOSCRIPT:
-    case goog.dom.TagName.META:
-    case goog.dom.TagName.OBJECT:
-    case goog.dom.TagName.PARAM:
-    case goog.dom.TagName.SCRIPT:
-    case goog.dom.TagName.SOURCE:
-    case goog.dom.TagName.STYLE:
-    case goog.dom.TagName.TRACK:
-    case goog.dom.TagName.WBR:
-      return false;
-  }
-  return true;
-};
+goog.i18n.bidi.DirectionalString.prototype.
+    implementsGoogI18nBidiDirectionalString;
 
 
 /**
- * Appends a child to a node.
- * @param {Node} parent Parent.
- * @param {Node} child Child.
+ * Retrieves this object's known direction (if any).
+ * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown.
  */
-goog.dom.appendChild = function(parent, child) {
-  parent.appendChild(child);
-};
+goog.i18n.bidi.DirectionalString.prototype.getDirection;
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Appends a node with text or other nodes.
- * @param {!Node} parent The node to append nodes to.
- * @param {...goog.dom.Appendable} var_args The things to append to the node.
- *     If this is a Node it is appended as is.
- *     If this is a string then a text node is appended.
- *     If this is an array like object then fields 0 to length - 1 are appended.
+ * @fileoverview The SafeUrl type and its builders.
+ *
+ * TODO(user): Link to document stating type contract.
  */
-goog.dom.append = function(parent, var_args) {
-  goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
-};
+
+goog.provide('goog.html.SafeUrl');
+
+goog.require('goog.asserts');
+goog.require('goog.i18n.bidi.Dir');
+goog.require('goog.i18n.bidi.DirectionalString');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
+
 
 
 /**
- * Removes all the child nodes on a DOM node.
- * @param {Node} node Node to remove children from.
+ * A string that is safe to use in URL context in DOM APIs and HTML documents.
+ *
+ * A SafeUrl is a string-like object that carries the security type contract
+ * that its value as a string will not cause untrusted script execution
+ * when evaluated as a hyperlink URL in a browser.
+ *
+ * Values of this type are guaranteed to be safe to use in URL/hyperlink
+ * contexts, such as, assignment to URL-valued DOM properties, or
+ * interpolation into a HTML template in URL context (e.g., inside a href
+ * attribute), in the sense that the use will not result in a
+ * Cross-Site-Scripting vulnerability.
+ *
+ * Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's
+ * contract does not guarantee that instances are safe to interpolate into HTML
+ * without appropriate escaping.
+ *
+ * Note also that this type's contract does not imply any guarantees regarding
+ * the resource the URL refers to.  In particular, SafeUrls are <b>not</b>
+ * safe to use in a context where the referred-to resource is interpreted as
+ * trusted code, e.g., as the src of a script tag.
+ *
+ * Instances of this type must be created via the factory methods
+ * ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}),
+ * etc and not by invoking its constructor.  The constructor intentionally
+ * takes no parameters and the type is immutable; hence only a default instance
+ * corresponding to the empty string can be obtained via constructor invocation.
+ *
+ * @see goog.html.SafeUrl#fromConstant
+ * @see goog.html.SafeUrl#from
+ * @see goog.html.SafeUrl#sanitize
+ * @constructor
+ * @final
+ * @struct
+ * @implements {goog.i18n.bidi.DirectionalString}
+ * @implements {goog.string.TypedString}
  */
-goog.dom.removeChildren = function(node) {
-  // Note: Iterations over live collections can be slow, this is the fastest
-  // we could find. The double parenthesis are used to prevent JsCompiler and
-  // strict warnings.
-  var child;
-  while ((child = node.firstChild)) {
-    node.removeChild(child);
-  }
+goog.html.SafeUrl = function() {
+  /**
+   * The contained value of this SafeUrl.  The field has a purposely ugly
+   * name to make (non-compiled) code that attempts to directly access this
+   * field stand out.
+   * @private {string}
+   */
+  this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
+
+  /**
+   * A type marker used to implement additional run-time type checking.
+   * @see goog.html.SafeUrl#unwrap
+   * @const
+   * @private
+   */
+  this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
+      goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
 };
 
 
 /**
- * Inserts a new node before an existing reference node (i.e. as the previous
- * sibling). If the reference node has no parent, then does nothing.
- * @param {Node} newNode Node to insert.
- * @param {Node} refNode Reference node to insert before.
+ * The innocuous string generated by goog.html.SafeUrl.sanitize when passed
+ * an unsafe URL.
+ *
+ * about:invalid is registered in
+ * http://www.w3.org/TR/css3-values/#about-invalid.
+ * http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to
+ * contain a fragment, which is not to be considered when determining if an
+ * about URL is well-known.
+ *
+ * Using about:invalid seems preferable to using a fixed data URL, since
+ * browsers might choose to not report CSP violations on it, as legitimate
+ * CSS function calls to attr() can result in this URL being produced. It is
+ * also a standard URL which matches exactly the semantics we need:
+ * "The about:invalid URI references a non-existent document with a generic
+ * error condition. It can be used when a URI is necessary, but the default
+ * value shouldn't be resolveable as any type of document".
+ *
+ * @const {string}
  */
-goog.dom.insertSiblingBefore = function(newNode, refNode) {
-  if (refNode.parentNode) {
-    refNode.parentNode.insertBefore(newNode, refNode);
-  }
-};
+goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez';
 
 
 /**
- * Inserts a new node after an existing reference node (i.e. as the next
- * sibling). If the reference node has no parent, then does nothing.
- * @param {Node} newNode Node to insert.
- * @param {Node} refNode Reference node to insert after.
+ * @override
+ * @const
  */
-goog.dom.insertSiblingAfter = function(newNode, refNode) {
-  if (refNode.parentNode) {
-    refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
-  }
-};
+goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
 
 
 /**
- * Insert a child at a given index. If index is larger than the number of child
- * nodes that the parent currently has, the node is inserted as the last child
- * node.
- * @param {Element} parent The element into which to insert the child.
- * @param {Node} child The element to insert.
- * @param {number} index The index at which to insert the new child node. Must
- *     not be negative.
+ * Returns this SafeUrl's value a string.
+ *
+ * IMPORTANT: In code where it is security relevant that an object's type is
+ * indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this
+ * method. If in doubt, assume that it's security relevant. In particular, note
+ * that goog.html functions which return a goog.html type do not guarantee that
+ * the returned instance is of the right type. For example:
+ *
+ * <pre>
+ * var fakeSafeHtml = new String('fake');
+ * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
+ * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
+ * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
+ * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
+ * // goog.html.SafeHtml.
+ * </pre>
+ *
+ * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
+ * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
+ * be appropriately escaped before embedding in a HTML document. Note that the
+ * required escaping is context-sensitive (e.g. a different escaping is
+ * required for embedding a URL in a style property within a style
+ * attribute, as opposed to embedding in a href attribute).
+ *
+ * @see goog.html.SafeUrl#unwrap
+ * @override
  */
-goog.dom.insertChildAt = function(parent, child, index) {
-  // Note that if the second argument is null, insertBefore
-  // will append the child at the end of the list of children.
-  parent.insertBefore(child, parent.childNodes[index] || null);
+goog.html.SafeUrl.prototype.getTypedStringValue = function() {
+  return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
 };
 
 
 /**
- * Removes a node from its parent.
- * @param {Node} node The node to remove.
- * @return {Node} The node removed if removed; else, null.
+ * @override
+ * @const
  */
-goog.dom.removeNode = function(node) {
-  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
-};
+goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
 
 
 /**
- * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
- * parent.
- * @param {Node} newNode Node to insert.
- * @param {Node} oldNode Node to replace.
+ * Returns this URLs directionality, which is always {@code LTR}.
+ * @override
  */
-goog.dom.replaceNode = function(newNode, oldNode) {
-  var parent = oldNode.parentNode;
-  if (parent) {
-    parent.replaceChild(newNode, oldNode);
-  }
+goog.html.SafeUrl.prototype.getDirection = function() {
+  return goog.i18n.bidi.Dir.LTR;
 };
 
 
+if (goog.DEBUG) {
+  /**
+   * Returns a debug string-representation of this value.
+   *
+   * To obtain the actual string value wrapped in a SafeUrl, use
+   * {@code goog.html.SafeUrl.unwrap}.
+   *
+   * @see goog.html.SafeUrl#unwrap
+   * @override
+   */
+  goog.html.SafeUrl.prototype.toString = function() {
+    return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
+        '}';
+  };
+}
+
+
 /**
- * Flattens an element. That is, removes it and replace it with its children.
- * Does nothing if the element is not in the document.
- * @param {Element} element The element to flatten.
- * @return {Element|undefined} The original element, detached from the document
- *     tree, sans children; or undefined, if the element was not in the document
- *     to begin with.
- */
-goog.dom.flattenElement = function(element) {
-  var child, parent = element.parentNode;
-  if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
-    // Use IE DOM method (supported by Opera too) if available
-    if (element.removeNode) {
-      return /** @type {Element} */ (element.removeNode(false));
-    } else {
-      // Move all children of the original node up one level.
-      while ((child = element.firstChild)) {
-        parent.insertBefore(child, element);
-      }
+ * Performs a runtime check that the provided object is indeed a SafeUrl
+ * object, and returns its value.
+ *
+ * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
+ * behavior of  browsers when interpreting URLs. Values of SafeUrl objects MUST
+ * be appropriately escaped before embedding in a HTML document. Note that the
+ * required escaping is context-sensitive (e.g. a different escaping is
+ * required for embedding a URL in a style property within a style
+ * attribute, as opposed to embedding in a href attribute).
+ *
+ * Note that the returned value does not necessarily correspond to the string
+ * with which the SafeUrl was constructed, since goog.html.SafeUrl.sanitize
+ * will percent-encode many characters.
+ *
+ * @param {!goog.html.SafeUrl} safeUrl The object to extract from.
+ * @return {string} The SafeUrl object's contained string, unless the run-time
+ *     type check fails. In that case, {@code unwrap} returns an innocuous
+ *     string, or, if assertions are enabled, throws
+ *     {@code goog.asserts.AssertionError}.
+ */
+goog.html.SafeUrl.unwrap = function(safeUrl) {
+  // Perform additional Run-time type-checking to ensure that safeUrl is indeed
+  // an instance of the expected type.  This provides some additional protection
+  // against security bugs due to application code that disables type checks.
+  // Specifically, the following checks are performed:
+  // 1. The object is an instance of the expected type.
+  // 2. The object is not an instance of a subclass.
+  // 3. The object carries a type marker for the expected type. "Faking" an
+  // object requires a reference to the type marker, which has names intended
+  // to stand out in code reviews.
+  if (safeUrl instanceof goog.html.SafeUrl &&
+      safeUrl.constructor === goog.html.SafeUrl &&
+      safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
+          goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
+    return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
+  } else {
+    goog.asserts.fail('expected object of type SafeUrl, got \'' +
+                      safeUrl + '\'');
+    return 'type_error:SafeUrl';
 
-      // Detach the original element.
-      return /** @type {Element} */ (goog.dom.removeNode(element));
-    }
   }
 };
 
 
 /**
- * Returns an array containing just the element children of the given element.
- * @param {Element} element The element whose element children we want.
- * @return {!(Array|NodeList)} An array or array-like list of just the element
- *     children of the given element.
+ * Creates a SafeUrl object from a compile-time constant string.
+ *
+ * Compile-time constant strings are inherently program-controlled and hence
+ * trusted.
+ *
+ * @param {!goog.string.Const} url A compile-time-constant string from which to
+ *         create a SafeUrl.
+ * @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}.
  */
-goog.dom.getChildren = function(element) {
-  // We check if the children attribute is supported for child elements
-  // since IE8 misuses the attribute by also including comments.
-  if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE &&
-      element.children != undefined) {
-    return element.children;
-  }
-  // Fall back to manually filtering the element's child nodes.
-  return goog.array.filter(element.childNodes, function(node) {
-    return node.nodeType == goog.dom.NodeType.ELEMENT;
-  });
+goog.html.SafeUrl.fromConstant = function(url) {
+  return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
+      goog.string.Const.unwrap(url));
 };
 
 
 /**
- * Returns the first child node that is an element.
- * @param {Node} node The node to get the first child element of.
- * @return {Element} The first child node of {@code node} that is an element.
+ * A pattern that recognizes a commonly useful subset of URLs that satisfy
+ * the SafeUrl contract.
+ *
+ * This regular expression matches a subset of URLs that will not cause script
+ * execution if used in URL context within a HTML document. Specifically, this
+ * regular expression matches if (comment from here on and regex copied from
+ * Soy's EscapingConventions):
+ * (1) Either a protocol in a whitelist (http, https, mailto).
+ * (2) or no protocol.  A protocol must be followed by a colon. The below
+ *     allows that by allowing colons only after one of the characters [/?#].
+ *     A colon after a hash (#) must be in the fragment.
+ *     Otherwise, a colon after a (?) must be in a query.
+ *     Otherwise, a colon after a single solidus (/) must be in a path.
+ *     Otherwise, a colon after a double solidus (//) must be in the authority
+ *     (before port).
+ *
+ * The pattern disallows &, used in HTML entity declarations before
+ * one of the characters in [/?#]. This disallows HTML entities used in the
+ * protocol name, which should never happen, e.g. "h&#116;tp" for "http".
+ * It also disallows HTML entities in the first path part of a relative path,
+ * e.g. "foo&lt;bar/baz".  Our existing escaping functions should not produce
+ * that. More importantly, it disallows masking of a colon,
+ * e.g. "javascript&#58;...".
+ *
+ * @private
+ * @const {!RegExp}
  */
-goog.dom.getFirstElementChild = function(node) {
-  if (node.firstElementChild != undefined) {
-    return /** @type {Element} */(node).firstElementChild;
-  }
-  return goog.dom.getNextElementNode_(node.firstChild, true);
-};
+goog.html.SAFE_URL_PATTERN_ = /^(?:(?:https?|mailto):|[^&:/?#]*(?:[/?#]|$))/i;
 
 
 /**
- * Returns the last child node that is an element.
- * @param {Node} node The node to get the last child element of.
- * @return {Element} The last child node of {@code node} that is an element.
+ * Creates a SafeUrl object from {@code url}. If {@code url} is a
+ * goog.html.SafeUrl then it is simply returned. Otherwise the input string is
+ * validated to match a pattern of commonly used safe URLs. The string is
+ * converted to UTF-8 and non-whitelisted characters are percent-encoded. The
+ * string wrapped by the created SafeUrl will thus contain only ASCII printable
+ * characters.
+ *
+ * {@code url} may be a URL with the http, https, or mailto scheme,
+ * or a relative URL (i.e., a URL without a scheme; specifically, a
+ * scheme-relative, absolute-path-relative, or path-relative URL).
+ *
+ * {@code url} is converted to UTF-8 and non-whitelisted characters are
+ * percent-encoded. Whitelisted characters are '%' and, from RFC 3986,
+ * unreserved characters and reserved characters, with the exception of '\'',
+ * '(' and ')'. This ensures the the SafeUrl contains only ASCII-printable
+ * characters and reduces the chance of security bugs were it to be
+ * interpolated into a specific context without the necessary escaping.
+ *
+ * If {@code url} fails validation or does not UTF-16 decode correctly
+ * (JavaScript strings are UTF-16 encoded), this function returns a SafeUrl
+ * object containing an innocuous string, goog.html.SafeUrl.INNOCUOUS_STRING.
+ *
+ * @see http://url.spec.whatwg.org/#concept-relative-url
+ * @param {string|!goog.string.TypedString} url The URL to validate.
+ * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
  */
-goog.dom.getLastElementChild = function(node) {
-  if (node.lastElementChild != undefined) {
-    return /** @type {Element} */(node).lastElementChild;
+goog.html.SafeUrl.sanitize = function(url) {
+  if (url instanceof goog.html.SafeUrl) {
+    return url;
   }
-  return goog.dom.getNextElementNode_(node.lastChild, false);
+  else if (url.implementsGoogStringTypedString) {
+    url = url.getTypedStringValue();
+  } else {
+    url = String(url);
+  }
+  if (!goog.html.SAFE_URL_PATTERN_.test(url)) {
+    url = goog.html.SafeUrl.INNOCUOUS_STRING;
+  } else {
+    url = goog.html.SafeUrl.normalize_(url);
+  }
+  return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
 };
 
 
 /**
- * Returns the first next sibling that is an element.
- * @param {Node} node The node to get the next sibling element of.
- * @return {Element} The next sibling of {@code node} that is an element.
+ * Normalizes {@code url} the UTF-8 encoding of url, using a whitelist of
+ * characters. Whitelisted characters are not percent-encoded.
+ * @param {string} url The URL to normalize.
+ * @return {string} The normalized URL.
+ * @private
  */
-goog.dom.getNextElementSibling = function(node) {
-  if (node.nextElementSibling != undefined) {
-    return /** @type {Element} */(node).nextElementSibling;
+goog.html.SafeUrl.normalize_ = function(url) {
+  try {
+    var normalized = encodeURI(url);
+  } catch (e) {  // Happens if url contains invalid surrogate sequences.
+    return goog.html.SafeUrl.INNOCUOUS_STRING;
   }
-  return goog.dom.getNextElementNode_(node.nextSibling, true);
+
+  return normalized.replace(
+      goog.html.SafeUrl.NORMALIZE_MATCHER_,
+      function(match) {
+        return goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_[match];
+      });
 };
 
 
 /**
- * Returns the first previous sibling that is an element.
- * @param {Node} node The node to get the previous sibling element of.
- * @return {Element} The first previous sibling of {@code node} that is
- *     an element.
+ * Matches characters and strings which need to be replaced in the string
+ * generated by encodeURI. Specifically:
+ *
+ * - '\'', '(' and ')' are not encoded. They are part of the reserved
+ *   characters group in RFC 3986 but only appear in the obsolete mark
+ *   production in Appendix D.2 of RFC 3986, so they can be encoded without
+ *   changing semantics.
+ * - '[' and ']' are encoded by encodeURI, despite being reserved characters
+ *   which can be used to represent IPv6 addresses. So they need to be decoded.
+ * - '%' is encoded by encodeURI. However, encoding '%' characters that are
+ *   already part of a valid percent-encoded sequence changes the semantics of a
+ *   URL, and hence we need to preserve them. Note that this may allow
+ *   non-encoded '%' characters to remain in the URL (i.e., occurrences of '%'
+ *   that are not part of a valid percent-encoded sequence, for example,
+ *   'ab%xy').
+ *
+ * @const {!RegExp}
+ * @private
  */
-goog.dom.getPreviousElementSibling = function(node) {
-  if (node.previousElementSibling != undefined) {
-    return /** @type {Element} */(node).previousElementSibling;
-  }
-  return goog.dom.getNextElementNode_(node.previousSibling, false);
+goog.html.SafeUrl.NORMALIZE_MATCHER_ = /[()']|%5B|%5D|%25/g;
+
+
+/**
+ * Map of replacements to be done in string generated by encodeURI.
+ * @const {!Object<string, string>}
+ * @private
+ */
+goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_ = {
+  '\'': '%27',
+  '(': '%28',
+  ')': '%29',
+  '%5B': '[',
+  '%5D': ']',
+  '%25': '%'
 };
 
 
 /**
- * Returns the first node that is an element in the specified direction,
- * starting with {@code node}.
- * @param {Node} node The node to get the next element from.
- * @param {boolean} forward Whether to look forwards or backwards.
- * @return {Element} The first element.
+ * Type marker for the SafeUrl type, used to implement additional run-time
+ * type checking.
+ * @const
  * @private
  */
-goog.dom.getNextElementNode_ = function(node, forward) {
-  while (node && node.nodeType != goog.dom.NodeType.ELEMENT) {
-    node = forward ? node.nextSibling : node.previousSibling;
-  }
+goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
 
-  return /** @type {Element} */ (node);
+
+/**
+ * Package-internal utility method to create SafeUrl instances.
+ *
+ * @param {string} url The string to initialize the SafeUrl object with.
+ * @return {!goog.html.SafeUrl} The initialized SafeUrl object.
+ * @package
+ */
+goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function(
+    url) {
+  var safeUrl = new goog.html.SafeUrl();
+  safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url;
+  return safeUrl;
 };
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Returns the next node in source order from the given node.
- * @param {Node} node The node.
- * @return {Node} The next node in the DOM tree, or null if this was the last
- *     node.
+ * @fileoverview The TrustedResourceUrl type and its builders.
+ *
+ * TODO(user): Link to document stating type contract.
  */
-goog.dom.getNextNode = function(node) {
-  if (!node) {
-    return null;
-  }
 
-  if (node.firstChild) {
-    return node.firstChild;
-  }
+goog.provide('goog.html.TrustedResourceUrl');
 
-  while (node && !node.nextSibling) {
-    node = node.parentNode;
-  }
+goog.require('goog.asserts');
+goog.require('goog.i18n.bidi.Dir');
+goog.require('goog.i18n.bidi.DirectionalString');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
 
-  return node ? node.nextSibling : null;
-};
 
 
 /**
- * Returns the previous node in source order from the given node.
- * @param {Node} node The node.
- * @return {Node} The previous node in the DOM tree, or null if this was the
- *     first node.
+ * A URL which is under application control and from which script, CSS, and
+ * other resources that represent executable code, can be fetched.
+ *
+ * Given that the URL can only be constructed from strings under application
+ * control and is used to load resources, bugs resulting in a malformed URL
+ * should not have a security impact and are likely to be easily detectable
+ * during testing. Given the wide number of non-RFC compliant URLs in use,
+ * stricter validation could prevent some applications from being able to use
+ * this type.
+ *
+ * Instances of this type must be created via the factory method,
+ * ({@code goog.html.TrustedResourceUrl.fromConstant}), and not by invoking its
+ * constructor. The constructor intentionally takes no parameters and the type
+ * is immutable; hence only a default instance corresponding to the empty
+ * string can be obtained via constructor invocation.
+ *
+ * @see goog.html.TrustedResourceUrl#fromConstant
+ * @constructor
+ * @final
+ * @struct
+ * @implements {goog.i18n.bidi.DirectionalString}
+ * @implements {goog.string.TypedString}
  */
-goog.dom.getPreviousNode = function(node) {
-  if (!node) {
-    return null;
-  }
-
-  if (!node.previousSibling) {
-    return node.parentNode;
-  }
-
-  node = node.previousSibling;
-  while (node && node.lastChild) {
-    node = node.lastChild;
-  }
+goog.html.TrustedResourceUrl = function() {
+  /**
+   * The contained value of this TrustedResourceUrl.  The field has a purposely
+   * ugly name to make (non-compiled) code that attempts to directly access this
+   * field stand out.
+   * @private {string}
+   */
+  this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = '';
 
-  return node;
+  /**
+   * A type marker used to implement additional run-time type checking.
+   * @see goog.html.TrustedResourceUrl#unwrap
+   * @const
+   * @private
+   */
+  this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
+      goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
 };
 
 
 /**
- * Whether the object looks like a DOM node.
- * @param {?} obj The object being tested for node likeness.
- * @return {boolean} Whether the object looks like a DOM node.
+ * @override
+ * @const
  */
-goog.dom.isNodeLike = function(obj) {
-  return goog.isObject(obj) && obj.nodeType > 0;
-};
+goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true;
 
 
 /**
- * Whether the object looks like an Element.
- * @param {?} obj The object being tested for Element likeness.
- * @return {boolean} Whether the object looks like an Element.
+ * Returns this TrustedResourceUrl's value as a string.
+ *
+ * IMPORTANT: In code where it is security relevant that an object's type is
+ * indeed {@code TrustedResourceUrl}, use
+ * {@code goog.html.TrustedResourceUrl.unwrap} instead of this method. If in
+ * doubt, assume that it's security relevant. In particular, note that
+ * goog.html functions which return a goog.html type do not guarantee that
+ * the returned instance is of the right type. For example:
+ *
+ * <pre>
+ * var fakeSafeHtml = new String('fake');
+ * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
+ * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
+ * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
+ * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
+ * // goog.html.SafeHtml.
+ * </pre>
+ *
+ * @see goog.html.TrustedResourceUrl#unwrap
+ * @override
  */
-goog.dom.isElement = function(obj) {
-  return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
+goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() {
+  return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
 };
 
 
 /**
- * Returns true if the specified value is a Window object. This includes the
- * global window for HTML pages, and iframe windows.
- * @param {?} obj Variable to test.
- * @return {boolean} Whether the variable is a window.
+ * @override
+ * @const
  */
-goog.dom.isWindow = function(obj) {
-  return goog.isObject(obj) && obj['window'] == obj;
-};
+goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString =
+    true;
 
 
 /**
- * Returns an element's parent, if it's an Element.
- * @param {Element} element The DOM element.
- * @return {Element} The parent, or null if not an Element.
+ * Returns this URLs directionality, which is always {@code LTR}.
+ * @override
  */
-goog.dom.getParentElement = function(element) {
-  var parent;
-  if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) {
-    var isIe9 = goog.userAgent.IE &&
-        goog.userAgent.isVersionOrHigher('9') &&
-        !goog.userAgent.isVersionOrHigher('10');
-    // SVG elements in IE9 can't use the parentElement property.
-    // goog.global['SVGElement'] is not defined in IE9 quirks mode.
-    if (!(isIe9 && goog.global['SVGElement'] &&
-        element instanceof goog.global['SVGElement'])) {
-      parent = element.parentElement;
-      if (parent) {
-        return parent;
-      }
-    }
-  }
-  parent = element.parentNode;
-  return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null;
+goog.html.TrustedResourceUrl.prototype.getDirection = function() {
+  return goog.i18n.bidi.Dir.LTR;
 };
 
 
-/**
- * Whether a node contains another node.
- * @param {Node} parent The node that should contain the other node.
- * @param {Node} descendant The node to test presence of.
- * @return {boolean} Whether the parent node contains the descendent node.
- */
-goog.dom.contains = function(parent, descendant) {
-  // We use browser specific methods for this if available since it is faster
-  // that way.
+if (goog.DEBUG) {
+  /**
+   * Returns a debug string-representation of this value.
+   *
+   * To obtain the actual string value wrapped in a TrustedResourceUrl, use
+   * {@code goog.html.TrustedResourceUrl.unwrap}.
+   *
+   * @see goog.html.TrustedResourceUrl#unwrap
+   * @override
+   */
+  goog.html.TrustedResourceUrl.prototype.toString = function() {
+    return 'TrustedResourceUrl{' +
+        this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '}';
+  };
+}
 
-  // IE DOM
-  if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
-    return parent == descendant || parent.contains(descendant);
-  }
 
-  // W3C DOM Level 3
-  if (typeof parent.compareDocumentPosition != 'undefined') {
-    return parent == descendant ||
-        Boolean(parent.compareDocumentPosition(descendant) & 16);
-  }
+/**
+ * Performs a runtime check that the provided object is indeed a
+ * TrustedResourceUrl object, and returns its value.
+ *
+ * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to
+ *     extract from.
+ * @return {string} The trustedResourceUrl object's contained string, unless
+ *     the run-time type check fails. In that case, {@code unwrap} returns an
+ *     innocuous string, or, if assertions are enabled, throws
+ *     {@code goog.asserts.AssertionError}.
+ */
+goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) {
+  // Perform additional Run-time type-checking to ensure that
+  // trustedResourceUrl is indeed an instance of the expected type.  This
+  // provides some additional protection against security bugs due to
+  // application code that disables type checks.
+  // Specifically, the following checks are performed:
+  // 1. The object is an instance of the expected type.
+  // 2. The object is not an instance of a subclass.
+  // 3. The object carries a type marker for the expected type. "Faking" an
+  // object requires a reference to the type marker, which has names intended
+  // to stand out in code reviews.
+  if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl &&
+      trustedResourceUrl.constructor === goog.html.TrustedResourceUrl &&
+      trustedResourceUrl
+          .TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
+              goog.html.TrustedResourceUrl
+                  .TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
+    return trustedResourceUrl
+        .privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
+  } else {
+    goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' +
+                      trustedResourceUrl + '\'');
+    return 'type_error:TrustedResourceUrl';
 
-  // W3C DOM Level 1
-  while (descendant && parent != descendant) {
-    descendant = descendant.parentNode;
   }
-  return descendant == parent;
 };
 
 
 /**
- * Compares the document order of two nodes, returning 0 if they are the same
- * node, a negative number if node1 is before node2, and a positive number if
- * node2 is before node1.  Note that we compare the order the tags appear in the
- * document so in the tree <b><i>text</i></b> the B node is considered to be
- * before the I node.
+ * Creates a TrustedResourceUrl object from a compile-time constant string.
  *
- * @param {Node} node1 The first node to compare.
- * @param {Node} node2 The second node to compare.
- * @return {number} 0 if the nodes are the same node, a negative number if node1
- *     is before node2, and a positive number if node2 is before node1.
+ * Compile-time constant strings are inherently program-controlled and hence
+ * trusted.
+ *
+ * @param {!goog.string.Const} url A compile-time-constant string from which to
+ *     create a TrustedResourceUrl.
+ * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object
+ *     initialized to {@code url}.
  */
-goog.dom.compareNodeOrder = function(node1, node2) {
-  // Fall out quickly for equality.
-  if (node1 == node2) {
-    return 0;
-  }
+goog.html.TrustedResourceUrl.fromConstant = function(url) {
+  return goog.html.TrustedResourceUrl
+      .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(
+          goog.string.Const.unwrap(url));
+};
 
-  // Use compareDocumentPosition where available
-  if (node1.compareDocumentPosition) {
-    // 4 is the bitmask for FOLLOWS.
-    return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
-  }
 
-  // Special case for document nodes on IE 7 and 8.
-  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
-    if (node1.nodeType == goog.dom.NodeType.DOCUMENT) {
-      return -1;
-    }
-    if (node2.nodeType == goog.dom.NodeType.DOCUMENT) {
-      return 1;
-    }
-  }
+/**
+ * Type marker for the TrustedResourceUrl type, used to implement additional
+ * run-time type checking.
+ * @const
+ * @private
+ */
+goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
 
-  // Process in IE using sourceIndex - we check to see if the first node has
-  // a source index or if its parent has one.
-  if ('sourceIndex' in node1 ||
-      (node1.parentNode && 'sourceIndex' in node1.parentNode)) {
-    var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
-    var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
 
-    if (isElement1 && isElement2) {
-      return node1.sourceIndex - node2.sourceIndex;
-    } else {
-      var parent1 = node1.parentNode;
-      var parent2 = node2.parentNode;
+/**
+ * Package-internal utility method to create TrustedResourceUrl instances.
+ *
+ * @param {string} url The string to initialize the TrustedResourceUrl object
+ *     with.
+ * @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl
+ *     object.
+ * @package
+ */
+goog.html.TrustedResourceUrl.
+    createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) {
+  var trustedResourceUrl = new goog.html.TrustedResourceUrl();
+  trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ =
+      url;
+  return trustedResourceUrl;
+};
 
-      if (parent1 == parent2) {
-        return goog.dom.compareSiblingOrder_(node1, node2);
-      }
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-      if (!isElement1 && goog.dom.contains(parent1, node2)) {
-        return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
-      }
 
+/**
+ * @fileoverview The SafeHtml type and its builders.
+ *
+ * TODO(user): Link to document stating type contract.
+ */
 
-      if (!isElement2 && goog.dom.contains(parent2, node1)) {
-        return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
-      }
+goog.provide('goog.html.SafeHtml');
 
-      return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
-             (isElement2 ? node2.sourceIndex : parent2.sourceIndex);
-    }
-  }
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.tags');
+goog.require('goog.html.SafeStyle');
+goog.require('goog.html.SafeUrl');
+goog.require('goog.html.TrustedResourceUrl');
+goog.require('goog.i18n.bidi.Dir');
+goog.require('goog.i18n.bidi.DirectionalString');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
 
-  // For Safari, we compare ranges.
-  var doc = goog.dom.getOwnerDocument(node1);
 
-  var range1, range2;
-  range1 = doc.createRange();
-  range1.selectNode(node1);
-  range1.collapse(true);
 
-  range2 = doc.createRange();
-  range2.selectNode(node2);
-  range2.collapse(true);
+/**
+ * A string that is safe to use in HTML context in DOM APIs and HTML documents.
+ *
+ * A SafeHtml is a string-like object that carries the security type contract
+ * that its value as a string will not cause untrusted script execution when
+ * evaluated as HTML in a browser.
+ *
+ * Values of this type are guaranteed to be safe to use in HTML contexts,
+ * such as, assignment to the innerHTML DOM property, or interpolation into
+ * a HTML template in HTML PC_DATA context, in the sense that the use will not
+ * result in a Cross-Site-Scripting vulnerability.
+ *
+ * Instances of this type must be created via the factory methods
+ * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}),
+ * etc and not by invoking its constructor.  The constructor intentionally
+ * takes no parameters and the type is immutable; hence only a default instance
+ * corresponding to the empty string can be obtained via constructor invocation.
+ *
+ * @see goog.html.SafeHtml#create
+ * @see goog.html.SafeHtml#htmlEscape
+ * @constructor
+ * @final
+ * @struct
+ * @implements {goog.i18n.bidi.DirectionalString}
+ * @implements {goog.string.TypedString}
+ */
+goog.html.SafeHtml = function() {
+  /**
+   * The contained value of this SafeHtml.  The field has a purposely ugly
+   * name to make (non-compiled) code that attempts to directly access this
+   * field stand out.
+   * @private {string}
+   */
+  this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
 
-  return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END,
-      range2);
+  /**
+   * A type marker used to implement additional run-time type checking.
+   * @see goog.html.SafeHtml#unwrap
+   * @const
+   * @private
+   */
+  this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
+      goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
+
+  /**
+   * This SafeHtml's directionality, or null if unknown.
+   * @private {?goog.i18n.bidi.Dir}
+   */
+  this.dir_ = null;
 };
 
 
 /**
- * Utility function to compare the position of two nodes, when
- * {@code textNode}'s parent is an ancestor of {@code node}.  If this entry
- * condition is not met, this function will attempt to reference a null object.
- * @param {Node} textNode The textNode to compare.
- * @param {Node} node The node to compare.
- * @return {number} -1 if node is before textNode, +1 otherwise.
- * @private
+ * @override
+ * @const
  */
-goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
-  var parent = textNode.parentNode;
-  if (parent == node) {
-    // If textNode is a child of node, then node comes first.
-    return -1;
-  }
-  var sibling = node;
-  while (sibling.parentNode != parent) {
-    sibling = sibling.parentNode;
-  }
-  return goog.dom.compareSiblingOrder_(sibling, textNode);
+goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
+
+
+/** @override */
+goog.html.SafeHtml.prototype.getDirection = function() {
+  return this.dir_;
 };
 
 
 /**
- * Utility function to compare the position of two nodes known to be non-equal
- * siblings.
- * @param {Node} node1 The first node to compare.
- * @param {Node} node2 The second node to compare.
- * @return {number} -1 if node1 is before node2, +1 otherwise.
- * @private
+ * @override
+ * @const
  */
-goog.dom.compareSiblingOrder_ = function(node1, node2) {
-  var s = node2;
-  while ((s = s.previousSibling)) {
-    if (s == node1) {
-      // We just found node1 before node2.
-      return -1;
-    }
-  }
-
-  // Since we didn't find it, node1 must be after node2.
-  return 1;
-};
+goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
 
 
 /**
- * Find the deepest common ancestor of the given nodes.
- * @param {...Node} var_args The nodes to find a common ancestor of.
- * @return {Node} The common ancestor of the nodes, or null if there is none.
- *     null will only be returned if two or more of the nodes are from different
- *     documents.
+ * Returns this SafeHtml's value a string.
+ *
+ * IMPORTANT: In code where it is security relevant that an object's type is
+ * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of
+ * this method. If in doubt, assume that it's security relevant. In particular,
+ * note that goog.html functions which return a goog.html type do not guarantee
+ * that the returned instance is of the right type. For example:
+ *
+ * <pre>
+ * var fakeSafeHtml = new String('fake');
+ * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
+ * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
+ * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
+ * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
+ * // instanceof goog.html.SafeHtml.
+ * </pre>
+ *
+ * @see goog.html.SafeHtml#unwrap
+ * @override
  */
-goog.dom.findCommonAncestor = function(var_args) {
-  var i, count = arguments.length;
-  if (!count) {
-    return null;
-  } else if (count == 1) {
-    return arguments[0];
-  }
+goog.html.SafeHtml.prototype.getTypedStringValue = function() {
+  return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
+};
 
-  var paths = [];
-  var minLength = Infinity;
-  for (i = 0; i < count; i++) {
-    // Compute the list of ancestors.
-    var ancestors = [];
-    var node = arguments[i];
-    while (node) {
-      ancestors.unshift(node);
-      node = node.parentNode;
-    }
 
-    // Save the list for comparison.
-    paths.push(ancestors);
-    minLength = Math.min(minLength, ancestors.length);
-  }
-  var output = null;
-  for (i = 0; i < minLength; i++) {
-    var first = paths[0][i];
-    for (var j = 1; j < count; j++) {
-      if (first != paths[j][i]) {
-        return output;
-      }
-    }
-    output = first;
-  }
-  return output;
-};
+if (goog.DEBUG) {
+  /**
+   * Returns a debug string-representation of this value.
+   *
+   * To obtain the actual string value wrapped in a SafeHtml, use
+   * {@code goog.html.SafeHtml.unwrap}.
+   *
+   * @see goog.html.SafeHtml#unwrap
+   * @override
+   */
+  goog.html.SafeHtml.prototype.toString = function() {
+    return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
+        '}';
+  };
+}
 
 
 /**
- * Returns the owner document for a node.
- * @param {Node|Window} node The node to get the document for.
- * @return {!Document} The document owning the node.
- */
-goog.dom.getOwnerDocument = function(node) {
-  // TODO(nnaze): Update param signature to be non-nullable.
-  goog.asserts.assert(node, 'Node cannot be null or undefined.');
-  return /** @type {!Document} */ (
-      node.nodeType == goog.dom.NodeType.DOCUMENT ? node :
-      node.ownerDocument || node.document);
+ * Performs a runtime check that the provided object is indeed a SafeHtml
+ * object, and returns its value.
+ * @param {!goog.html.SafeHtml} safeHtml The object to extract from.
+ * @return {string} The SafeHtml object's contained string, unless the run-time
+ *     type check fails. In that case, {@code unwrap} returns an innocuous
+ *     string, or, if assertions are enabled, throws
+ *     {@code goog.asserts.AssertionError}.
+ */
+goog.html.SafeHtml.unwrap = function(safeHtml) {
+  // Perform additional run-time type-checking to ensure that safeHtml is indeed
+  // an instance of the expected type.  This provides some additional protection
+  // against security bugs due to application code that disables type checks.
+  // Specifically, the following checks are performed:
+  // 1. The object is an instance of the expected type.
+  // 2. The object is not an instance of a subclass.
+  // 3. The object carries a type marker for the expected type. "Faking" an
+  // object requires a reference to the type marker, which has names intended
+  // to stand out in code reviews.
+  if (safeHtml instanceof goog.html.SafeHtml &&
+      safeHtml.constructor === goog.html.SafeHtml &&
+      safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
+          goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
+    return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
+  } else {
+    goog.asserts.fail('expected object of type SafeHtml, got \'' +
+                      safeHtml + '\'');
+    return 'type_error:SafeHtml';
+  }
 };
 
 
 /**
- * Cross-browser function for getting the document element of a frame or iframe.
- * @param {Element} frame Frame element.
- * @return {!Document} The frame content document.
+ * Shorthand for union of types that can sensibly be converted to strings
+ * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString).
+ * @private
+ * @typedef {string|number|boolean|!goog.string.TypedString|
+ *           !goog.i18n.bidi.DirectionalString}
  */
-goog.dom.getFrameContentDocument = function(frame) {
-  var doc = frame.contentDocument || frame.contentWindow.document;
-  return doc;
-};
+goog.html.SafeHtml.TextOrHtml_;
 
 
 /**
- * Cross-browser function for getting the window of a frame or iframe.
- * @param {Element} frame Frame element.
- * @return {Window} The window associated with the given frame.
+ * Returns HTML-escaped text as a SafeHtml object.
+ *
+ * If text is of a type that implements
+ * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new
+ * {@code SafeHtml} object is set to {@code text}'s directionality, if known.
+ * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e.,
+ * {@code null}).
+ *
+ * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
+ *     the parameter is of type SafeHtml it is returned directly (no escaping
+ *     is done).
+ * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
  */
-goog.dom.getFrameContentWindow = function(frame) {
-  return frame.contentWindow ||
-      goog.dom.getWindow(goog.dom.getFrameContentDocument(frame));
+goog.html.SafeHtml.htmlEscape = function(textOrHtml) {
+  if (textOrHtml instanceof goog.html.SafeHtml) {
+    return textOrHtml;
+  }
+  var dir = null;
+  if (textOrHtml.implementsGoogI18nBidiDirectionalString) {
+    dir = textOrHtml.getDirection();
+  }
+  var textAsString;
+  if (textOrHtml.implementsGoogStringTypedString) {
+    textAsString = textOrHtml.getTypedStringValue();
+  } else {
+    textAsString = String(textOrHtml);
+  }
+  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+      goog.string.htmlEscape(textAsString), dir);
 };
 
 
 /**
- * Sets the text content of a node, with cross-browser support.
- * @param {Node} node The node to change the text content of.
- * @param {string|number} text The value that should replace the node's content.
+ * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
+ * &lt;br&gt;.
+ * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
+ *     the parameter is of type SafeHtml it is returned directly (no escaping
+ *     is done).
+ * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
  */
-goog.dom.setTextContent = function(node, text) {
-  goog.asserts.assert(node != null,
-      'goog.dom.setTextContent expects a non-null value for node');
-
-  if ('textContent' in node) {
-    node.textContent = text;
-  } else if (node.nodeType == goog.dom.NodeType.TEXT) {
-    node.data = text;
-  } else if (node.firstChild &&
-             node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
-    // If the first child is a text node we just change its data and remove the
-    // rest of the children.
-    while (node.lastChild != node.firstChild) {
-      node.removeChild(node.lastChild);
-    }
-    node.firstChild.data = text;
-  } else {
-    goog.dom.removeChildren(node);
-    var doc = goog.dom.getOwnerDocument(node);
-    node.appendChild(doc.createTextNode(String(text)));
+goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) {
+  if (textOrHtml instanceof goog.html.SafeHtml) {
+    return textOrHtml;
   }
+  var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
+  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+      goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)),
+      html.getDirection());
 };
 
 
 /**
- * Gets the outerHTML of a node, which islike innerHTML, except that it
- * actually contains the HTML of the node itself.
- * @param {Element} element The element to get the HTML of.
- * @return {string} The outerHTML of the given element.
+ * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
+ * &lt;br&gt; and escaping whitespace to preserve spatial formatting. Character
+ * entity #160 is used to make it safer for XML.
+ * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
+ *     the parameter is of type SafeHtml it is returned directly (no escaping
+ *     is done).
+ * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
  */
-goog.dom.getOuterHtml = function(element) {
-  // IE, Opera and WebKit all have outerHTML.
-  if ('outerHTML' in element) {
-    return element.outerHTML;
-  } else {
-    var doc = goog.dom.getOwnerDocument(element);
-    var div = doc.createElement('div');
-    div.appendChild(element.cloneNode(true));
-    return div.innerHTML;
+goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(
+    textOrHtml) {
+  if (textOrHtml instanceof goog.html.SafeHtml) {
+    return textOrHtml;
   }
+  var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
+  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+      goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)),
+      html.getDirection());
 };
 
 
 /**
- * Finds the first descendant node that matches the filter function, using
- * a depth first search. This function offers the most general purpose way
- * of finding a matching element. You may also wish to consider
- * {@code goog.dom.query} which can express many matching criteria using
- * CSS selector expressions. These expressions often result in a more
- * compact representation of the desired result.
- * @see goog.dom.query
+ * Coerces an arbitrary object into a SafeHtml object.
  *
- * @param {Node} root The root of the tree to search.
- * @param {function(Node) : boolean} p The filter function.
- * @return {Node|undefined} The found node or undefined if none is found.
+ * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same
+ * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and
+ * HTML-escaped. If {@code textOrHtml} is of a type that implements
+ * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is
+ * preserved.
+ *
+ * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to
+ *     coerce.
+ * @return {!goog.html.SafeHtml} The resulting SafeHtml object.
+ * @deprecated Use goog.html.SafeHtml.htmlEscape.
  */
-goog.dom.findNode = function(root, p) {
-  var rv = [];
-  var found = goog.dom.findNodes_(root, p, rv, true);
-  return found ? rv[0] : undefined;
-};
+goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;
 
 
 /**
- * Finds all the descendant nodes that match the filter function, using a
- * a depth first search. This function offers the most general-purpose way
- * of finding a set of matching elements. You may also wish to consider
- * {@code goog.dom.query} which can express many matching criteria using
- * CSS selector expressions. These expressions often result in a more
- * compact representation of the desired result.
-
- * @param {Node} root The root of the tree to search.
- * @param {function(Node) : boolean} p The filter function.
- * @return {!Array.<!Node>} The found nodes or an empty array if none are found.
+ * @const
+ * @private
  */
-goog.dom.findNodes = function(root, p) {
-  var rv = [];
-  goog.dom.findNodes_(root, p, rv, false);
-  return rv;
-};
+goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
 
 
 /**
- * Finds the first or all the descendant nodes that match the filter function,
- * using a depth first search.
- * @param {Node} root The root of the tree to search.
- * @param {function(Node) : boolean} p The filter function.
- * @param {!Array.<!Node>} rv The found nodes are added to this array.
- * @param {boolean} findOne If true we exit after the first found node.
- * @return {boolean} Whether the search is complete or not. True in case findOne
- *     is true and the node is found. False otherwise.
- * @private
+ * Set of attributes containing URL as defined at
+ * http://www.w3.org/TR/html5/index.html#attributes-1.
+ * @private @const {!Object<string,boolean>}
  */
-goog.dom.findNodes_ = function(root, p, rv, findOne) {
-  if (root != null) {
-    var child = root.firstChild;
-    while (child) {
-      if (p(child)) {
-        rv.push(child);
-        if (findOne) {
-          return true;
-        }
-      }
-      if (goog.dom.findNodes_(child, p, rv, findOne)) {
-        return true;
-      }
-      child = child.nextSibling;
-    }
-  }
-  return false;
-};
+goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet('action', 'cite',
+    'data', 'formaction', 'href', 'manifest', 'poster', 'src');
 
 
 /**
- * Map of tags whose content to ignore when calculating text length.
- * @type {Object}
- * @private
+ * Tags which are unsupported via create(). They might be supported via a
+ * tag-specific create method. These are tags which might require a
+ * TrustedResourceUrl in one of their attributes or a restricted type for
+ * their content.
+ * @private @const {!Object<string,boolean>}
  */
-goog.dom.TAGS_TO_IGNORE_ = {
-  'SCRIPT': 1,
-  'STYLE': 1,
-  'HEAD': 1,
-  'IFRAME': 1,
-  'OBJECT': 1
-};
+goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(
+    'embed', 'iframe', 'link', 'object', 'script', 'style', 'template');
 
 
 /**
- * Map of tags which have predefined values with regard to whitespace.
- * @type {Object}
+ * @typedef {string|number|goog.string.TypedString|
+ *     goog.html.SafeStyle.PropertyMap}
  * @private
  */
-goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};
+goog.html.SafeHtml.AttributeValue_;
 
 
 /**
- * Returns true if the element has a tab index that allows it to receive
- * keyboard focus (tabIndex >= 0), false otherwise.  Note that some elements
- * natively support keyboard focus, even if they have no tab index.
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element has a tab index that allows keyboard
- *     focus.
- * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
- */
-goog.dom.isFocusableTabIndex = function(element) {
-  return goog.dom.hasSpecifiedTabIndex_(element) &&
-         goog.dom.isTabIndexFocusable_(element);
+ * Creates a SafeHtml content consisting of a tag with optional attributes and
+ * optional content.
+ *
+ * For convenience tag names and attribute names are accepted as regular
+ * strings, instead of goog.string.Const. Nevertheless, you should not pass
+ * user-controlled values to these parameters. Note that these parameters are
+ * syntactically validated at runtime, and invalid values will result in
+ * an exception.
+ *
+ * Example usage:
+ *
+ * goog.html.SafeHtml.create('br');
+ * goog.html.SafeHtml.create('div', {'class': 'a'});
+ * goog.html.SafeHtml.create('p', {}, 'a');
+ * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br'));
+ *
+ * goog.html.SafeHtml.create('span', {
+ *   'style': {'margin': '0'}
+ * });
+ *
+ * To guarantee SafeHtml's type contract is upheld there are restrictions on
+ * attribute values and tag names.
+ *
+ * - For attributes which contain script code (on*), a goog.string.Const is
+ *   required.
+ * - For attributes which contain style (style), a goog.html.SafeStyle or a
+ *   goog.html.SafeStyle.PropertyMap is required.
+ * - For attributes which are interpreted as URLs (e.g. src, href) a
+ *   goog.html.SafeUrl or goog.string.Const is required.
+ * - For tags which can load code, more specific goog.html.SafeHtml.create*()
+ *   functions must be used. Tags which can load code and are not supported by
+ *   this function are embed, iframe, link, object, script, style, and template.
+ *
+ * @param {string} tagName The name of the tag. Only tag names consisting of
+ *     [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed.
+ * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
+ *     opt_attributes Mapping from attribute names to their values. Only
+ *     attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
+ *     undefined causes the attribute to be omitted.
+ * @param {!goog.html.SafeHtml.TextOrHtml_|
+ *     !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
+ *     HTML-escape and put inside the tag. This must be empty for void tags
+ *     like <br>. Array elements are concatenated.
+ * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
+ * @throws {Error} If invalid tag name, attribute name, or attribute value is
+ *     provided.
+ * @throws {goog.asserts.AssertionError} If content for void tag is provided.
+ */
+goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {
+  if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {
+    throw Error('Invalid tag name <' + tagName + '>.');
+  }
+  if (tagName.toLowerCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {
+    throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');
+  }
+  return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
+      tagName, opt_attributes, opt_content);
+};
+
+
+/**
+ * Creates a SafeHtml representing an iframe tag.
+ *
+ * By default the sandbox attribute is set to an empty value, which is the most
+ * secure option, as it confers the iframe the least privileges. If this
+ * is too restrictive then granting individual privileges is the preferable
+ * option. Unsetting the attribute entirely is the least secure option and
+ * should never be done unless it's stricly necessary.
+ *
+ * @param {goog.html.TrustedResourceUrl=} opt_src The value of the src
+ *     attribute. If null or undefined src will not be set.
+ * @param {goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.
+ *     If null or undefined srcdoc will not be set.
+ * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
+ *     opt_attributes Mapping from attribute names to their values. Only
+ *     attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
+ *     undefined causes the attribute to be omitted.
+ * @param {!goog.html.SafeHtml.TextOrHtml_|
+ *     !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
+ *     HTML-escape and put inside the tag. Array elements are concatenated.
+ * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
+ * @throws {Error} If invalid tag name, attribute name, or attribute value is
+ *     provided. If opt_attributes contains the src or srcdoc attributes.
+ */
+goog.html.SafeHtml.createIframe = function(
+    opt_src, opt_srcdoc, opt_attributes, opt_content) {
+  var fixedAttributes = {};
+  fixedAttributes['src'] = opt_src || null;
+  fixedAttributes['srcdoc'] = opt_srcdoc || null;
+  var defaultAttributes = {'sandbox': ''};
+  var attributes = goog.html.SafeHtml.combineAttributes(
+      fixedAttributes, defaultAttributes, opt_attributes);
+  return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
+      'iframe', attributes, opt_content);
+};
+
+
+/**
+ * @param {string} tagName The tag name.
+ * @param {string} name The attribute name.
+ * @param {!goog.html.SafeHtml.AttributeValue_} value The attribute value.
+ * @return {string} A "name=value" string.
+ * @throws {Error} If attribute value is unsafe for the given tag and attribute.
+ * @private
+ */
+goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {
+  // If it's goog.string.Const, allow any valid attribute name.
+  if (value instanceof goog.string.Const) {
+    value = goog.string.Const.unwrap(value);
+  } else if (name.toLowerCase() == 'style') {
+    value = goog.html.SafeHtml.getStyleValue_(value);
+  } else if (/^on/i.test(name)) {
+    // TODO(jakubvrana): Disallow more attributes with a special meaning.
+    throw Error('Attribute "' + name +
+        '" requires goog.string.Const value, "' + value + '" given.');
+  // URL attributes handled differently accroding to tag.
+  } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {
+    if (value instanceof goog.html.TrustedResourceUrl) {
+      value = goog.html.TrustedResourceUrl.unwrap(value);
+    } else if (value instanceof goog.html.SafeUrl) {
+      value = goog.html.SafeUrl.unwrap(value);
+    } else {
+      // TODO(user): Allow strings and sanitize them automatically,
+      // so that it's consistent with accepting a map directly for "style".
+      throw Error('Attribute "' + name + '" on tag "' + tagName +
+          '" requires goog.html.SafeUrl or goog.string.Const value, "' +
+          value + '" given.');
+    }
+  }
+
+  // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require
+  // HTML-escaping.
+  if (value.implementsGoogStringTypedString) {
+    // Ok to call getTypedStringValue() since there's no reliance on the type
+    // contract for security here.
+    value = value.getTypedStringValue();
+  }
+
+  goog.asserts.assert(goog.isString(value) || goog.isNumber(value),
+      'String or number value expected, got ' +
+      (typeof value) + ' with value: ' + value);
+  return name + '="' + goog.string.htmlEscape(String(value)) + '"';
 };
 
 
 /**
- * Enables or disables keyboard focus support on the element via its tab index.
- * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
- * (or elements that natively support keyboard focus, like form elements) can
- * receive keyboard focus.  See http://go/tabindex for more info.
- * @param {Element} element Element whose tab index is to be changed.
- * @param {boolean} enable Whether to set or remove a tab index on the element
- *     that supports keyboard focus.
+ * Gets value allowed in "style" attribute.
+ * @param {goog.html.SafeHtml.AttributeValue_} value It could be SafeStyle or a
+ *     map which will be passed to goog.html.SafeStyle.create.
+ * @return {string} Unwrapped value.
+ * @throws {Error} If string value is given.
+ * @private
  */
-goog.dom.setFocusableTabIndex = function(element, enable) {
-  if (enable) {
-    element.tabIndex = 0;
-  } else {
-    // Set tabIndex to -1 first, then remove it. This is a workaround for
-    // Safari (confirmed in version 4 on Windows). When removing the attribute
-    // without setting it to -1 first, the element remains keyboard focusable
-    // despite not having a tabIndex attribute anymore.
-    element.tabIndex = -1;
-    element.removeAttribute('tabIndex'); // Must be camelCase!
+goog.html.SafeHtml.getStyleValue_ = function(value) {
+  if (!goog.isObject(value)) {
+    throw Error('The "style" attribute requires goog.html.SafeStyle or map ' +
+        'of style properties, ' + (typeof value) + ' given: ' + value);
   }
+  if (!(value instanceof goog.html.SafeStyle)) {
+    // Process the property bag into a style object.
+    value = goog.html.SafeStyle.create(value);
+  }
+  return goog.html.SafeStyle.unwrap(value);
 };
 
 
 /**
- * Returns true if the element can be focused, i.e. it has a tab index that
- * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
- * that natively supports keyboard focus.
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element allows keyboard focus.
+ * Creates a SafeHtml content with known directionality consisting of a tag with
+ * optional attributes and optional content.
+ * @param {!goog.i18n.bidi.Dir} dir Directionality.
+ * @param {string} tagName
+ * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
+ * @param {!goog.html.SafeHtml.TextOrHtml_|
+ *     !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content
+ * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  */
-goog.dom.isFocusable = function(element) {
-  var focusable;
-  // Some elements can have unspecified tab index and still receive focus.
-  if (goog.dom.nativelySupportsFocus_(element)) {
-    // Make sure the element is not disabled ...
-    focusable = !element.disabled &&
-        // ... and if a tab index is specified, it allows focus.
-        (!goog.dom.hasSpecifiedTabIndex_(element) ||
-         goog.dom.isTabIndexFocusable_(element));
-  } else {
-    focusable = goog.dom.isFocusableTabIndex(element);
-  }
-
-  // IE requires elements to be visible in order to focus them.
-  return focusable && goog.userAgent.IE ?
-             goog.dom.hasNonZeroBoundingRect_(element) : focusable;
+goog.html.SafeHtml.createWithDir = function(dir, tagName, opt_attributes,
+    opt_content) {
+  var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
+  html.dir_ = dir;
+  return html;
 };
 
 
 /**
- * Returns true if the element has a specified tab index.
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element has a specified tab index.
- * @private
+ * Creates a new SafeHtml object by concatenating values.
+ * @param {...(!goog.html.SafeHtml.TextOrHtml_|
+ *     !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.
+ * @return {!goog.html.SafeHtml}
  */
-goog.dom.hasSpecifiedTabIndex_ = function(element) {
-  // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(),
-  // which returns an object with a 'specified' property if tabIndex is
-  // specified.  This works on other browsers, too.
-  var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
-  return goog.isDefAndNotNull(attrNode) && attrNode.specified;
+goog.html.SafeHtml.concat = function(var_args) {
+  var dir = goog.i18n.bidi.Dir.NEUTRAL;
+  var content = '';
+
+  /**
+   * @param {!goog.html.SafeHtml.TextOrHtml_|
+   *     !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
+   */
+  var addArgument = function(argument) {
+    if (goog.isArray(argument)) {
+      goog.array.forEach(argument, addArgument);
+    } else {
+      var html = goog.html.SafeHtml.htmlEscape(argument);
+      content += goog.html.SafeHtml.unwrap(html);
+      var htmlDir = html.getDirection();
+      if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
+        dir = htmlDir;
+      } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
+        dir = null;
+      }
+    }
+  };
+
+  goog.array.forEach(arguments, addArgument);
+  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+      content, dir);
 };
 
 
 /**
- * Returns true if the element's tab index allows the element to be focused.
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element's tab index allows focus.
- * @private
+ * Creates a new SafeHtml object with known directionality by concatenating the
+ * values.
+ * @param {!goog.i18n.bidi.Dir} dir Directionality.
+ * @param {...!goog.html.SafeHtml.TextOrHtml_|
+ *     !Array<!goog.html.SafeHtml.TextOrHtml_>} var_args Elements of array
+ *     arguments would be processed recursively.
+ * @return {!goog.html.SafeHtml}
  */
-goog.dom.isTabIndexFocusable_ = function(element) {
-  var index = element.tabIndex;
-  // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534.
-  return goog.isNumber(index) && index >= 0 && index < 32768;
+goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
+  var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
+  html.dir_ = dir;
+  return html;
 };
 
 
 /**
- * Returns true if the element is focusable even when tabIndex is not set.
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element natively supports focus.
+ * Type marker for the SafeHtml type, used to implement additional run-time
+ * type checking.
+ * @const
  * @private
  */
-goog.dom.nativelySupportsFocus_ = function(element) {
-  return element.tagName == goog.dom.TagName.A ||
-         element.tagName == goog.dom.TagName.INPUT ||
-         element.tagName == goog.dom.TagName.TEXTAREA ||
-         element.tagName == goog.dom.TagName.SELECT ||
-         element.tagName == goog.dom.TagName.BUTTON;
-};
+goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
 
 
 /**
- * Returns true if the element has a bounding rectangle that would be visible
- * (i.e. its width and height are greater than zero).
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element has a non-zero bounding rectangle.
- * @private
+ * Package-internal utility method to create SafeHtml instances.
+ *
+ * @param {string} html The string to initialize the SafeHtml object with.
+ * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be
+ *     constructed, or null if unknown.
+ * @return {!goog.html.SafeHtml} The initialized SafeHtml object.
+ * @package
  */
-goog.dom.hasNonZeroBoundingRect_ = function(element) {
-  var rect = goog.isFunction(element['getBoundingClientRect']) ?
-      element.getBoundingClientRect() :
-      {'height': element.offsetHeight, 'width': element.offsetWidth};
-  return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0;
+goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(
+    html, dir) {
+  var safeHtml = new goog.html.SafeHtml();
+  safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
+  safeHtml.dir_ = dir;
+  return safeHtml;
 };
 
 
 /**
- * Returns the text content of the current node, without markup and invisible
- * symbols. New lines are stripped and whitespace is collapsed,
- * such that each character would be visible.
+ * Like create() but does not restrict which tags can be constructed.
  *
- * In browsers that support it, innerText is used.  Other browsers attempt to
- * simulate it via node traversal.  Line breaks are canonicalized in IE.
- *
- * @param {Node} node The node from which we are getting content.
- * @return {string} The text content.
+ * @param {string} tagName Tag name. Set or validated by caller.
+ * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
+ * @param {(!goog.html.SafeHtml.TextOrHtml_|
+ *     !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content
+ * @return {!goog.html.SafeHtml}
+ * @throws {Error} If invalid or unsafe attribute name or value is provided.
+ * @throws {goog.asserts.AssertionError} If content for void tag is provided.
+ * @package
  */
-goog.dom.getTextContent = function(node) {
-  var textContent;
-  // Note(arv): IE9, Opera, and Safari 3 support innerText but they include
-  // text nodes in script tags. So we revert to use a user agent test here.
-  if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) {
-    textContent = goog.string.canonicalizeNewlines(node.innerText);
-    // Unfortunately .innerText() returns text with &shy; symbols
-    // We need to filter it out and then remove duplicate whitespaces
-  } else {
-    var buf = [];
-    goog.dom.getTextContent_(node, buf, true);
-    textContent = buf.join('');
+goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse =
+    function(tagName, opt_attributes, opt_content) {
+  var dir = null;
+  var result = '<' + tagName;
+
+  if (opt_attributes) {
+    for (var name in opt_attributes) {
+      if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {
+        throw Error('Invalid attribute name "' + name + '".');
+      }
+      var value = opt_attributes[name];
+      if (!goog.isDefAndNotNull(value)) {
+        continue;
+      }
+      result += ' ' +
+          goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);
+    }
   }
 
-  // Strip &shy; entities. goog.format.insertWordBreaks inserts them in Opera.
-  textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
-  // Strip &#8203; entities. goog.format.insertWordBreaks inserts them in IE8.
-  textContent = textContent.replace(/\u200B/g, '');
+  var content = opt_content;
+  if (!goog.isDef(content)) {
+    content = [];
+  } else if (!goog.isArray(content)) {
+    content = [content];
+  }
 
-  // Skip this replacement on old browsers with working innerText, which
-  // automatically turns &nbsp; into ' ' and / +/ into ' ' when reading
-  // innerText.
-  if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) {
-    textContent = textContent.replace(/ +/g, ' ');
+  if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {
+    goog.asserts.assert(!content.length,
+        'Void tag <' + tagName + '> does not allow content.');
+    result += '>';
+  } else {
+    var html = goog.html.SafeHtml.concat(content);
+    result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';
+    dir = html.getDirection();
   }
-  if (textContent != ' ') {
-    textContent = textContent.replace(/^\s*/, '');
+
+  var dirAttribute = opt_attributes && opt_attributes['dir'];
+  if (dirAttribute) {
+    if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {
+      // If the tag has the "dir" attribute specified then its direction is
+      // neutral because it can be safely used in any context.
+      dir = goog.i18n.bidi.Dir.NEUTRAL;
+    } else {
+      dir = null;
+    }
   }
 
-  return textContent;
+  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+      result, dir);
 };
 
 
 /**
- * Returns the text content of the current node, without markup.
- *
- * Unlike {@code getTextContent} this method does not collapse whitespaces
- * or normalize lines breaks.
- *
- * @param {Node} node The node from which we are getting content.
- * @return {string} The raw text content.
+ * @param {!Object<string, string>} fixedAttributes
+ * @param {!Object<string, string>} defaultAttributes
+ * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
+ *     opt_attributes Optional attributes passed to create*().
+ * @return {!Object<string, goog.html.SafeHtml.AttributeValue_>}
+ * @throws {Error} If opt_attributes contains an attribute with the same name
+ *     as an attribute in fixedAttributes.
+ * @package
  */
-goog.dom.getRawTextContent = function(node) {
-  var buf = [];
-  goog.dom.getTextContent_(node, buf, false);
-
-  return buf.join('');
-};
+goog.html.SafeHtml.combineAttributes = function(
+    fixedAttributes, defaultAttributes, opt_attributes) {
+  var combinedAttributes = {};
+  var name;
 
+  for (name in fixedAttributes) {
+    goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
+    combinedAttributes[name] = fixedAttributes[name];
+  }
+  for (name in defaultAttributes) {
+    goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
+    combinedAttributes[name] = defaultAttributes[name];
+  }
 
-/**
- * Recursive support function for text content retrieval.
- *
- * @param {Node} node The node from which we are getting content.
- * @param {Array} buf string buffer.
- * @param {boolean} normalizeWhitespace Whether to normalize whitespace.
- * @private
- */
-goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
-  if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
-    // ignore certain tags
-  } else if (node.nodeType == goog.dom.NodeType.TEXT) {
-    if (normalizeWhitespace) {
-      buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
-    } else {
-      buf.push(node.nodeValue);
+  for (name in opt_attributes) {
+    var nameLower = name.toLowerCase();
+    if (nameLower in fixedAttributes) {
+      throw Error('Cannot override "' + nameLower + '" attribute, got "' +
+          name + '" with value "' + opt_attributes[name] + '"');
     }
-  } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
-    buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]);
-  } else {
-    var child = node.firstChild;
-    while (child) {
-      goog.dom.getTextContent_(child, buf, normalizeWhitespace);
-      child = child.nextSibling;
+    if (nameLower in defaultAttributes) {
+      delete combinedAttributes[nameLower];
     }
+    combinedAttributes[name] = opt_attributes[name];
   }
+
+  return combinedAttributes;
 };
 
 
 /**
- * Returns the text length of the text contained in a node, without markup. This
- * is equivalent to the selection length if the node was selected, or the number
- * of cursor movements to traverse the node. Images & BRs take one space.  New
- * lines are ignored.
- *
- * @param {Node} node The node whose text content length is being calculated.
- * @return {number} The length of {@code node}'s text content.
+ * A SafeHtml instance corresponding to the empty string.
+ * @const {!goog.html.SafeHtml}
  */
-goog.dom.getNodeTextLength = function(node) {
-  return goog.dom.getTextContent(node).length;
-};
+goog.html.SafeHtml.EMPTY =
+    goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+        '', goog.i18n.bidi.Dir.NEUTRAL);
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Returns the text offset of a node relative to one of its ancestors. The text
- * length is the same as the length calculated by goog.dom.getNodeTextLength.
+ * @fileoverview Type-safe wrappers for unsafe DOM APIs.
  *
- * @param {Node} node The node whose offset is being calculated.
- * @param {Node=} opt_offsetParent The node relative to which the offset will
- *     be calculated. Defaults to the node's owner document's body.
- * @return {number} The text offset.
+ * This file provides type-safe wrappers for DOM APIs that can result in
+ * cross-site scripting (XSS) vulnerabilities, if the API is supplied with
+ * untrusted (attacker-controlled) input.  Instead of plain strings, the type
+ * safe wrappers consume values of types from the goog.html package whose
+ * contract promises that values are safe to use in the corresponding context.
+ *
+ * Hence, a program that exclusively uses the wrappers in this file (i.e., whose
+ * only reference to security-sensitive raw DOM APIs are in this file) is
+ * guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo
+ * correctness of code that produces values of the respective goog.html types,
+ * and absent code that violates type safety).
+ *
+ * For example, assigning to an element's .innerHTML property a string that is
+ * derived (even partially) from untrusted input typically results in an XSS
+ * vulnerability. The type-safe wrapper goog.html.setInnerHtml consumes a value
+ * of type goog.html.SafeHtml, whose contract states that using its values in a
+ * HTML context will not result in XSS. Hence a program that is free of direct
+ * assignments to any element's innerHTML property (with the exception of the
+ * assignment to .innerHTML in this file) is guaranteed to be free of XSS due to
+ * assignment of untrusted strings to the innerHTML property.
  */
-goog.dom.getNodeTextOffset = function(node, opt_offsetParent) {
-  var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body;
-  var buf = [];
-  while (node && node != root) {
-    var cur = node;
-    while ((cur = cur.previousSibling)) {
-      buf.unshift(goog.dom.getTextContent(cur));
-    }
-    node = node.parentNode;
-  }
-  // Trim left to deal with FF cases when there might be line breaks and empty
-  // nodes at the front of the text
-  return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length;
-};
 
+goog.provide('goog.dom.safe');
 
-/**
- * Returns the node at a given offset in a parent node.  If an object is
- * provided for the optional third parameter, the node and the remainder of the
- * offset will stored as properties of this object.
- * @param {Node} parent The parent node.
- * @param {number} offset The offset into the parent node.
- * @param {Object=} opt_result Object to be used to store the return value. The
- *     return value will be stored in the form {node: Node, remainder: number}
- *     if this object is provided.
- * @return {Node} The node at the given offset.
- */
-goog.dom.getNodeAtOffset = function(parent, offset, opt_result) {
-  var stack = [parent], pos = 0, cur = null;
-  while (stack.length > 0 && pos < offset) {
-    cur = stack.pop();
-    if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) {
-      // ignore certain tags
-    } else if (cur.nodeType == goog.dom.NodeType.TEXT) {
-      var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' ');
-      pos += text.length;
-    } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
-      pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length;
-    } else {
-      for (var i = cur.childNodes.length - 1; i >= 0; i--) {
-        stack.push(cur.childNodes[i]);
-      }
-    }
-  }
-  if (goog.isObject(opt_result)) {
-    opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0;
-    opt_result.node = cur;
-  }
-
-  return cur;
-};
+goog.require('goog.html.SafeHtml');
+goog.require('goog.html.SafeUrl');
 
 
 /**
- * Returns true if the object is a {@code NodeList}.  To qualify as a NodeList,
- * the object must have a numeric length property and an item function (which
- * has type 'string' on IE for some reason).
- * @param {Object} val Object to test.
- * @return {boolean} Whether the object is a NodeList.
+ * Assigns known-safe HTML to an element's innerHTML property.
+ * @param {!Element} elem The element whose innerHTML is to be assigned to.
+ * @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
  */
-goog.dom.isNodeList = function(val) {
-  // TODO(attila): Now the isNodeList is part of goog.dom we can use
-  // goog.userAgent to make this simpler.
-  // A NodeList must have a length property of type 'number' on all platforms.
-  if (val && typeof val.length == 'number') {
-    // A NodeList is an object everywhere except Safari, where it's a function.
-    if (goog.isObject(val)) {
-      // A NodeList must have an item function (on non-IE platforms) or an item
-      // property of type 'string' (on IE).
-      return typeof val.item == 'function' || typeof val.item == 'string';
-    } else if (goog.isFunction(val)) {
-      // On Safari, a NodeList is a function with an item property that is also
-      // a function.
-      return typeof val.item == 'function';
-    }
-  }
-
-  // Not a NodeList.
-  return false;
+goog.dom.safe.setInnerHtml = function(elem, html) {
+  elem.innerHTML = goog.html.SafeHtml.unwrap(html);
 };
 
 
 /**
- * Walks up the DOM hierarchy returning the first ancestor that has the passed
- * tag name and/or class name. If the passed element matches the specified
- * criteria, the element itself is returned.
- * @param {Node} element The DOM node to start with.
- * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
- *     null/undefined to match only based on class name).
- * @param {?string=} opt_class The class name to match (or null/undefined to
- *     match only based on tag name).
- * @return {Element} The first ancestor that matches the passed criteria, or
- *     null if no match is found.
+ * Assigns known-safe HTML to an element's outerHTML property.
+ * @param {!Element} elem The element whose outerHTML is to be assigned to.
+ * @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
  */
-goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class) {
-  if (!opt_tag && !opt_class) {
-    return null;
-  }
-  var tagName = opt_tag ? opt_tag.toUpperCase() : null;
-  return /** @type {Element} */ (goog.dom.getAncestor(element,
-      function(node) {
-        return (!tagName || node.nodeName == tagName) &&
-               (!opt_class || goog.isString(node.className) &&
-                   goog.array.contains(node.className.split(/\s+/), opt_class));
-      }, true));
+goog.dom.safe.setOuterHtml = function(elem, html) {
+  elem.outerHTML = goog.html.SafeHtml.unwrap(html);
 };
 
 
 /**
- * Walks up the DOM hierarchy returning the first ancestor that has the passed
- * class name. If the passed element matches the specified criteria, the
- * element itself is returned.
- * @param {Node} element The DOM node to start with.
- * @param {string} className The class name to match.
- * @return {Element} The first ancestor that matches the passed criteria, or
- *     null if none match.
+ * Writes known-safe HTML to a document.
+ * @param {!Document} doc The document to be written to.
+ * @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
  */
-goog.dom.getAncestorByClass = function(element, className) {
-  return goog.dom.getAncestorByTagNameAndClass(element, null, className);
+goog.dom.safe.documentWrite = function(doc, html) {
+  doc.write(goog.html.SafeHtml.unwrap(html));
 };
 
 
 /**
- * Walks up the DOM hierarchy returning the first ancestor that passes the
- * matcher function.
- * @param {Node} element The DOM node to start with.
- * @param {function(Node) : boolean} matcher A function that returns true if the
- *     passed node matches the desired criteria.
- * @param {boolean=} opt_includeNode If true, the node itself is included in
- *     the search (the first call to the matcher will pass startElement as
- *     the node to test).
- * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
- *     dom.
- * @return {Node} DOM node that matched the matcher, or null if there was
- *     no match.
- */
-goog.dom.getAncestor = function(
-    element, matcher, opt_includeNode, opt_maxSearchSteps) {
-  if (!opt_includeNode) {
-    element = element.parentNode;
-  }
-  var ignoreSearchSteps = opt_maxSearchSteps == null;
-  var steps = 0;
-  while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) {
-    if (matcher(element)) {
-      return element;
-    }
-    element = element.parentNode;
-    steps++;
+ * Safely assigns a URL to an anchor element's href property.
+ *
+ * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
+ * anchor's href property.  If url is of type string however, it is first
+ * sanitized using goog.html.SafeUrl.sanitize.
+ *
+ * Example usage:
+ *   goog.dom.safe.setAnchorHref(anchorEl, url);
+ * which is a safe alternative to
+ *   anchorEl.href = url;
+ * The latter can result in XSS vulnerabilities if url is a
+ * user-/attacker-controlled value.
+ *
+ * @param {!HTMLAnchorElement} anchor The anchor element whose href property
+ *     is to be assigned to.
+ * @param {string|!goog.html.SafeUrl} url The URL to assign.
+ * @see goog.html.SafeUrl#sanitize
+ */
+goog.dom.safe.setAnchorHref = function(anchor, url) {
+  /** @type {!goog.html.SafeUrl} */
+  var safeUrl;
+  if (url instanceof goog.html.SafeUrl) {
+    safeUrl = url;
+  } else {
+    safeUrl = goog.html.SafeUrl.sanitize(url);
   }
-  // Reached the root of the DOM without a match
-  return null;
+  anchor.href = goog.html.SafeUrl.unwrap(safeUrl);
 };
 
 
 /**
- * Determines the active element in the given document.
- * @param {Document} doc The document to look in.
- * @return {Element} The active element.
- */
-goog.dom.getActiveElement = function(doc) {
-  try {
-    return doc && doc.activeElement;
-  } catch (e) {
-    // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE
-    // throws an exception. I'm not 100% sure why, but I suspect it chokes
-    // on document.activeElement if the activeElement has been recently
-    // removed from the DOM by a JS operation.
-    //
-    // We assume that an exception here simply means
-    // "there is no active element."
+ * Safely assigns a URL to a Location object's href property.
+ *
+ * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
+ * loc's href property.  If url is of type string however, it is first sanitized
+ * using goog.html.SafeUrl.sanitize.
+ *
+ * Example usage:
+ *   goog.dom.safe.setLocationHref(document.location, redirectUrl);
+ * which is a safe alternative to
+ *   document.location.href = redirectUrl;
+ * The latter can result in XSS vulnerabilities if redirectUrl is a
+ * user-/attacker-controlled value.
+ *
+ * @param {!Location} loc The Location object whose href property is to be
+ *     assigned to.
+ * @param {string|!goog.html.SafeUrl} url The URL to assign.
+ * @see goog.html.SafeUrl#sanitize
+ */
+goog.dom.safe.setLocationHref = function(loc, url) {
+  /** @type {!goog.html.SafeUrl} */
+  var safeUrl;
+  if (url instanceof goog.html.SafeUrl) {
+    safeUrl = url;
+  } else {
+    safeUrl = goog.html.SafeUrl.sanitize(url);
   }
-
-  return null;
+  loc.href = goog.html.SafeUrl.unwrap(safeUrl);
 };
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @private {number} Cached version of the devicePixelRatio.
+ * @fileoverview A utility class for representing two-dimensional positions.
  */
-goog.dom.devicePixelRatio_;
 
 
-/**
- * Gives the devicePixelRatio, or attempts to determine if not present.
- *
- * By default, this is the same value given by window.devicePixelRatio. If
- * devicePixelRatio is not defined, the ratio is calculated with
- * window.matchMedia, if present. Otherwise, gives 1.0.
- *
- * This function is cached so that the pixel ratio is calculated only once
- * and only calculated when first requested.
- *
- * @return {number} The number of actual pixels per virtual pixel.
- */
-goog.dom.getPixelRatio = goog.functions.cacheReturnValue(function() {
-  var win = goog.dom.getWindow();
+goog.provide('goog.math.Coordinate');
 
-  // devicePixelRatio does not work on Mobile firefox.
-  // TODO(user): Enable this check on a known working mobile Gecko version.
-  // Filed a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=896804
-  var isFirefoxMobile = goog.userAgent.GECKO && goog.userAgent.MOBILE;
+goog.require('goog.math');
 
-  if (goog.isDef(win.devicePixelRatio) && !isFirefoxMobile) {
-    return win.devicePixelRatio;
-  } else if (win.matchMedia) {
-    return goog.dom.matchesPixelRatio_(.75) ||
-           goog.dom.matchesPixelRatio_(1.5) ||
-           goog.dom.matchesPixelRatio_(2) ||
-           goog.dom.matchesPixelRatio_(3) || 1;
-  }
-  return 1;
-});
 
 
 /**
- * Calculates a mediaQuery to check if the current device supports the
- * given actual to virtual pixel ratio.
- * @param {number} pixelRatio The ratio of actual pixels to virtual pixels.
- * @return {number} pixelRatio if applicable, otherwise 0.
- * @private
+ * Class for representing coordinates and positions.
+ * @param {number=} opt_x Left, defaults to 0.
+ * @param {number=} opt_y Top, defaults to 0.
+ * @constructor
  */
-goog.dom.matchesPixelRatio_ = function(pixelRatio) {
-  var win = goog.dom.getWindow();
-  var query = ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' +
-               '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' +
-               '(min-resolution: ' + pixelRatio + 'dppx)');
-  return win.matchMedia(query).matches ? pixelRatio : 0;
-};
+goog.math.Coordinate = function(opt_x, opt_y) {
+  /**
+   * X-value
+   * @type {number}
+   */
+  this.x = goog.isDef(opt_x) ? opt_x : 0;
 
+  /**
+   * Y-value
+   * @type {number}
+   */
+  this.y = goog.isDef(opt_y) ? opt_y : 0;
+};
 
 
 /**
- * Create an instance of a DOM helper with a new document object.
- * @param {Document=} opt_document Document object to associate with this
- *     DOM helper.
- * @constructor
+ * Returns a new copy of the coordinate.
+ * @return {!goog.math.Coordinate} A clone of this coordinate.
  */
-goog.dom.DomHelper = function(opt_document) {
+goog.math.Coordinate.prototype.clone = function() {
+  return new goog.math.Coordinate(this.x, this.y);
+};
+
+
+if (goog.DEBUG) {
   /**
-   * Reference to the document object to use
-   * @type {!Document}
-   * @private
+   * Returns a nice string representing the coordinate.
+   * @return {string} In the form (50, 73).
+   * @override
    */
-  this.document_ = opt_document || goog.global.document || document;
-};
+  goog.math.Coordinate.prototype.toString = function() {
+    return '(' + this.x + ', ' + this.y + ')';
+  };
+}
 
 
 /**
- * Gets the dom helper object for the document where the element resides.
- * @param {Node=} opt_node If present, gets the DomHelper for this node.
- * @return {!goog.dom.DomHelper} The DomHelper.
+ * Compares coordinates for equality.
+ * @param {goog.math.Coordinate} a A Coordinate.
+ * @param {goog.math.Coordinate} b A Coordinate.
+ * @return {boolean} True iff the coordinates are equal, or if both are null.
  */
-goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;
+goog.math.Coordinate.equals = function(a, b) {
+  if (a == b) {
+    return true;
+  }
+  if (!a || !b) {
+    return false;
+  }
+  return a.x == b.x && a.y == b.y;
+};
 
 
 /**
- * Sets the document object.
- * @param {!Document} document Document object.
+ * Returns the distance between two coordinates.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {number} The distance between {@code a} and {@code b}.
  */
-goog.dom.DomHelper.prototype.setDocument = function(document) {
-  this.document_ = document;
+goog.math.Coordinate.distance = function(a, b) {
+  var dx = a.x - b.x;
+  var dy = a.y - b.y;
+  return Math.sqrt(dx * dx + dy * dy);
 };
 
 
 /**
- * Gets the document object being used by the dom library.
- * @return {!Document} Document object.
+ * Returns the magnitude of a coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @return {number} The distance between the origin and {@code a}.
  */
-goog.dom.DomHelper.prototype.getDocument = function() {
-  return this.document_;
+goog.math.Coordinate.magnitude = function(a) {
+  return Math.sqrt(a.x * a.x + a.y * a.y);
 };
 
 
 /**
- * Alias for {@code getElementById}. If a DOM node is passed in then we just
- * return that.
- * @param {string|Element} element Element ID or a DOM node.
- * @return {Element} The element with the given ID, or the node passed in.
+ * Returns the angle from the origin to a coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @return {number} The angle, in degrees, clockwise from the positive X
+ *     axis to {@code a}.
  */
-goog.dom.DomHelper.prototype.getElement = function(element) {
-  return goog.dom.getElementHelper_(this.document_, element);
+goog.math.Coordinate.azimuth = function(a) {
+  return goog.math.angle(0, 0, a.x, a.y);
 };
 
 
 /**
- * Gets an element by id, asserting that the element is found.
- *
- * This is used when an element is expected to exist, and should fail with
- * an assertion error if it does not (if assertions are enabled).
+ * Returns the squared distance between two coordinates. Squared distances can
+ * be used for comparisons when the actual value is not required.
  *
- * @param {string} id Element ID.
- * @return {!Element} The element with the given ID, if it exists.
+ * Performance note: eliminating the square root is an optimization often used
+ * in lower-level languages, but the speed difference is not nearly as
+ * pronounced in JavaScript (only a few percent.)
+ *
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {number} The squared distance between {@code a} and {@code b}.
  */
-goog.dom.DomHelper.prototype.getRequiredElement = function(id) {
-  return goog.dom.getRequiredElementHelper_(this.document_, id);
+goog.math.Coordinate.squaredDistance = function(a, b) {
+  var dx = a.x - b.x;
+  var dy = a.y - b.y;
+  return dx * dx + dy * dy;
 };
 
 
 /**
- * Alias for {@code getElement}.
- * @param {string|Element} element Element ID or a DOM node.
- * @return {Element} The element with the given ID, or the node passed in.
- * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead.
+ * Returns the difference between two coordinates as a new
+ * goog.math.Coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {!goog.math.Coordinate} A Coordinate representing the difference
+ *     between {@code a} and {@code b}.
  */
-goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
+goog.math.Coordinate.difference = function(a, b) {
+  return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
+};
 
 
 /**
- * Looks up elements by both tag and class name, using browser native functions
- * ({@code querySelectorAll}, {@code getElementsByTagName} or
- * {@code getElementsByClassName}) where possible. The returned array is a live
- * NodeList or a static list depending on the code path taken.
- *
- * @see goog.dom.query
- *
- * @param {?string=} opt_tag Element tag name or * for all tags.
- * @param {?string=} opt_class Optional class name.
- * @param {(Document|Element)=} opt_el Optional element to look in.
- * @return { {length: number} } Array-like list of elements (only a length
- *     property and numerical indices are guaranteed to exist).
+ * Returns the sum of two coordinates as a new goog.math.Coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
+ *     coordinates.
  */
-goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag,
-                                                                     opt_class,
-                                                                     opt_el) {
-  return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag,
-                                                opt_class, opt_el);
+goog.math.Coordinate.sum = function(a, b) {
+  return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
 };
 
 
 /**
- * Returns an array of all the elements with the provided className.
- * @see {goog.dom.query}
- * @param {string} className the name of the class to look for.
- * @param {Element|Document=} opt_el Optional element to look in.
- * @return { {length: number} } The items found with the class name provided.
+ * Rounds the x and y fields to the next larger integer values.
+ * @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
  */
-goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
-  var doc = opt_el || this.document_;
-  return goog.dom.getElementsByClass(className, doc);
+goog.math.Coordinate.prototype.ceil = function() {
+  this.x = Math.ceil(this.x);
+  this.y = Math.ceil(this.y);
+  return this;
 };
 
 
 /**
- * Returns the first element we find matching the provided class name.
- * @see {goog.dom.query}
- * @param {string} className the name of the class to look for.
- * @param {(Element|Document)=} opt_el Optional element to look in.
- * @return {Element} The first item found with the class name provided.
+ * Rounds the x and y fields to the next smaller integer values.
+ * @return {!goog.math.Coordinate} This coordinate with floored fields.
  */
-goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
-  var doc = opt_el || this.document_;
-  return goog.dom.getElementByClass(className, doc);
+goog.math.Coordinate.prototype.floor = function() {
+  this.x = Math.floor(this.x);
+  this.y = Math.floor(this.y);
+  return this;
 };
 
 
 /**
- * Ensures an element with the given className exists, and then returns the
- * first element with the provided className.
- * @see {goog.dom.query}
- * @param {string} className the name of the class to look for.
- * @param {(!Element|!Document)=} opt_root Optional element or document to look
- *     in.
- * @return {!Element} The first item found with the class name provided.
- * @throws {goog.asserts.AssertionError} Thrown if no element is found.
+ * Rounds the x and y fields to the nearest integer values.
+ * @return {!goog.math.Coordinate} This coordinate with rounded fields.
  */
-goog.dom.DomHelper.prototype.getRequiredElementByClass = function(className,
-                                                                  opt_root) {
-  var root = opt_root || this.document_;
-  return goog.dom.getRequiredElementByClass(className, root);
+goog.math.Coordinate.prototype.round = function() {
+  this.x = Math.round(this.x);
+  this.y = Math.round(this.y);
+  return this;
 };
 
 
 /**
- * Alias for {@code getElementsByTagNameAndClass}.
- * @deprecated Use DomHelper getElementsByTagNameAndClass.
- * @see goog.dom.query
- *
- * @param {?string=} opt_tag Element tag name.
- * @param {?string=} opt_class Optional class name.
- * @param {Element=} opt_el Optional element to look in.
- * @return { {length: number} } Array-like list of elements (only a length
- *     property and numerical indices are guaranteed to exist).
+ * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
+ * is given, then the x and y values are translated by the coordinate's x and y.
+ * Otherwise, x and y are translated by {@code tx} and {@code opt_ty}
+ * respectively.
+ * @param {number|goog.math.Coordinate} tx The value to translate x by or the
+ *     the coordinate to translate this coordinate by.
+ * @param {number=} opt_ty The value to translate y by.
+ * @return {!goog.math.Coordinate} This coordinate after translating.
  */
-goog.dom.DomHelper.prototype.$$ =
-    goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
+goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
+  if (tx instanceof goog.math.Coordinate) {
+    this.x += tx.x;
+    this.y += tx.y;
+  } else {
+    this.x += tx;
+    if (goog.isNumber(opt_ty)) {
+      this.y += opt_ty;
+    }
+  }
+  return this;
+};
 
 
 /**
- * Sets a number of properties on a node.
- * @param {Element} element DOM node to set properties on.
- * @param {Object} properties Hash of property:value pairs.
+ * Scales this coordinate by the given scale factors. The x and y values are
+ * scaled by {@code sx} and {@code opt_sy} respectively.  If {@code opt_sy}
+ * is not given, then {@code sx} is used for both x and y.
+ * @param {number} sx The scale factor to use for the x dimension.
+ * @param {number=} opt_sy The scale factor to use for the y dimension.
+ * @return {!goog.math.Coordinate} This coordinate after scaling.
  */
-goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;
+goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
+  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+  this.x *= sx;
+  this.y *= sy;
+  return this;
+};
 
 
 /**
- * Gets the dimensions of the viewport.
- * @param {Window=} opt_window Optional window element to test. Defaults to
- *     the window of the Dom Helper.
- * @return {!goog.math.Size} Object with values 'width' and 'height'.
+ * Rotates this coordinate clockwise about the origin (or, optionally, the given
+ * center) by the given angle, in radians.
+ * @param {number} radians The angle by which to rotate this coordinate
+ *     clockwise about the given center, in radians.
+ * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
+ *     to (0, 0) if not given.
  */
-goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
-  // TODO(arv): This should not take an argument. That breaks the rule of a
-  // a DomHelper representing a single frame/window/document.
-  return goog.dom.getViewportSize(opt_window || this.getWindow());
+goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) {
+  var center = opt_center || new goog.math.Coordinate(0, 0);
+
+  var x = this.x;
+  var y = this.y;
+  var cos = Math.cos(radians);
+  var sin = Math.sin(radians);
+
+  this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
+  this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
 };
 
 
 /**
- * Calculates the height of the document.
- *
- * @return {number} The height of the document.
+ * Rotates this coordinate clockwise about the origin (or, optionally, the given
+ * center) by the given angle, in degrees.
+ * @param {number} degrees The angle by which to rotate this coordinate
+ *     clockwise about the given center, in degrees.
+ * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
+ *     to (0, 0) if not given.
  */
-goog.dom.DomHelper.prototype.getDocumentHeight = function() {
-  return goog.dom.getDocumentHeight_(this.getWindow());
+goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
+  this.rotateRadians(goog.math.toRadians(degrees), opt_center);
 };
 
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Typedef for use with goog.dom.createDom and goog.dom.append.
- * @typedef {Object|string|Array|NodeList}
+ * @fileoverview A utility class for representing two-dimensional sizes.
+ * @author brenneman@google.com (Shawn Brenneman)
  */
-goog.dom.Appendable;
+
+
+goog.provide('goog.math.Size');
+
 
 
 /**
- * Returns a dom node with a set of attributes.  This function accepts varargs
- * for subsequent nodes to be added.  Subsequent nodes will be added to the
- * first node as childNodes.
- *
- * So:
- * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
- * would return a div with two child paragraphs
- *
- * An easy way to move all child nodes of an existing element to a new parent
- * element is:
- * <code>createDom('div', null, oldElement.childNodes);</code>
- * which will remove all child nodes from the old element and add them as
- * child nodes of the new DIV.
- *
- * @param {string} tagName Tag to create.
- * @param {Object|string=} opt_attributes If object, then a map of name-value
- *     pairs for attributes. If a string, then this is the className of the new
- *     element.
- * @param {...goog.dom.Appendable} var_args Further DOM nodes or
- *     strings for text nodes. If one of the var_args is an array or
- *     NodeList, its elements will be added as childNodes instead.
- * @return {!Element} Reference to a DOM node.
+ * Class for representing sizes consisting of a width and height. Undefined
+ * width and height support is deprecated and results in compiler warning.
+ * @param {number} width Width.
+ * @param {number} height Height.
+ * @constructor
  */
-goog.dom.DomHelper.prototype.createDom = function(tagName,
-                                                  opt_attributes,
-                                                  var_args) {
-  return goog.dom.createDom_(this.document_, arguments);
+goog.math.Size = function(width, height) {
+  /**
+   * Width
+   * @type {number}
+   */
+  this.width = width;
+
+  /**
+   * Height
+   * @type {number}
+   */
+  this.height = height;
 };
 
 
 /**
- * Alias for {@code createDom}.
- * @param {string} tagName Tag to create.
- * @param {(Object|string)=} opt_attributes If object, then a map of name-value
- *     pairs for attributes. If a string, then this is the className of the new
- *     element.
- * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for
- *     text nodes.  If one of the var_args is an array, its children will be
- *     added as childNodes instead.
- * @return {!Element} Reference to a DOM node.
- * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead.
+ * Compares sizes for equality.
+ * @param {goog.math.Size} a A Size.
+ * @param {goog.math.Size} b A Size.
+ * @return {boolean} True iff the sizes have equal widths and equal
+ *     heights, or if both are null.
  */
-goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
+goog.math.Size.equals = function(a, b) {
+  if (a == b) {
+    return true;
+  }
+  if (!a || !b) {
+    return false;
+  }
+  return a.width == b.width && a.height == b.height;
+};
 
 
 /**
- * Creates a new element.
- * @param {string} name Tag name.
- * @return {!Element} The new element.
+ * @return {!goog.math.Size} A new copy of the Size.
  */
-goog.dom.DomHelper.prototype.createElement = function(name) {
-  return this.document_.createElement(name);
+goog.math.Size.prototype.clone = function() {
+  return new goog.math.Size(this.width, this.height);
 };
 
 
+if (goog.DEBUG) {
+  /**
+   * Returns a nice string representing size.
+   * @return {string} In the form (50 x 73).
+   * @override
+   */
+  goog.math.Size.prototype.toString = function() {
+    return '(' + this.width + ' x ' + this.height + ')';
+  };
+}
+
+
 /**
- * Creates a new text node.
- * @param {number|string} content Content.
- * @return {!Text} The new text node.
+ * @return {number} The longer of the two dimensions in the size.
  */
-goog.dom.DomHelper.prototype.createTextNode = function(content) {
-  return this.document_.createTextNode(String(content));
+goog.math.Size.prototype.getLongest = function() {
+  return Math.max(this.width, this.height);
 };
 
 
 /**
- * Create a table.
- * @param {number} rows The number of rows in the table.  Must be >= 1.
- * @param {number} columns The number of columns in the table.  Must be >= 1.
- * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps.
- * @return {!Element} The created table.
+ * @return {number} The shorter of the two dimensions in the size.
  */
-goog.dom.DomHelper.prototype.createTable = function(rows, columns,
-    opt_fillWithNbsp) {
-  return goog.dom.createTable_(this.document_, rows, columns,
-      !!opt_fillWithNbsp);
+goog.math.Size.prototype.getShortest = function() {
+  return Math.min(this.width, this.height);
 };
 
 
 /**
- * Converts an HTML string into a node or a document fragment.  A single Node
- * is used if the {@code htmlString} only generates a single node.  If the
- * {@code htmlString} generates multiple nodes then these are put inside a
- * {@code DocumentFragment}.
- *
- * @param {string} htmlString The HTML string to convert.
- * @return {!Node} The resulting node.
+ * @return {number} The area of the size (width * height).
  */
-goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) {
-  return goog.dom.htmlToDocumentFragment_(this.document_, htmlString);
+goog.math.Size.prototype.area = function() {
+  return this.width * this.height;
 };
 
 
 /**
- * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
- * mode, false otherwise.
- * @return {boolean} True if in CSS1-compatible mode.
+ * @return {number} The perimeter of the size (width + height) * 2.
  */
-goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
-  return goog.dom.isCss1CompatMode_(this.document_);
+goog.math.Size.prototype.perimeter = function() {
+  return (this.width + this.height) * 2;
 };
 
 
 /**
- * Gets the window object associated with the document.
- * @return {!Window} The window associated with the given document.
+ * @return {number} The ratio of the size's width to its height.
  */
-goog.dom.DomHelper.prototype.getWindow = function() {
-  return goog.dom.getWindow_(this.document_);
+goog.math.Size.prototype.aspectRatio = function() {
+  return this.width / this.height;
 };
 
 
 /**
- * Gets the document scroll element.
- * @return {!Element} Scrolling element.
+ * @return {boolean} True if the size has zero area, false if both dimensions
+ *     are non-zero numbers.
  */
-goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
-  return goog.dom.getDocumentScrollElement_(this.document_);
+goog.math.Size.prototype.isEmpty = function() {
+  return !this.area();
 };
 
 
 /**
- * Gets the document scroll distance as a coordinate object.
- * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'.
+ * Clamps the width and height parameters upward to integer values.
+ * @return {!goog.math.Size} This size with ceil'd components.
  */
-goog.dom.DomHelper.prototype.getDocumentScroll = function() {
-  return goog.dom.getDocumentScroll_(this.document_);
+goog.math.Size.prototype.ceil = function() {
+  this.width = Math.ceil(this.width);
+  this.height = Math.ceil(this.height);
+  return this;
 };
 
 
 /**
- * Determines the active element in the given document.
- * @param {Document=} opt_doc The document to look in.
- * @return {Element} The active element.
+ * @param {!goog.math.Size} target The target size.
+ * @return {boolean} True if this Size is the same size or smaller than the
+ *     target size in both dimensions.
  */
-goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) {
-  return goog.dom.getActiveElement(opt_doc || this.document_);
+goog.math.Size.prototype.fitsInside = function(target) {
+  return this.width <= target.width && this.height <= target.height;
 };
 
 
 /**
- * Appends a child to a node.
- * @param {Node} parent Parent.
- * @param {Node} child Child.
+ * Clamps the width and height parameters downward to integer values.
+ * @return {!goog.math.Size} This size with floored components.
  */
-goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
+goog.math.Size.prototype.floor = function() {
+  this.width = Math.floor(this.width);
+  this.height = Math.floor(this.height);
+  return this;
+};
 
 
 /**
- * Appends a node with text or other nodes.
- * @param {!Node} parent The node to append nodes to.
- * @param {...goog.dom.Appendable} var_args The things to append to the node.
- *     If this is a Node it is appended as is.
- *     If this is a string then a text node is appended.
- *     If this is an array like object then fields 0 to length - 1 are appended.
+ * Rounds the width and height parameters to integer values.
+ * @return {!goog.math.Size} This size with rounded components.
  */
-goog.dom.DomHelper.prototype.append = goog.dom.append;
+goog.math.Size.prototype.round = function() {
+  this.width = Math.round(this.width);
+  this.height = Math.round(this.height);
+  return this;
+};
 
 
 /**
- * Determines if the given node can contain children, intended to be used for
- * HTML generation.
- *
- * @param {Node} node The node to check.
- * @return {boolean} Whether the node can contain children.
+ * Scales this size by the given scale factors. The width and height are scaled
+ * by {@code sx} and {@code opt_sy} respectively.  If {@code opt_sy} is not
+ * given, then {@code sx} is used for both the width and height.
+ * @param {number} sx The scale factor to use for the width.
+ * @param {number=} opt_sy The scale factor to use for the height.
+ * @return {!goog.math.Size} This Size object after scaling.
  */
-goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren;
+goog.math.Size.prototype.scale = function(sx, opt_sy) {
+  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+  this.width *= sx;
+  this.height *= sy;
+  return this;
+};
 
 
 /**
- * Removes all the child nodes on a DOM node.
- * @param {Node} node Node to remove children from.
+ * Uniformly scales the size to fit inside the dimensions of a given size. The
+ * original aspect ratio will be preserved.
+ *
+ * This function assumes that both Sizes contain strictly positive dimensions.
+ * @param {!goog.math.Size} target The target size.
+ * @return {!goog.math.Size} This Size object, after optional scaling.
  */
-goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;
+goog.math.Size.prototype.scaleToFit = function(target) {
+  var s = this.aspectRatio() > target.aspectRatio() ?
+      target.width / this.width :
+      target.height / this.height;
+
+  return this.scale(s);
+};
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Inserts a new node before an existing reference node (i.e., as the previous
- * sibling). If the reference node has no parent, then does nothing.
- * @param {Node} newNode Node to insert.
- * @param {Node} refNode Reference node to insert before.
+ * @fileoverview Utilities for manipulating the browser's Document Object Model
+ * Inspiration taken *heavily* from mochikit (http://mochikit.com/).
+ *
+ * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer
+ * to a different document object.  This is useful if you are working with
+ * frames or multiple windows.
+ *
+ * @author arv@google.com (Erik Arvidsson)
  */
-goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;
 
 
-/**
- * Inserts a new node after an existing reference node (i.e., as the next
- * sibling). If the reference node has no parent, then does nothing.
- * @param {Node} newNode Node to insert.
- * @param {Node} refNode Reference node to insert after.
- */
-goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
+// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem
+// is that getTextContent should mimic the DOM3 textContent. We should add a
+// getInnerText (or getText) which tries to return the visible text, innerText.
 
 
-/**
- * Insert a child at a given index. If index is larger than the number of child
- * nodes that the parent currently has, the node is inserted as the last child
- * node.
- * @param {Element} parent The element into which to insert the child.
- * @param {Node} child The element to insert.
- * @param {number} index The index at which to insert the new child node. Must
- *     not be negative.
- */
-goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt;
+goog.provide('goog.dom');
+goog.provide('goog.dom.Appendable');
+goog.provide('goog.dom.DomHelper');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.BrowserFeature');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.safe');
+goog.require('goog.html.SafeHtml');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Size');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.string.Unicode');
+goog.require('goog.userAgent');
 
 
 /**
- * Removes a node from its parent.
- * @param {Node} node The node to remove.
- * @return {Node} The node removed if removed; else, null.
+ * @define {boolean} Whether we know at compile time that the browser is in
+ * quirks mode.
  */
-goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
+goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);
 
 
 /**
- * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
- * parent.
- * @param {Node} newNode Node to insert.
- * @param {Node} oldNode Node to replace.
+ * @define {boolean} Whether we know at compile time that the browser is in
+ * standards compliance mode.
  */
-goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
+goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);
 
 
 /**
- * Flattens an element. That is, removes it and replace it with its children.
- * @param {Element} element The element to flatten.
- * @return {Element|undefined} The original element, detached from the document
- *     tree, sans children, or undefined if the element was already not in the
- *     document.
+ * Whether we know the compatibility mode at compile time.
+ * @type {boolean}
+ * @private
  */
-goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
+goog.dom.COMPAT_MODE_KNOWN_ =
+    goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
 
 
 /**
- * Returns an array containing just the element children of the given element.
- * @param {Element} element The element whose element children we want.
- * @return {!(Array|NodeList)} An array or array-like list of just the element
- *     children of the given element.
+ * Gets the DomHelper object for the document where the element resides.
+ * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this
+ *     element.
+ * @return {!goog.dom.DomHelper} The DomHelper.
  */
-goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren;
+goog.dom.getDomHelper = function(opt_element) {
+  return opt_element ?
+      new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) :
+      (goog.dom.defaultDomHelper_ ||
+          (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper()));
+};
 
 
 /**
- * Returns the first child node that is an element.
- * @param {Node} node The node to get the first child element of.
- * @return {Element} The first child node of {@code node} that is an element.
+ * Cached default DOM helper.
+ * @type {goog.dom.DomHelper}
+ * @private
  */
-goog.dom.DomHelper.prototype.getFirstElementChild =
-    goog.dom.getFirstElementChild;
+goog.dom.defaultDomHelper_;
 
 
 /**
- * Returns the last child node that is an element.
- * @param {Node} node The node to get the last child element of.
- * @return {Element} The last child node of {@code node} that is an element.
+ * Gets the document object being used by the dom library.
+ * @return {!Document} Document object.
  */
-goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild;
+goog.dom.getDocument = function() {
+  return document;
+};
 
 
 /**
- * Returns the first next sibling that is an element.
- * @param {Node} node The node to get the next sibling element of.
- * @return {Element} The next sibling of {@code node} that is an element.
+ * Gets an element from the current document by element id.
+ *
+ * If an Element is passed in, it is returned.
+ *
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
  */
-goog.dom.DomHelper.prototype.getNextElementSibling =
-    goog.dom.getNextElementSibling;
+goog.dom.getElement = function(element) {
+  return goog.dom.getElementHelper_(document, element);
+};
 
 
 /**
- * Returns the first previous sibling that is an element.
- * @param {Node} node The node to get the previous sibling element of.
- * @return {Element} The first previous sibling of {@code node} that is
- *     an element.
+ * Gets an element by id from the given document (if present).
+ * If an element is given, it is returned.
+ * @param {!Document} doc
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The resulting element.
+ * @private
  */
-goog.dom.DomHelper.prototype.getPreviousElementSibling =
-    goog.dom.getPreviousElementSibling;
+goog.dom.getElementHelper_ = function(doc, element) {
+  return goog.isString(element) ?
+      doc.getElementById(element) :
+      element;
+};
 
 
 /**
- * Returns the next node in source order from the given node.
- * @param {Node} node The node.
- * @return {Node} The next node in the DOM tree, or null if this was the last
- *     node.
+ * Gets an element by id, asserting that the element is found.
+ *
+ * This is used when an element is expected to exist, and should fail with
+ * an assertion error if it does not (if assertions are enabled).
+ *
+ * @param {string} id Element ID.
+ * @return {!Element} The element with the given ID, if it exists.
  */
-goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;
+goog.dom.getRequiredElement = function(id) {
+  return goog.dom.getRequiredElementHelper_(document, id);
+};
 
 
 /**
- * Returns the previous node in source order from the given node.
- * @param {Node} node The node.
- * @return {Node} The previous node in the DOM tree, or null if this was the
- *     first node.
+ * Helper function for getRequiredElementHelper functions, both static and
+ * on DomHelper.  Asserts the element with the given id exists.
+ * @param {!Document} doc
+ * @param {string} id
+ * @return {!Element} The element with the given ID, if it exists.
+ * @private
  */
-goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;
+goog.dom.getRequiredElementHelper_ = function(doc, id) {
+  // To prevent users passing in Elements as is permitted in getElement().
+  goog.asserts.assertString(id);
+  var element = goog.dom.getElementHelper_(doc, id);
+  element = goog.asserts.assertElement(element,
+      'No element found with id: ' + id);
+  return element;
+};
 
 
 /**
- * Whether the object looks like a DOM node.
- * @param {?} obj The object being tested for node likeness.
- * @return {boolean} Whether the object looks like a DOM node.
+ * Alias for getElement.
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
+ * @deprecated Use {@link goog.dom.getElement} instead.
  */
-goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
+goog.dom.$ = goog.dom.getElement;
 
 
 /**
- * Whether the object looks like an Element.
- * @param {?} obj The object being tested for Element likeness.
- * @return {boolean} Whether the object looks like an Element.
+ * Looks up elements by both tag and class name, using browser native functions
+ * ({@code querySelectorAll}, {@code getElementsByTagName} or
+ * {@code getElementsByClassName}) where possible. This function
+ * is a useful, if limited, way of collecting a list of DOM elements
+ * with certain characteristics.  {@code goog.dom.query} offers a
+ * more powerful and general solution which allows matching on CSS3
+ * selector expressions, but at increased cost in code size. If all you
+ * need is particular tags belonging to a single class, this function
+ * is fast and sleek.
+ *
+ * Note that tag names are case sensitive in the SVG namespace, and this
+ * function converts opt_tag to uppercase for comparisons. For queries in the
+ * SVG namespace you should use querySelector or querySelectorAll instead.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=963870
+ * https://bugs.webkit.org/show_bug.cgi?id=83438
+ *
+ * @see {goog.dom.query}
+ *
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ *     property and numerical indices are guaranteed to exist).
  */
-goog.dom.DomHelper.prototype.isElement = goog.dom.isElement;
+goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
+  return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class,
+                                                opt_el);
+};
 
 
 /**
- * Returns true if the specified value is a Window object. This includes the
- * global window for HTML pages, and iframe windows.
- * @param {?} obj Variable to test.
- * @return {boolean} Whether the variable is a window.
+ * Returns a static, array-like list of the elements with the provided
+ * className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } The items found with the class name provided.
  */
-goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow;
+goog.dom.getElementsByClass = function(className, opt_el) {
+  var parent = opt_el || document;
+  if (goog.dom.canUseQuerySelector_(parent)) {
+    return parent.querySelectorAll('.' + className);
+  }
+  return goog.dom.getElementsByTagNameAndClass_(
+      document, '*', className, opt_el);
+};
 
 
 /**
- * Returns an element's parent, if it's an Element.
- * @param {Element} element The DOM element.
- * @return {Element} The parent, or null if not an Element.
+ * Returns the first element with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {Element|Document=} opt_el Optional element to look in.
+ * @return {Element} The first item with the class name provided.
  */
-goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement;
+goog.dom.getElementByClass = function(className, opt_el) {
+  var parent = opt_el || document;
+  var retVal = null;
+  if (goog.dom.canUseQuerySelector_(parent)) {
+    retVal = parent.querySelector('.' + className);
+  } else {
+    retVal = goog.dom.getElementsByTagNameAndClass_(
+        document, '*', className, opt_el)[0];
+  }
+  return retVal || null;
+};
 
 
 /**
- * Whether a node contains another node.
- * @param {Node} parent The node that should contain the other node.
- * @param {Node} descendant The node to test presence of.
- * @return {boolean} Whether the parent node contains the descendent node.
+ * Ensures an element with the given className exists, and then returns the
+ * first element with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {!Element|!Document=} opt_root Optional element or document to look
+ *     in.
+ * @return {!Element} The first item with the class name provided.
+ * @throws {goog.asserts.AssertionError} Thrown if no element is found.
  */
-goog.dom.DomHelper.prototype.contains = goog.dom.contains;
+goog.dom.getRequiredElementByClass = function(className, opt_root) {
+  var retValue = goog.dom.getElementByClass(className, opt_root);
+  return goog.asserts.assert(retValue,
+      'No element found with className: ' + className);
+};
 
 
 /**
- * Compares the document order of two nodes, returning 0 if they are the same
- * node, a negative number if node1 is before node2, and a positive number if
- * node2 is before node1.  Note that we compare the order the tags appear in the
- * document so in the tree <b><i>text</i></b> the B node is considered to be
- * before the I node.
- *
- * @param {Node} node1 The first node to compare.
- * @param {Node} node2 The second node to compare.
- * @return {number} 0 if the nodes are the same node, a negative number if node1
- *     is before node2, and a positive number if node2 is before node1.
+ * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and
+ * fast W3C Selectors API.
+ * @param {!(Element|Document)} parent The parent document object.
+ * @return {boolean} whether or not we can use parent.querySelector* APIs.
+ * @private
  */
-goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder;
+goog.dom.canUseQuerySelector_ = function(parent) {
+  return !!(parent.querySelectorAll && parent.querySelector);
+};
 
 
 /**
- * Find the deepest common ancestor of the given nodes.
- * @param {...Node} var_args The nodes to find a common ancestor of.
- * @return {Node} The common ancestor of the nodes, or null if there is none.
- *     null will only be returned if two or more of the nodes are from different
- *     documents.
+ * Helper for {@code getElementsByTagNameAndClass}.
+ * @param {!Document} doc The document to get the elements in.
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ *     property and numerical indices are guaranteed to exist).
+ * @private
  */
-goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor;
-
+goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class,
+                                                  opt_el) {
+  var parent = opt_el || doc;
+  var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : '';
 
-/**
- * Returns the owner document for a node.
- * @param {Node} node The node to get the document for.
- * @return {!Document} The document owning the node.
- */
-goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
+  if (goog.dom.canUseQuerySelector_(parent) &&
+      (tagName || opt_class)) {
+    var query = tagName + (opt_class ? '.' + opt_class : '');
+    return parent.querySelectorAll(query);
+  }
 
+  // Use the native getElementsByClassName if available, under the assumption
+  // that even when the tag name is specified, there will be fewer elements to
+  // filter through when going by class than by tag name
+  if (opt_class && parent.getElementsByClassName) {
+    var els = parent.getElementsByClassName(opt_class);
 
-/**
- * Cross browser function for getting the document element of an iframe.
- * @param {Element} iframe Iframe element.
- * @return {!Document} The frame content document.
- */
-goog.dom.DomHelper.prototype.getFrameContentDocument =
-    goog.dom.getFrameContentDocument;
+    if (tagName) {
+      var arrayLike = {};
+      var len = 0;
 
+      // Filter for specific tags if requested.
+      for (var i = 0, el; el = els[i]; i++) {
+        if (tagName == el.nodeName) {
+          arrayLike[len++] = el;
+        }
+      }
+      arrayLike.length = len;
 
-/**
- * Cross browser function for getting the window of a frame or iframe.
- * @param {Element} frame Frame element.
- * @return {Window} The window associated with the given frame.
- */
-goog.dom.DomHelper.prototype.getFrameContentWindow =
-    goog.dom.getFrameContentWindow;
+      return arrayLike;
+    } else {
+      return els;
+    }
+  }
 
+  var els = parent.getElementsByTagName(tagName || '*');
 
-/**
- * Sets the text content of a node, with cross-browser support.
- * @param {Node} node The node to change the text content of.
- * @param {string|number} text The value that should replace the node's content.
- */
-goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent;
+  if (opt_class) {
+    var arrayLike = {};
+    var len = 0;
+    for (var i = 0, el; el = els[i]; i++) {
+      var className = el.className;
+      // Check if className has a split function since SVG className does not.
+      if (typeof className.split == 'function' &&
+          goog.array.contains(className.split(/\s+/), opt_class)) {
+        arrayLike[len++] = el;
+      }
+    }
+    arrayLike.length = len;
+    return arrayLike;
+  } else {
+    return els;
+  }
+};
 
 
 /**
- * Gets the outerHTML of a node, which islike innerHTML, except that it
- * actually contains the HTML of the node itself.
- * @param {Element} element The element to get the HTML of.
- * @return {string} The outerHTML of the given element.
+ * Alias for {@code getElementsByTagNameAndClass}.
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {Element=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ *     property and numerical indices are guaranteed to exist).
+ * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead.
  */
-goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml;
+goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;
 
 
 /**
- * Finds the first descendant node that matches the filter function. This does
- * a depth first search.
- * @param {Node} root The root of the tree to search.
- * @param {function(Node) : boolean} p The filter function.
- * @return {Node|undefined} The found node or undefined if none is found.
+ * Sets multiple properties on a node.
+ * @param {Element} element DOM node to set properties on.
+ * @param {Object} properties Hash of property:value pairs.
  */
-goog.dom.DomHelper.prototype.findNode = goog.dom.findNode;
+goog.dom.setProperties = function(element, properties) {
+  goog.object.forEach(properties, function(val, key) {
+    if (key == 'style') {
+      element.style.cssText = val;
+    } else if (key == 'class') {
+      element.className = val;
+    } else if (key == 'for') {
+      element.htmlFor = val;
+    } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) {
+      element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val);
+    } else if (goog.string.startsWith(key, 'aria-') ||
+        goog.string.startsWith(key, 'data-')) {
+      element.setAttribute(key, val);
+    } else {
+      element[key] = val;
+    }
+  });
+};
 
 
 /**
- * Finds all the descendant nodes that matches the filter function. This does a
- * depth first search.
- * @param {Node} root The root of the tree to search.
- * @param {function(Node) : boolean} p The filter function.
- * @return {Array.<Node>} The found nodes or an empty array if none are found.
+ * Map of attributes that should be set using
+ * element.setAttribute(key, val) instead of element[key] = val.  Used
+ * by goog.dom.setProperties.
+ *
+ * @private {!Object<string, string>}
+ * @const
  */
-goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
+goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
+  'cellpadding': 'cellPadding',
+  'cellspacing': 'cellSpacing',
+  'colspan': 'colSpan',
+  'frameborder': 'frameBorder',
+  'height': 'height',
+  'maxlength': 'maxLength',
+  'role': 'role',
+  'rowspan': 'rowSpan',
+  'type': 'type',
+  'usemap': 'useMap',
+  'valign': 'vAlign',
+  'width': 'width'
+};
 
 
 /**
- * Returns true if the element has a tab index that allows it to receive
- * keyboard focus (tabIndex >= 0), false otherwise.  Note that some elements
- * natively support keyboard focus, even if they have no tab index.
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element has a tab index that allows keyboard
- *     focus.
+ * Gets the dimensions of the viewport.
+ *
+ * Gecko Standards mode:
+ * docEl.clientWidth  Width of viewport excluding scrollbar.
+ * win.innerWidth     Width of viewport including scrollbar.
+ * body.clientWidth   Width of body element.
+ *
+ * docEl.clientHeight Height of viewport excluding scrollbar.
+ * win.innerHeight    Height of viewport including scrollbar.
+ * body.clientHeight  Height of document.
+ *
+ * Gecko Backwards compatible mode:
+ * docEl.clientWidth  Width of viewport excluding scrollbar.
+ * win.innerWidth     Width of viewport including scrollbar.
+ * body.clientWidth   Width of viewport excluding scrollbar.
+ *
+ * docEl.clientHeight Height of document.
+ * win.innerHeight    Height of viewport including scrollbar.
+ * body.clientHeight  Height of viewport excluding scrollbar.
+ *
+ * IE6/7 Standards mode:
+ * docEl.clientWidth  Width of viewport excluding scrollbar.
+ * win.innerWidth     Undefined.
+ * body.clientWidth   Width of body element.
+ *
+ * docEl.clientHeight Height of viewport excluding scrollbar.
+ * win.innerHeight    Undefined.
+ * body.clientHeight  Height of document element.
+ *
+ * IE5 + IE6/7 Backwards compatible mode:
+ * docEl.clientWidth  0.
+ * win.innerWidth     Undefined.
+ * body.clientWidth   Width of viewport excluding scrollbar.
+ *
+ * docEl.clientHeight 0.
+ * win.innerHeight    Undefined.
+ * body.clientHeight  Height of viewport excluding scrollbar.
+ *
+ * Opera 9 Standards and backwards compatible mode:
+ * docEl.clientWidth  Width of viewport excluding scrollbar.
+ * win.innerWidth     Width of viewport including scrollbar.
+ * body.clientWidth   Width of viewport excluding scrollbar.
+ *
+ * docEl.clientHeight Height of document.
+ * win.innerHeight    Height of viewport including scrollbar.
+ * body.clientHeight  Height of viewport excluding scrollbar.
+ *
+ * WebKit:
+ * Safari 2
+ * docEl.clientHeight Same as scrollHeight.
+ * docEl.clientWidth  Same as innerWidth.
+ * win.innerWidth     Width of viewport excluding scrollbar.
+ * win.innerHeight    Height of the viewport including scrollbar.
+ * frame.innerHeight  Height of the viewport exluding scrollbar.
+ *
+ * Safari 3 (tested in 522)
+ *
+ * docEl.clientWidth  Width of viewport excluding scrollbar.
+ * docEl.clientHeight Height of viewport excluding scrollbar in strict mode.
+ * body.clientHeight  Height of viewport excluding scrollbar in quirks mode.
+ *
+ * @param {Window=} opt_window Optional window element to test.
+ * @return {!goog.math.Size} Object with values 'width' and 'height'.
  */
-goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;
+goog.dom.getViewportSize = function(opt_window) {
+  // TODO(arv): This should not take an argument
+  return goog.dom.getViewportSize_(opt_window || window);
+};
 
 
 /**
- * Enables or disables keyboard focus support on the element via its tab index.
- * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
- * (or elements that natively support keyboard focus, like form elements) can
- * receive keyboard focus.  See http://go/tabindex for more info.
- * @param {Element} element Element whose tab index is to be changed.
- * @param {boolean} enable Whether to set or remove a tab index on the element
- *     that supports keyboard focus.
+ * Helper for {@code getViewportSize}.
+ * @param {Window} win The window to get the view port size for.
+ * @return {!goog.math.Size} Object with values 'width' and 'height'.
+ * @private
  */
-goog.dom.DomHelper.prototype.setFocusableTabIndex =
-    goog.dom.setFocusableTabIndex;
+goog.dom.getViewportSize_ = function(win) {
+  var doc = win.document;
+  var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
+  return new goog.math.Size(el.clientWidth, el.clientHeight);
+};
 
 
 /**
- * Returns true if the element can be focused, i.e. it has a tab index that
- * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
- * that natively supports keyboard focus.
- * @param {Element} element Element to check.
- * @return {boolean} Whether the element allows keyboard focus.
+ * Calculates the height of the document.
+ *
+ * @return {number} The height of the current document.
  */
-goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable;
+goog.dom.getDocumentHeight = function() {
+  return goog.dom.getDocumentHeight_(window);
+};
 
 
 /**
- * Returns the text contents of the current node, without markup. New lines are
- * stripped and whitespace is collapsed, such that each character would be
- * visible.
+ * Calculates the height of the document of the given window.
  *
- * In browsers that support it, innerText is used.  Other browsers attempt to
- * simulate it via node traversal.  Line breaks are canonicalized in IE.
+ * Function code copied from the opensocial gadget api:
+ *   gadgets.window.adjustHeight(opt_height)
  *
- * @param {Node} node The node from which we are getting content.
- * @return {string} The text content.
+ * @private
+ * @param {!Window} win The window whose document height to retrieve.
+ * @return {number} The height of the document of the given window.
  */
-goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
-
+goog.dom.getDocumentHeight_ = function(win) {
+  // NOTE(eae): This method will return the window size rather than the document
+  // size in webkit quirks mode.
+  var doc = win.document;
+  var height = 0;
 
-/**
- * Returns the text length of the text contained in a node, without markup. This
- * is equivalent to the selection length if the node was selected, or the number
- * of cursor movements to traverse the node. Images & BRs take one space.  New
- * lines are ignored.
- *
- * @param {Node} node The node whose text content length is being calculated.
- * @return {number} The length of {@code node}'s text content.
- */
-goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
+  if (doc) {
+    // Calculating inner content height is hard and different between
+    // browsers rendering in Strict vs. Quirks mode.  We use a combination of
+    // three properties within document.body and document.documentElement:
+    // - scrollHeight
+    // - offsetHeight
+    // - clientHeight
+    // These values differ significantly between browsers and rendering modes.
+    // But there are patterns.  It just takes a lot of time and persistence
+    // to figure out.
 
+    var body = doc.body;
+    var docEl = doc.documentElement;
+    if (!(docEl && body)) {
+      return 0;
+    }
 
-/**
- * Returns the text offset of a node relative to one of its ancestors. The text
- * length is the same as the length calculated by
- * {@code goog.dom.getNodeTextLength}.
- *
- * @param {Node} node The node whose offset is being calculated.
- * @param {Node=} opt_offsetParent Defaults to the node's owner document's body.
- * @return {number} The text offset.
- */
-goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
-
-
-/**
- * Returns the node at a given offset in a parent node.  If an object is
- * provided for the optional third parameter, the node and the remainder of the
- * offset will stored as properties of this object.
- * @param {Node} parent The parent node.
- * @param {number} offset The offset into the parent node.
- * @param {Object=} opt_result Object to be used to store the return value. The
- *     return value will be stored in the form {node: Node, remainder: number}
- *     if this object is provided.
- * @return {Node} The node at the given offset.
- */
-goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset;
+    // Get the height of the viewport
+    var vh = goog.dom.getViewportSize_(win).height;
+    if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) {
+      // In Strict mode:
+      // The inner content height is contained in either:
+      //    document.documentElement.scrollHeight
+      //    document.documentElement.offsetHeight
+      // Based on studying the values output by different browsers,
+      // use the value that's NOT equal to the viewport height found above.
+      height = docEl.scrollHeight != vh ?
+          docEl.scrollHeight : docEl.offsetHeight;
+    } else {
+      // In Quirks mode:
+      // documentElement.clientHeight is equal to documentElement.offsetHeight
+      // except in IE.  In most browsers, document.documentElement can be used
+      // to calculate the inner content height.
+      // However, in other browsers (e.g. IE), document.body must be used
+      // instead.  How do we know which one to use?
+      // If document.documentElement.clientHeight does NOT equal
+      // document.documentElement.offsetHeight, then use document.body.
+      var sh = docEl.scrollHeight;
+      var oh = docEl.offsetHeight;
+      if (docEl.clientHeight != oh) {
+        sh = body.scrollHeight;
+        oh = body.offsetHeight;
+      }
 
+      // Detect whether the inner content height is bigger or smaller
+      // than the bounding box (viewport).  If bigger, take the larger
+      // value.  If smaller, take the smaller value.
+      if (sh > vh) {
+        // Content is larger
+        height = sh > oh ? sh : oh;
+      } else {
+        // Content is smaller
+        height = sh < oh ? sh : oh;
+      }
+    }
+  }
 
-/**
- * Returns true if the object is a {@code NodeList}.  To qualify as a NodeList,
- * the object must have a numeric length property and an item function (which
- * has type 'string' on IE for some reason).
- * @param {Object} val Object to test.
- * @return {boolean} Whether the object is a NodeList.
- */
-goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList;
+  return height;
+};
 
 
 /**
- * Walks up the DOM hierarchy returning the first ancestor that has the passed
- * tag name and/or class name. If the passed element matches the specified
- * criteria, the element itself is returned.
- * @param {Node} element The DOM node to start with.
- * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
- *     null/undefined to match only based on class name).
- * @param {?string=} opt_class The class name to match (or null/undefined to
- *     match only based on tag name).
- * @return {Element} The first ancestor that matches the passed criteria, or
- *     null if no match is found.
+ * Gets the page scroll distance as a coordinate object.
+ *
+ * @param {Window=} opt_window Optional window element to test.
+ * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
+ * @deprecated Use {@link goog.dom.getDocumentScroll} instead.
  */
-goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass =
-    goog.dom.getAncestorByTagNameAndClass;
+goog.dom.getPageScroll = function(opt_window) {
+  var win = opt_window || goog.global || window;
+  return goog.dom.getDomHelper(win.document).getDocumentScroll();
+};
 
 
 /**
- * Walks up the DOM hierarchy returning the first ancestor that has the passed
- * class name. If the passed element matches the specified criteria, the
- * element itself is returned.
- * @param {Node} element The DOM node to start with.
- * @param {string} class The class name to match.
- * @return {Element} The first ancestor that matches the passed criteria, or
- *     null if none match.
+ * Gets the document scroll distance as a coordinate object.
+ *
+ * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
  */
-goog.dom.DomHelper.prototype.getAncestorByClass =
-    goog.dom.getAncestorByClass;
+goog.dom.getDocumentScroll = function() {
+  return goog.dom.getDocumentScroll_(document);
+};
 
 
 /**
- * Walks up the DOM hierarchy returning the first ancestor that passes the
- * matcher function.
- * @param {Node} element The DOM node to start with.
- * @param {function(Node) : boolean} matcher A function that returns true if the
- *     passed node matches the desired criteria.
- * @param {boolean=} opt_includeNode If true, the node itself is included in
- *     the search (the first call to the matcher will pass startElement as
- *     the node to test).
- * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
- *     dom.
- * @return {Node} DOM node that matched the matcher, or null if there was
- *     no match.
+ * Helper for {@code getDocumentScroll}.
+ *
+ * @param {!Document} doc The document to get the scroll for.
+ * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
+ * @private
  */
-goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor;
+goog.dom.getDocumentScroll_ = function(doc) {
+  var el = goog.dom.getDocumentScrollElement_(doc);
+  var win = goog.dom.getWindow_(doc);
+  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') &&
+      win.pageYOffset != el.scrollTop) {
+    // The keyboard on IE10 touch devices shifts the page using the pageYOffset
+    // without modifying scrollTop. For this case, we want the body scroll
+    // offsets.
+    return new goog.math.Coordinate(el.scrollLeft, el.scrollTop);
+  }
+  return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft,
+      win.pageYOffset || el.scrollTop);
+};
 
-// Copyright 2012 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Vendor prefix getters.
+ * Gets the document scroll element.
+ * @return {!Element} Scrolling element.
  */
-
-goog.provide('goog.dom.vendor');
-
-goog.require('goog.string');
-goog.require('goog.userAgent');
+goog.dom.getDocumentScrollElement = function() {
+  return goog.dom.getDocumentScrollElement_(document);
+};
 
 
 /**
- * Returns the JS vendor prefix used in CSS properties. Different vendors
- * use different methods of changing the case of the property names.
- *
- * @return {?string} The JS vendor prefix or null if there is none.
+ * Helper for {@code getDocumentScrollElement}.
+ * @param {!Document} doc The document to get the scroll element for.
+ * @return {!Element} Scrolling element.
+ * @private
  */
-goog.dom.vendor.getVendorJsPrefix = function() {
-  if (goog.userAgent.WEBKIT) {
-    return 'Webkit';
-  } else if (goog.userAgent.GECKO) {
-    return 'Moz';
-  } else if (goog.userAgent.IE) {
-    return 'ms';
-  } else if (goog.userAgent.OPERA) {
-    return 'O';
+goog.dom.getDocumentScrollElement_ = function(doc) {
+  // WebKit needs body.scrollLeft in both quirks mode and strict mode. We also
+  // default to the documentElement if the document does not have a body (e.g.
+  // a SVG document).
+  if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) {
+    return doc.documentElement;
   }
-
-  return null;
+  return doc.body || doc.documentElement;
 };
 
 
 /**
- * Returns the vendor prefix used in CSS properties.
+ * Gets the window object associated with the given document.
  *
- * @return {?string} The vendor prefix or null if there is none.
+ * @param {Document=} opt_doc  Document object to get window for.
+ * @return {!Window} The window associated with the given document.
  */
-goog.dom.vendor.getVendorPrefix = function() {
-  if (goog.userAgent.WEBKIT) {
-    return '-webkit';
-  } else if (goog.userAgent.GECKO) {
-    return '-moz';
-  } else if (goog.userAgent.IE) {
-    return '-ms';
-  } else if (goog.userAgent.OPERA) {
-    return '-o';
-  }
-
-  return null;
+goog.dom.getWindow = function(opt_doc) {
+  // TODO(arv): This should not take an argument.
+  return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
 };
 
 
 /**
- * @param {string} propertyName A property name.
- * @param {!Object=} opt_object If provided, we verify if the property exists in
- *     the object.
- * @return {?string} A vendor prefixed property name, or null if it does not
- *     exist.
+ * Helper for {@code getWindow}.
+ *
+ * @param {!Document} doc  Document object to get window for.
+ * @return {!Window} The window associated with the given document.
+ * @private
  */
-goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) {
-  // We first check for a non-prefixed property, if available.
-  if (opt_object && propertyName in opt_object) {
-    return propertyName;
-  }
-  var prefix = goog.dom.vendor.getVendorJsPrefix();
-  if (prefix) {
-    prefix = prefix.toLowerCase();
-    var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName);
-    return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ?
-        prefixedPropertyName : null;
-  }
-  return null;
+goog.dom.getWindow_ = function(doc) {
+  return doc.parentWindow || doc.defaultView;
 };
 
 
 /**
- * @param {string} eventType An event type.
- * @return {string} A lower-cased vendor prefixed event type.
+ * Returns a dom node with a set of attributes.  This function accepts varargs
+ * for subsequent nodes to be added.  Subsequent nodes will be added to the
+ * first node as childNodes.
+ *
+ * So:
+ * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
+ * would return a div with two child paragraphs
+ *
+ * @param {string} tagName Tag to create.
+ * @param {(Object|Array<string>|string)=} opt_attributes If object, then a map
+ *     of name-value pairs for attributes. If a string, then this is the
+ *     className of the new element. If an array, the elements will be joined
+ *     together as the className of the new element.
+ * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
+ *     strings for text nodes. If one of the var_args is an array or NodeList,i
+ *     its elements will be added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
  */
-goog.dom.vendor.getPrefixedEventType = function(eventType) {
-  var prefix = goog.dom.vendor.getVendorJsPrefix() || '';
-  return (prefix + eventType).toLowerCase();
+goog.dom.createDom = function(tagName, opt_attributes, var_args) {
+  return goog.dom.createDom_(document, arguments);
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview A utility class for representing a numeric box.
+ * Helper for {@code createDom}.
+ * @param {!Document} doc The document to create the DOM in.
+ * @param {!Arguments} args Argument object passed from the callers. See
+ *     {@code goog.dom.createDom} for details.
+ * @return {!Element} Reference to a DOM node.
+ * @private
  */
+goog.dom.createDom_ = function(doc, args) {
+  var tagName = args[0];
+  var attributes = args[1];
 
+  // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/
+  //                            dhtml/reference/properties/name_2.asp
+  // Also does not allow setting of 'type' attribute on 'input' or 'button'.
+  if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes &&
+      (attributes.name || attributes.type)) {
+    var tagNameArr = ['<', tagName];
+    if (attributes.name) {
+      tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name),
+                      '"');
+    }
+    if (attributes.type) {
+      tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type),
+                      '"');
 
-goog.provide('goog.math.Box');
-
-goog.require('goog.math.Coordinate');
+      // Clone attributes map to remove 'type' without mutating the input.
+      var clone = {};
+      goog.object.extend(clone, attributes);
 
+      // JSCompiler can't see how goog.object.extend added this property,
+      // because it was essentially added by reflection.
+      // So it needs to be quoted.
+      delete clone['type'];
 
+      attributes = clone;
+    }
+    tagNameArr.push('>');
+    tagName = tagNameArr.join('');
+  }
 
-/**
- * Class for representing a box. A box is specified as a top, right, bottom,
- * and left. A box is useful for representing margins and padding.
- *
- * This class assumes 'screen coordinates': larger Y coordinates are further
- * from the top of the screen.
- *
- * @param {number} top Top.
- * @param {number} right Right.
- * @param {number} bottom Bottom.
- * @param {number} left Left.
- * @constructor
- */
-goog.math.Box = function(top, right, bottom, left) {
-  /**
-   * Top
-   * @type {number}
-   */
-  this.top = top;
+  var element = doc.createElement(tagName);
 
-  /**
-   * Right
-   * @type {number}
-   */
-  this.right = right;
+  if (attributes) {
+    if (goog.isString(attributes)) {
+      element.className = attributes;
+    } else if (goog.isArray(attributes)) {
+      element.className = attributes.join(' ');
+    } else {
+      goog.dom.setProperties(element, attributes);
+    }
+  }
 
-  /**
-   * Bottom
-   * @type {number}
-   */
-  this.bottom = bottom;
+  if (args.length > 2) {
+    goog.dom.append_(doc, element, args, 2);
+  }
 
-  /**
-   * Left
-   * @type {number}
-   */
-  this.left = left;
+  return element;
 };
 
 
 /**
- * Creates a Box by bounding a collection of goog.math.Coordinate objects
- * @param {...goog.math.Coordinate} var_args Coordinates to be included inside
- *     the box.
- * @return {!goog.math.Box} A Box containing all the specified Coordinates.
+ * Appends a node with text or other nodes.
+ * @param {!Document} doc The document to create new nodes in.
+ * @param {!Node} parent The node to append nodes to.
+ * @param {!Arguments} args The values to add. See {@code goog.dom.append}.
+ * @param {number} startIndex The index of the array to start from.
+ * @private
  */
-goog.math.Box.boundingBox = function(var_args) {
-  var box = new goog.math.Box(arguments[0].y, arguments[0].x,
-                              arguments[0].y, arguments[0].x);
-  for (var i = 1; i < arguments.length; i++) {
-    var coord = arguments[i];
-    box.top = Math.min(box.top, coord.y);
-    box.right = Math.max(box.right, coord.x);
-    box.bottom = Math.max(box.bottom, coord.y);
-    box.left = Math.min(box.left, coord.x);
+goog.dom.append_ = function(doc, parent, args, startIndex) {
+  function childHandler(child) {
+    // TODO(user): More coercion, ala MochiKit?
+    if (child) {
+      parent.appendChild(goog.isString(child) ?
+          doc.createTextNode(child) : child);
+    }
+  }
+
+  for (var i = startIndex; i < args.length; i++) {
+    var arg = args[i];
+    // TODO(attila): Fix isArrayLike to return false for a text node.
+    if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
+      // If the argument is a node list, not a real array, use a clone,
+      // because forEach can't be used to mutate a NodeList.
+      goog.array.forEach(goog.dom.isNodeList(arg) ?
+          goog.array.toArray(arg) : arg,
+          childHandler);
+    } else {
+      childHandler(arg);
+    }
   }
-  return box;
 };
 
 
 /**
- * @return {number} width The width of this Box.
+ * Alias for {@code createDom}.
+ * @param {string} tagName Tag to create.
+ * @param {(string|Object)=} opt_attributes If object, then a map of name-value
+ *     pairs for attributes. If a string, then this is the className of the new
+ *     element.
+ * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
+ *     strings for text nodes. If one of the var_args is an array, its
+ *     children will be added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
+ * @deprecated Use {@link goog.dom.createDom} instead.
  */
-goog.math.Box.prototype.getWidth = function() {
-  return this.right - this.left;
-};
+goog.dom.$dom = goog.dom.createDom;
 
 
 /**
- * @return {number} height The height of this Box.
+ * Creates a new element.
+ * @param {string} name Tag name.
+ * @return {!Element} The new element.
  */
-goog.math.Box.prototype.getHeight = function() {
-  return this.bottom - this.top;
+goog.dom.createElement = function(name) {
+  return document.createElement(name);
 };
 
 
 /**
- * Creates a copy of the box with the same dimensions.
- * @return {!goog.math.Box} A clone of this Box.
+ * Creates a new text node.
+ * @param {number|string} content Content.
+ * @return {!Text} The new text node.
  */
-goog.math.Box.prototype.clone = function() {
-  return new goog.math.Box(this.top, this.right, this.bottom, this.left);
+goog.dom.createTextNode = function(content) {
+  return document.createTextNode(String(content));
 };
 
 
-if (goog.DEBUG) {
-  /**
-   * Returns a nice string representing the box.
-   * @return {string} In the form (50t, 73r, 24b, 13l).
-   * @override
-   */
-  goog.math.Box.prototype.toString = function() {
-    return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' +
-           this.left + 'l)';
-  };
-}
-
-
 /**
- * Returns whether the box contains a coordinate or another box.
- *
- * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
- * @return {boolean} Whether the box contains the coordinate or other box.
+ * Create a table.
+ * @param {number} rows The number of rows in the table.  Must be >= 1.
+ * @param {number} columns The number of columns in the table.  Must be >= 1.
+ * @param {boolean=} opt_fillWithNbsp If true, fills table entries with
+ *     {@code goog.string.Unicode.NBSP} characters.
+ * @return {!Element} The created table.
  */
-goog.math.Box.prototype.contains = function(other) {
-  return goog.math.Box.contains(this, other);
+goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) {
+  // TODO(user): Return HTMLTableElement, also in prototype function.
+  // Callers need to be updated to e.g. not assign numbers to table.cellSpacing.
+  return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp);
 };
 
 
 /**
- * Expands box with the given margins.
- *
- * @param {number|goog.math.Box} top Top margin or box with all margins.
- * @param {number=} opt_right Right margin.
- * @param {number=} opt_bottom Bottom margin.
- * @param {number=} opt_left Left margin.
- * @return {!goog.math.Box} A reference to this Box.
+ * Create a table.
+ * @param {!Document} doc Document object to use to create the table.
+ * @param {number} rows The number of rows in the table.  Must be >= 1.
+ * @param {number} columns The number of columns in the table.  Must be >= 1.
+ * @param {boolean} fillWithNbsp If true, fills table entries with
+ *     {@code goog.string.Unicode.NBSP} characters.
+ * @return {!HTMLTableElement} The created table.
+ * @private
  */
-goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom,
-    opt_left) {
-  if (goog.isObject(top)) {
-    this.top -= top.top;
-    this.right += top.right;
-    this.bottom += top.bottom;
-    this.left -= top.left;
-  } else {
-    this.top -= top;
-    this.right += opt_right;
-    this.bottom += opt_bottom;
-    this.left -= opt_left;
+goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) {
+  var table = /** @type {!HTMLTableElement} */
+      (doc.createElement(goog.dom.TagName.TABLE));
+  var tbody = table.appendChild(doc.createElement(goog.dom.TagName.TBODY));
+  for (var i = 0; i < rows; i++) {
+    var tr = doc.createElement(goog.dom.TagName.TR);
+    for (var j = 0; j < columns; j++) {
+      var td = doc.createElement(goog.dom.TagName.TD);
+      // IE <= 9 will create a text node if we set text content to the empty
+      // string, so we avoid doing it unless necessary. This ensures that the
+      // same DOM tree is returned on all browsers.
+      if (fillWithNbsp) {
+        goog.dom.setTextContent(td, goog.string.Unicode.NBSP);
+      }
+      tr.appendChild(td);
+    }
+    tbody.appendChild(tr);
   }
-
-  return this;
+  return table;
 };
 
 
 /**
- * Expand this box to include another box.
- * NOTE(user): This is used in code that needs to be very fast, please don't
- * add functionality to this function at the expense of speed (variable
- * arguments, accepting multiple argument types, etc).
- * @param {goog.math.Box} box The box to include in this one.
+ * Converts HTML markup into a node.
+ * @param {!goog.html.SafeHtml} html The HTML markup to convert.
+ * @return {!Node} The resulting node.
  */
-goog.math.Box.prototype.expandToInclude = function(box) {
-  this.left = Math.min(this.left, box.left);
-  this.top = Math.min(this.top, box.top);
-  this.right = Math.max(this.right, box.right);
-  this.bottom = Math.max(this.bottom, box.bottom);
+goog.dom.safeHtmlToNode = function(html) {
+  return goog.dom.safeHtmlToNode_(document, html);
 };
 
 
 /**
- * Compares boxes for equality.
- * @param {goog.math.Box} a A Box.
- * @param {goog.math.Box} b A Box.
- * @return {boolean} True iff the boxes are equal, or if both are null.
+ * Helper for {@code safeHtmlToNode}.
+ * @param {!Document} doc The document.
+ * @param {!goog.html.SafeHtml} html The HTML markup to convert.
+ * @return {!Node} The resulting node.
+ * @private
  */
-goog.math.Box.equals = function(a, b) {
-  if (a == b) {
-    return true;
-  }
-  if (!a || !b) {
-    return false;
+goog.dom.safeHtmlToNode_ = function(doc, html) {
+  var tempDiv = doc.createElement('div');
+  if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
+    goog.dom.safe.setInnerHtml(tempDiv,
+        goog.html.SafeHtml.concat(goog.html.SafeHtml.create('br'), html));
+    tempDiv.removeChild(tempDiv.firstChild);
+  } else {
+    goog.dom.safe.setInnerHtml(tempDiv, html);
   }
-  return a.top == b.top && a.right == b.right &&
-         a.bottom == b.bottom && a.left == b.left;
+  return goog.dom.childrenToNode_(doc, tempDiv);
 };
 
 
 /**
- * Returns whether a box contains a coordinate or another box.
+ * Converts an HTML string into a document fragment. The string must be
+ * sanitized in order to avoid cross-site scripting. For example
+ * {@code goog.dom.htmlToDocumentFragment('&lt;img src=x onerror=alert(0)&gt;')}
+ * triggers an alert in all browsers, even if the returned document fragment
+ * is thrown away immediately.
  *
- * @param {goog.math.Box} box A Box.
- * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
- * @return {boolean} Whether the box contains the coordinate or other box.
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting document fragment.
  */
-goog.math.Box.contains = function(box, other) {
-  if (!box || !other) {
-    return false;
-  }
-
-  if (other instanceof goog.math.Box) {
-    return other.left >= box.left && other.right <= box.right &&
-        other.top >= box.top && other.bottom <= box.bottom;
-  }
-
-  // other is a Coordinate.
-  return other.x >= box.left && other.x <= box.right &&
-         other.y >= box.top && other.y <= box.bottom;
+goog.dom.htmlToDocumentFragment = function(htmlString) {
+  return goog.dom.htmlToDocumentFragment_(document, htmlString);
 };
 
 
+// TODO(jakubvrana): Merge with {@code safeHtmlToNode_}.
 /**
- * Returns the relative x position of a coordinate compared to a box.  Returns
- * zero if the coordinate is inside the box.
+ * Helper for {@code htmlToDocumentFragment}.
  *
- * @param {goog.math.Box} box A Box.
- * @param {goog.math.Coordinate} coord A Coordinate.
- * @return {number} The x position of {@code coord} relative to the nearest
- *     side of {@code box}, or zero if {@code coord} is inside {@code box}.
+ * @param {!Document} doc The document.
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting document fragment.
+ * @private
  */
-goog.math.Box.relativePositionX = function(box, coord) {
-  if (coord.x < box.left) {
-    return coord.x - box.left;
-  } else if (coord.x > box.right) {
-    return coord.x - box.right;
+goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
+  var tempDiv = doc.createElement('div');
+  if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
+    tempDiv.innerHTML = '<br>' + htmlString;
+    tempDiv.removeChild(tempDiv.firstChild);
+  } else {
+    tempDiv.innerHTML = htmlString;
   }
-  return 0;
+  return goog.dom.childrenToNode_(doc, tempDiv);
 };
 
 
 /**
- * Returns the relative y position of a coordinate compared to a box.  Returns
- * zero if the coordinate is inside the box.
- *
- * @param {goog.math.Box} box A Box.
- * @param {goog.math.Coordinate} coord A Coordinate.
- * @return {number} The y position of {@code coord} relative to the nearest
- *     side of {@code box}, or zero if {@code coord} is inside {@code box}.
+ * Helper for {@code htmlToDocumentFragment_}.
+ * @param {!Document} doc The document.
+ * @param {!Node} tempDiv The input node.
+ * @return {!Node} The resulting node.
+ * @private
  */
-goog.math.Box.relativePositionY = function(box, coord) {
-  if (coord.y < box.top) {
-    return coord.y - box.top;
-  } else if (coord.y > box.bottom) {
-    return coord.y - box.bottom;
+goog.dom.childrenToNode_ = function(doc, tempDiv) {
+  if (tempDiv.childNodes.length == 1) {
+    return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild));
+  } else {
+    var fragment = doc.createDocumentFragment();
+    while (tempDiv.firstChild) {
+      fragment.appendChild(tempDiv.firstChild);
+    }
+    return fragment;
   }
-  return 0;
 };
 
 
 /**
- * Returns the distance between a coordinate and the nearest corner/side of a
- * box. Returns zero if the coordinate is inside the box.
- *
- * @param {goog.math.Box} box A Box.
- * @param {goog.math.Coordinate} coord A Coordinate.
- * @return {number} The distance between {@code coord} and the nearest
- *     corner/side of {@code box}, or zero if {@code coord} is inside
- *     {@code box}.
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @return {boolean} True if in CSS1-compatible mode.
  */
-goog.math.Box.distance = function(box, coord) {
-  var x = goog.math.Box.relativePositionX(box, coord);
-  var y = goog.math.Box.relativePositionY(box, coord);
-  return Math.sqrt(x * x + y * y);
+goog.dom.isCss1CompatMode = function() {
+  return goog.dom.isCss1CompatMode_(document);
 };
 
 
 /**
- * Returns whether two boxes intersect.
- *
- * @param {goog.math.Box} a A Box.
- * @param {goog.math.Box} b A second Box.
- * @return {boolean} Whether the boxes intersect.
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @param {!Document} doc The document to check.
+ * @return {boolean} True if in CSS1-compatible mode.
+ * @private
  */
-goog.math.Box.intersects = function(a, b) {
-  return (a.left <= b.right && b.left <= a.right &&
-          a.top <= b.bottom && b.top <= a.bottom);
-};
-
+goog.dom.isCss1CompatMode_ = function(doc) {
+  if (goog.dom.COMPAT_MODE_KNOWN_) {
+    return goog.dom.ASSUME_STANDARDS_MODE;
+  }
 
-/**
- * Returns whether two boxes would intersect with additional padding.
- *
- * @param {goog.math.Box} a A Box.
- * @param {goog.math.Box} b A second Box.
- * @param {number} padding The additional padding.
- * @return {boolean} Whether the boxes intersect.
- */
-goog.math.Box.intersectsWithPadding = function(a, b, padding) {
-  return (a.left <= b.right + padding && b.left <= a.right + padding &&
-          a.top <= b.bottom + padding && b.top <= a.bottom + padding);
+  return doc.compatMode == 'CSS1Compat';
 };
 
 
 /**
- * Rounds the fields to the next larger integer values.
+ * Determines if the given node can contain children, intended to be used for
+ * HTML generation.
  *
- * @return {!goog.math.Box} This box with ceil'd fields.
+ * IE natively supports node.canHaveChildren but has inconsistent behavior.
+ * Prior to IE8 the base tag allows children and in IE9 all nodes return true
+ * for canHaveChildren.
+ *
+ * In practice all non-IE browsers allow you to add children to any node, but
+ * the behavior is inconsistent:
+ *
+ * <pre>
+ *   var a = document.createElement('br');
+ *   a.appendChild(document.createTextNode('foo'));
+ *   a.appendChild(document.createTextNode('bar'));
+ *   console.log(a.childNodes.length);  // 2
+ *   console.log(a.innerHTML);  // Chrome: "", IE9: "foobar", FF3.5: "foobar"
+ * </pre>
+ *
+ * For more information, see:
+ * http://dev.w3.org/html5/markup/syntax.html#syntax-elements
+ *
+ * TODO(user): Rename shouldAllowChildren() ?
+ *
+ * @param {Node} node The node to check.
+ * @return {boolean} Whether the node can contain children.
  */
-goog.math.Box.prototype.ceil = function() {
-  this.top = Math.ceil(this.top);
-  this.right = Math.ceil(this.right);
-  this.bottom = Math.ceil(this.bottom);
-  this.left = Math.ceil(this.left);
-  return this;
+goog.dom.canHaveChildren = function(node) {
+  if (node.nodeType != goog.dom.NodeType.ELEMENT) {
+    return false;
+  }
+  switch (node.tagName) {
+    case goog.dom.TagName.APPLET:
+    case goog.dom.TagName.AREA:
+    case goog.dom.TagName.BASE:
+    case goog.dom.TagName.BR:
+    case goog.dom.TagName.COL:
+    case goog.dom.TagName.COMMAND:
+    case goog.dom.TagName.EMBED:
+    case goog.dom.TagName.FRAME:
+    case goog.dom.TagName.HR:
+    case goog.dom.TagName.IMG:
+    case goog.dom.TagName.INPUT:
+    case goog.dom.TagName.IFRAME:
+    case goog.dom.TagName.ISINDEX:
+    case goog.dom.TagName.KEYGEN:
+    case goog.dom.TagName.LINK:
+    case goog.dom.TagName.NOFRAMES:
+    case goog.dom.TagName.NOSCRIPT:
+    case goog.dom.TagName.META:
+    case goog.dom.TagName.OBJECT:
+    case goog.dom.TagName.PARAM:
+    case goog.dom.TagName.SCRIPT:
+    case goog.dom.TagName.SOURCE:
+    case goog.dom.TagName.STYLE:
+    case goog.dom.TagName.TRACK:
+    case goog.dom.TagName.WBR:
+      return false;
+  }
+  return true;
 };
 
 
 /**
- * Rounds the fields to the next smaller integer values.
- *
- * @return {!goog.math.Box} This box with floored fields.
+ * Appends a child to a node.
+ * @param {Node} parent Parent.
+ * @param {Node} child Child.
  */
-goog.math.Box.prototype.floor = function() {
-  this.top = Math.floor(this.top);
-  this.right = Math.floor(this.right);
-  this.bottom = Math.floor(this.bottom);
-  this.left = Math.floor(this.left);
-  return this;
+goog.dom.appendChild = function(parent, child) {
+  parent.appendChild(child);
 };
 
 
 /**
- * Rounds the fields to nearest integer values.
- *
- * @return {!goog.math.Box} This box with rounded fields.
+ * Appends a node with text or other nodes.
+ * @param {!Node} parent The node to append nodes to.
+ * @param {...goog.dom.Appendable} var_args The things to append to the node.
+ *     If this is a Node it is appended as is.
+ *     If this is a string then a text node is appended.
+ *     If this is an array like object then fields 0 to length - 1 are appended.
  */
-goog.math.Box.prototype.round = function() {
-  this.top = Math.round(this.top);
-  this.right = Math.round(this.right);
-  this.bottom = Math.round(this.bottom);
-  this.left = Math.round(this.left);
-  return this;
+goog.dom.append = function(parent, var_args) {
+  goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
 };
 
 
 /**
- * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
- * is given, then the left and right values are translated by the coordinate's
- * x value and the top and bottom values are translated by the coordinate's y
- * value.  Otherwise, {@code tx} and {@code opt_ty} are used to translate the x
- * and y dimension values.
- *
- * @param {number|goog.math.Coordinate} tx The value to translate the x
- *     dimension values by or the the coordinate to translate this box by.
- * @param {number=} opt_ty The value to translate y dimension values by.
- * @return {!goog.math.Box} This box after translating.
+ * Removes all the child nodes on a DOM node.
+ * @param {Node} node Node to remove children from.
  */
-goog.math.Box.prototype.translate = function(tx, opt_ty) {
-  if (tx instanceof goog.math.Coordinate) {
-    this.left += tx.x;
-    this.right += tx.x;
-    this.top += tx.y;
-    this.bottom += tx.y;
-  } else {
-    this.left += tx;
-    this.right += tx;
-    if (goog.isNumber(opt_ty)) {
-      this.top += opt_ty;
-      this.bottom += opt_ty;
-    }
+goog.dom.removeChildren = function(node) {
+  // Note: Iterations over live collections can be slow, this is the fastest
+  // we could find. The double parenthesis are used to prevent JsCompiler and
+  // strict warnings.
+  var child;
+  while ((child = node.firstChild)) {
+    node.removeChild(child);
   }
-  return this;
 };
 
 
 /**
- * Scales this coordinate by the given scale factors. The x and y dimension
- * values are scaled by {@code sx} and {@code opt_sy} respectively.
- * If {@code opt_sy} is not given, then {@code sx} is used for both x and y.
- *
- * @param {number} sx The scale factor to use for the x dimension.
- * @param {number=} opt_sy The scale factor to use for the y dimension.
- * @return {!goog.math.Box} This box after scaling.
+ * Inserts a new node before an existing reference node (i.e. as the previous
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert before.
  */
-goog.math.Box.prototype.scale = function(sx, opt_sy) {
-  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
-  this.left *= sx;
-  this.right *= sx;
-  this.top *= sy;
-  this.bottom *= sy;
-  return this;
+goog.dom.insertSiblingBefore = function(newNode, refNode) {
+  if (refNode.parentNode) {
+    refNode.parentNode.insertBefore(newNode, refNode);
+  }
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview A utility class for representing rectangles.
+ * Inserts a new node after an existing reference node (i.e. as the next
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert after.
  */
-
-goog.provide('goog.math.Rect');
-
-goog.require('goog.math.Box');
-goog.require('goog.math.Coordinate');
-goog.require('goog.math.Size');
-
+goog.dom.insertSiblingAfter = function(newNode, refNode) {
+  if (refNode.parentNode) {
+    refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
+  }
+};
 
 
 /**
- * Class for representing rectangular regions.
- * @param {number} x Left.
- * @param {number} y Top.
- * @param {number} w Width.
- * @param {number} h Height.
- * @constructor
+ * Insert a child at a given index. If index is larger than the number of child
+ * nodes that the parent currently has, the node is inserted as the last child
+ * node.
+ * @param {Element} parent The element into which to insert the child.
+ * @param {Node} child The element to insert.
+ * @param {number} index The index at which to insert the new child node. Must
+ *     not be negative.
  */
-goog.math.Rect = function(x, y, w, h) {
-  /** @type {number} */
-  this.left = x;
-
-  /** @type {number} */
-  this.top = y;
-
-  /** @type {number} */
-  this.width = w;
-
-  /** @type {number} */
-  this.height = h;
+goog.dom.insertChildAt = function(parent, child, index) {
+  // Note that if the second argument is null, insertBefore
+  // will append the child at the end of the list of children.
+  parent.insertBefore(child, parent.childNodes[index] || null);
 };
 
 
 /**
- * @return {!goog.math.Rect} A new copy of this Rectangle.
+ * Removes a node from its parent.
+ * @param {Node} node The node to remove.
+ * @return {Node} The node removed if removed; else, null.
  */
-goog.math.Rect.prototype.clone = function() {
-  return new goog.math.Rect(this.left, this.top, this.width, this.height);
+goog.dom.removeNode = function(node) {
+  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
 };
 
 
 /**
- * Returns a new Box object with the same position and dimensions as this
- * rectangle.
- * @return {!goog.math.Box} A new Box representation of this Rectangle.
+ * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
+ * parent.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} oldNode Node to replace.
  */
-goog.math.Rect.prototype.toBox = function() {
-  var right = this.left + this.width;
-  var bottom = this.top + this.height;
-  return new goog.math.Box(this.top,
-                           right,
-                           bottom,
-                           this.left);
+goog.dom.replaceNode = function(newNode, oldNode) {
+  var parent = oldNode.parentNode;
+  if (parent) {
+    parent.replaceChild(newNode, oldNode);
+  }
 };
 
 
 /**
- * Creates a new Rect object with the same position and dimensions as a given
- * Box.  Note that this is only the inverse of toBox if left/top are defined.
- * @param {goog.math.Box} box A box.
- * @return {!goog.math.Rect} A new Rect initialized with the box's position
- *     and size.
+ * Flattens an element. That is, removes it and replace it with its children.
+ * Does nothing if the element is not in the document.
+ * @param {Element} element The element to flatten.
+ * @return {Element|undefined} The original element, detached from the document
+ *     tree, sans children; or undefined, if the element was not in the document
+ *     to begin with.
  */
-goog.math.Rect.createFromBox = function(box) {
-  return new goog.math.Rect(box.left, box.top,
-      box.right - box.left, box.bottom - box.top);
-};
-
+goog.dom.flattenElement = function(element) {
+  var child, parent = element.parentNode;
+  if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
+    // Use IE DOM method (supported by Opera too) if available
+    if (element.removeNode) {
+      return /** @type {Element} */ (element.removeNode(false));
+    } else {
+      // Move all children of the original node up one level.
+      while ((child = element.firstChild)) {
+        parent.insertBefore(child, element);
+      }
 
-if (goog.DEBUG) {
-  /**
-   * Returns a nice string representing size and dimensions of rectangle.
-   * @return {string} In the form (50, 73 - 75w x 25h).
-   * @override
-   */
-  goog.math.Rect.prototype.toString = function() {
-    return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +
-           this.height + 'h)';
-  };
-}
+      // Detach the original element.
+      return /** @type {Element} */ (goog.dom.removeNode(element));
+    }
+  }
+};
 
 
 /**
- * Compares rectangles for equality.
- * @param {goog.math.Rect} a A Rectangle.
- * @param {goog.math.Rect} b A Rectangle.
- * @return {boolean} True iff the rectangles have the same left, top, width,
- *     and height, or if both are null.
+ * Returns an array containing just the element children of the given element.
+ * @param {Element} element The element whose element children we want.
+ * @return {!(Array|NodeList)} An array or array-like list of just the element
+ *     children of the given element.
  */
-goog.math.Rect.equals = function(a, b) {
-  if (a == b) {
-    return true;
-  }
-  if (!a || !b) {
-    return false;
+goog.dom.getChildren = function(element) {
+  // We check if the children attribute is supported for child elements
+  // since IE8 misuses the attribute by also including comments.
+  if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE &&
+      element.children != undefined) {
+    return element.children;
   }
-  return a.left == b.left && a.width == b.width &&
-         a.top == b.top && a.height == b.height;
+  // Fall back to manually filtering the element's child nodes.
+  return goog.array.filter(element.childNodes, function(node) {
+    return node.nodeType == goog.dom.NodeType.ELEMENT;
+  });
 };
 
 
 /**
- * Computes the intersection of this rectangle and the rectangle parameter.  If
- * there is no intersection, returns false and leaves this rectangle as is.
- * @param {goog.math.Rect} rect A Rectangle.
- * @return {boolean} True iff this rectangle intersects with the parameter.
+ * Returns the first child node that is an element.
+ * @param {Node} node The node to get the first child element of.
+ * @return {Element} The first child node of {@code node} that is an element.
  */
-goog.math.Rect.prototype.intersection = function(rect) {
-  var x0 = Math.max(this.left, rect.left);
-  var x1 = Math.min(this.left + this.width, rect.left + rect.width);
-
-  if (x0 <= x1) {
-    var y0 = Math.max(this.top, rect.top);
-    var y1 = Math.min(this.top + this.height, rect.top + rect.height);
-
-    if (y0 <= y1) {
-      this.left = x0;
-      this.top = y0;
-      this.width = x1 - x0;
-      this.height = y1 - y0;
-
-      return true;
-    }
+goog.dom.getFirstElementChild = function(node) {
+  if (node.firstElementChild != undefined) {
+    return /** @type {!Element} */(node).firstElementChild;
   }
-  return false;
+  return goog.dom.getNextElementNode_(node.firstChild, true);
 };
 
 
 /**
- * Returns the intersection of two rectangles. Two rectangles intersect if they
- * touch at all, for example, two zero width and height rectangles would
- * intersect if they had the same top and left.
- * @param {goog.math.Rect} a A Rectangle.
- * @param {goog.math.Rect} b A Rectangle.
- * @return {goog.math.Rect} A new intersection rect (even if width and height
- *     are 0), or null if there is no intersection.
+ * Returns the last child node that is an element.
+ * @param {Node} node The node to get the last child element of.
+ * @return {Element} The last child node of {@code node} that is an element.
  */
-goog.math.Rect.intersection = function(a, b) {
-  // There is no nice way to do intersection via a clone, because any such
-  // clone might be unnecessary if this function returns null.  So, we duplicate
-  // code from above.
-
-  var x0 = Math.max(a.left, b.left);
-  var x1 = Math.min(a.left + a.width, b.left + b.width);
-
-  if (x0 <= x1) {
-    var y0 = Math.max(a.top, b.top);
-    var y1 = Math.min(a.top + a.height, b.top + b.height);
-
-    if (y0 <= y1) {
-      return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
-    }
+goog.dom.getLastElementChild = function(node) {
+  if (node.lastElementChild != undefined) {
+    return /** @type {!Element} */(node).lastElementChild;
   }
-  return null;
+  return goog.dom.getNextElementNode_(node.lastChild, false);
 };
 
 
 /**
- * Returns whether two rectangles intersect. Two rectangles intersect if they
- * touch at all, for example, two zero width and height rectangles would
- * intersect if they had the same top and left.
- * @param {goog.math.Rect} a A Rectangle.
- * @param {goog.math.Rect} b A Rectangle.
- * @return {boolean} Whether a and b intersect.
+ * Returns the first next sibling that is an element.
+ * @param {Node} node The node to get the next sibling element of.
+ * @return {Element} The next sibling of {@code node} that is an element.
  */
-goog.math.Rect.intersects = function(a, b) {
-  return (a.left <= b.left + b.width && b.left <= a.left + a.width &&
-      a.top <= b.top + b.height && b.top <= a.top + a.height);
+goog.dom.getNextElementSibling = function(node) {
+  if (node.nextElementSibling != undefined) {
+    return /** @type {!Element} */(node).nextElementSibling;
+  }
+  return goog.dom.getNextElementNode_(node.nextSibling, true);
 };
 
 
 /**
- * Returns whether a rectangle intersects this rectangle.
- * @param {goog.math.Rect} rect A rectangle.
- * @return {boolean} Whether rect intersects this rectangle.
+ * Returns the first previous sibling that is an element.
+ * @param {Node} node The node to get the previous sibling element of.
+ * @return {Element} The first previous sibling of {@code node} that is
+ *     an element.
  */
-goog.math.Rect.prototype.intersects = function(rect) {
-  return goog.math.Rect.intersects(this, rect);
+goog.dom.getPreviousElementSibling = function(node) {
+  if (node.previousElementSibling != undefined) {
+    return /** @type {!Element} */(node).previousElementSibling;
+  }
+  return goog.dom.getNextElementNode_(node.previousSibling, false);
 };
 
 
 /**
- * Computes the difference regions between two rectangles. The return value is
- * an array of 0 to 4 rectangles defining the remaining regions of the first
- * rectangle after the second has been subtracted.
- * @param {goog.math.Rect} a A Rectangle.
- * @param {goog.math.Rect} b A Rectangle.
- * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which
- *     together define the difference area of rectangle a minus rectangle b.
+ * Returns the first node that is an element in the specified direction,
+ * starting with {@code node}.
+ * @param {Node} node The node to get the next element from.
+ * @param {boolean} forward Whether to look forwards or backwards.
+ * @return {Element} The first element.
+ * @private
  */
-goog.math.Rect.difference = function(a, b) {
-  var intersection = goog.math.Rect.intersection(a, b);
-  if (!intersection || !intersection.height || !intersection.width) {
-    return [a.clone()];
-  }
-
-  var result = [];
-
-  var top = a.top;
-  var height = a.height;
-
-  var ar = a.left + a.width;
-  var ab = a.top + a.height;
-
-  var br = b.left + b.width;
-  var bb = b.top + b.height;
-
-  // Subtract off any area on top where A extends past B
-  if (b.top > a.top) {
-    result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));
-    top = b.top;
-    // If we're moving the top down, we also need to subtract the height diff.
-    height -= b.top - a.top;
-  }
-  // Subtract off any area on bottom where A extends past B
-  if (bb < ab) {
-    result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));
-    height = bb - top;
-  }
-  // Subtract any area on left where A extends past B
-  if (b.left > a.left) {
-    result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));
-  }
-  // Subtract any area on right where A extends past B
-  if (br < ar) {
-    result.push(new goog.math.Rect(br, top, ar - br, height));
+goog.dom.getNextElementNode_ = function(node, forward) {
+  while (node && node.nodeType != goog.dom.NodeType.ELEMENT) {
+    node = forward ? node.nextSibling : node.previousSibling;
   }
 
-  return result;
+  return /** @type {Element} */ (node);
 };
 
 
 /**
- * Computes the difference regions between this rectangle and {@code rect}. The
- * return value is an array of 0 to 4 rectangles defining the remaining regions
- * of this rectangle after the other has been subtracted.
- * @param {goog.math.Rect} rect A Rectangle.
- * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which
- *     together define the difference area of rectangle a minus rectangle b.
+ * Returns the next node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The next node in the DOM tree, or null if this was the last
+ *     node.
  */
-goog.math.Rect.prototype.difference = function(rect) {
-  return goog.math.Rect.difference(this, rect);
-};
-
+goog.dom.getNextNode = function(node) {
+  if (!node) {
+    return null;
+  }
 
-/**
- * Expand this rectangle to also include the area of the given rectangle.
- * @param {goog.math.Rect} rect The other rectangle.
- */
-goog.math.Rect.prototype.boundingRect = function(rect) {
-  // We compute right and bottom before we change left and top below.
-  var right = Math.max(this.left + this.width, rect.left + rect.width);
-  var bottom = Math.max(this.top + this.height, rect.top + rect.height);
+  if (node.firstChild) {
+    return node.firstChild;
+  }
 
-  this.left = Math.min(this.left, rect.left);
-  this.top = Math.min(this.top, rect.top);
+  while (node && !node.nextSibling) {
+    node = node.parentNode;
+  }
 
-  this.width = right - this.left;
-  this.height = bottom - this.top;
+  return node ? node.nextSibling : null;
 };
 
 
 /**
- * Returns a new rectangle which completely contains both input rectangles.
- * @param {goog.math.Rect} a A rectangle.
- * @param {goog.math.Rect} b A rectangle.
- * @return {goog.math.Rect} A new bounding rect, or null if either rect is
- *     null.
+ * Returns the previous node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The previous node in the DOM tree, or null if this was the
+ *     first node.
  */
-goog.math.Rect.boundingRect = function(a, b) {
-  if (!a || !b) {
+goog.dom.getPreviousNode = function(node) {
+  if (!node) {
     return null;
   }
 
-  var clone = a.clone();
-  clone.boundingRect(b);
-
-  return clone;
-};
-
-
-/**
- * Tests whether this rectangle entirely contains another rectangle or
- * coordinate.
- *
- * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or
- *     coordinate to test for containment.
- * @return {boolean} Whether this rectangle contains given rectangle or
- *     coordinate.
- */
-goog.math.Rect.prototype.contains = function(another) {
-  if (another instanceof goog.math.Rect) {
-    return this.left <= another.left &&
-           this.left + this.width >= another.left + another.width &&
-           this.top <= another.top &&
-           this.top + this.height >= another.top + another.height;
-  } else { // (another instanceof goog.math.Coordinate)
-    return another.x >= this.left &&
-           another.x <= this.left + this.width &&
-           another.y >= this.top &&
-           another.y <= this.top + this.height;
+  if (!node.previousSibling) {
+    return node.parentNode;
   }
-};
 
+  node = node.previousSibling;
+  while (node && node.lastChild) {
+    node = node.lastChild;
+  }
 
-/**
- * @param {!goog.math.Coordinate} point A coordinate.
- * @return {number} The squared distance between the point and the closest
- *     point inside the rectangle. Returns 0 if the point is inside the
- *     rectangle.
- */
-goog.math.Rect.prototype.squaredDistance = function(point) {
-  var dx = point.x < this.left ?
-      this.left - point.x : Math.max(point.x - (this.left + this.width), 0);
-  var dy = point.y < this.top ?
-      this.top - point.y : Math.max(point.y - (this.top + this.height), 0);
-  return dx * dx + dy * dy;
+  return node;
 };
 
 
 /**
- * @param {!goog.math.Coordinate} point A coordinate.
- * @return {number} The distance between the point and the closest point
- *     inside the rectangle. Returns 0 if the point is inside the rectangle.
+ * Whether the object looks like a DOM node.
+ * @param {?} obj The object being tested for node likeness.
+ * @return {boolean} Whether the object looks like a DOM node.
  */
-goog.math.Rect.prototype.distance = function(point) {
-  return Math.sqrt(this.squaredDistance(point));
+goog.dom.isNodeLike = function(obj) {
+  return goog.isObject(obj) && obj.nodeType > 0;
 };
 
 
 /**
- * @return {!goog.math.Size} The size of this rectangle.
+ * Whether the object looks like an Element.
+ * @param {?} obj The object being tested for Element likeness.
+ * @return {boolean} Whether the object looks like an Element.
  */
-goog.math.Rect.prototype.getSize = function() {
-  return new goog.math.Size(this.width, this.height);
+goog.dom.isElement = function(obj) {
+  return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
 };
 
 
 /**
- * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
- *     the rectangle.
+ * Returns true if the specified value is a Window object. This includes the
+ * global window for HTML pages, and iframe windows.
+ * @param {?} obj Variable to test.
+ * @return {boolean} Whether the variable is a window.
  */
-goog.math.Rect.prototype.getTopLeft = function() {
-  return new goog.math.Coordinate(this.left, this.top);
+goog.dom.isWindow = function(obj) {
+  return goog.isObject(obj) && obj['window'] == obj;
 };
 
 
 /**
- * @return {!goog.math.Coordinate} A new coordinate for the center of the
- *     rectangle.
+ * Returns an element's parent, if it's an Element.
+ * @param {Element} element The DOM element.
+ * @return {Element} The parent, or null if not an Element.
  */
-goog.math.Rect.prototype.getCenter = function() {
-  return new goog.math.Coordinate(
-      this.left + this.width / 2, this.top + this.height / 2);
+goog.dom.getParentElement = function(element) {
+  var parent;
+  if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) {
+    var isIe9 = goog.userAgent.IE &&
+        goog.userAgent.isVersionOrHigher('9') &&
+        !goog.userAgent.isVersionOrHigher('10');
+    // SVG elements in IE9 can't use the parentElement property.
+    // goog.global['SVGElement'] is not defined in IE9 quirks mode.
+    if (!(isIe9 && goog.global['SVGElement'] &&
+        element instanceof goog.global['SVGElement'])) {
+      parent = element.parentElement;
+      if (parent) {
+        return parent;
+      }
+    }
+  }
+  parent = element.parentNode;
+  return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null;
 };
 
 
 /**
- * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner
- *     of the rectangle.
+ * Whether a node contains another node.
+ * @param {Node} parent The node that should contain the other node.
+ * @param {Node} descendant The node to test presence of.
+ * @return {boolean} Whether the parent node contains the descendent node.
  */
-goog.math.Rect.prototype.getBottomRight = function() {
-  return new goog.math.Coordinate(
-      this.left + this.width, this.top + this.height);
-};
-
+goog.dom.contains = function(parent, descendant) {
+  // We use browser specific methods for this if available since it is faster
+  // that way.
 
-/**
- * Rounds the fields to the next larger integer values.
- * @return {!goog.math.Rect} This rectangle with ceil'd fields.
- */
-goog.math.Rect.prototype.ceil = function() {
-  this.left = Math.ceil(this.left);
-  this.top = Math.ceil(this.top);
-  this.width = Math.ceil(this.width);
-  this.height = Math.ceil(this.height);
-  return this;
-};
+  // IE DOM
+  if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
+    return parent == descendant || parent.contains(descendant);
+  }
 
+  // W3C DOM Level 3
+  if (typeof parent.compareDocumentPosition != 'undefined') {
+    return parent == descendant ||
+        Boolean(parent.compareDocumentPosition(descendant) & 16);
+  }
 
-/**
- * Rounds the fields to the next smaller integer values.
- * @return {!goog.math.Rect} This rectangle with floored fields.
- */
-goog.math.Rect.prototype.floor = function() {
-  this.left = Math.floor(this.left);
-  this.top = Math.floor(this.top);
-  this.width = Math.floor(this.width);
-  this.height = Math.floor(this.height);
-  return this;
+  // W3C DOM Level 1
+  while (descendant && parent != descendant) {
+    descendant = descendant.parentNode;
+  }
+  return descendant == parent;
 };
 
 
 /**
- * Rounds the fields to nearest integer values.
- * @return {!goog.math.Rect} This rectangle with rounded fields.
+ * Compares the document order of two nodes, returning 0 if they are the same
+ * node, a negative number if node1 is before node2, and a positive number if
+ * node2 is before node1.  Note that we compare the order the tags appear in the
+ * document so in the tree <b><i>text</i></b> the B node is considered to be
+ * before the I node.
+ *
+ * @param {Node} node1 The first node to compare.
+ * @param {Node} node2 The second node to compare.
+ * @return {number} 0 if the nodes are the same node, a negative number if node1
+ *     is before node2, and a positive number if node2 is before node1.
  */
-goog.math.Rect.prototype.round = function() {
-  this.left = Math.round(this.left);
-  this.top = Math.round(this.top);
-  this.width = Math.round(this.width);
-  this.height = Math.round(this.height);
-  return this;
-};
+goog.dom.compareNodeOrder = function(node1, node2) {
+  // Fall out quickly for equality.
+  if (node1 == node2) {
+    return 0;
+  }
 
+  // Use compareDocumentPosition where available
+  if (node1.compareDocumentPosition) {
+    // 4 is the bitmask for FOLLOWS.
+    return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
+  }
 
-/**
- * Translates this rectangle by the given offsets. If a
- * {@code goog.math.Coordinate} is given, then the left and top values are
- * translated by the coordinate's x and y values. Otherwise, top and left are
- * translated by {@code tx} and {@code opt_ty} respectively.
- * @param {number|goog.math.Coordinate} tx The value to translate left by or the
- *     the coordinate to translate this rect by.
- * @param {number=} opt_ty The value to translate top by.
- * @return {!goog.math.Rect} This rectangle after translating.
- */
-goog.math.Rect.prototype.translate = function(tx, opt_ty) {
-  if (tx instanceof goog.math.Coordinate) {
-    this.left += tx.x;
-    this.top += tx.y;
-  } else {
-    this.left += tx;
-    if (goog.isNumber(opt_ty)) {
-      this.top += opt_ty;
+  // Special case for document nodes on IE 7 and 8.
+  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
+    if (node1.nodeType == goog.dom.NodeType.DOCUMENT) {
+      return -1;
+    }
+    if (node2.nodeType == goog.dom.NodeType.DOCUMENT) {
+      return 1;
     }
   }
-  return this;
-};
 
+  // Process in IE using sourceIndex - we check to see if the first node has
+  // a source index or if its parent has one.
+  if ('sourceIndex' in node1 ||
+      (node1.parentNode && 'sourceIndex' in node1.parentNode)) {
+    var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
+    var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
 
-/**
- * Scales this rectangle by the given scale factors. The left and width values
- * are scaled by {@code sx} and the top and height values are scaled by
- * {@code opt_sy}.  If {@code opt_sy} is not given, then all fields are scaled
- * by {@code sx}.
- * @param {number} sx The scale factor to use for the x dimension.
- * @param {number=} opt_sy The scale factor to use for the y dimension.
- * @return {!goog.math.Rect} This rectangle after scaling.
- */
-goog.math.Rect.prototype.scale = function(sx, opt_sy) {
-  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
-  this.left *= sx;
-  this.width *= sx;
-  this.top *= sy;
-  this.height *= sy;
-  return this;
-};
+    if (isElement1 && isElement2) {
+      return node1.sourceIndex - node2.sourceIndex;
+    } else {
+      var parent1 = node1.parentNode;
+      var parent2 = node2.parentNode;
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+      if (parent1 == parent2) {
+        return goog.dom.compareSiblingOrder_(node1, node2);
+      }
 
-/**
- * @fileoverview Utilities for element styles.
- *
- * @see ../demos/inline_block_quirks.html
- * @see ../demos/inline_block_standards.html
- * @see ../demos/style_viewport.html
- */
+      if (!isElement1 && goog.dom.contains(parent1, node2)) {
+        return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
+      }
 
-goog.provide('goog.style');
 
+      if (!isElement2 && goog.dom.contains(parent2, node1)) {
+        return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
+      }
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.NodeType');
-goog.require('goog.dom.vendor');
-goog.require('goog.math.Box');
-goog.require('goog.math.Coordinate');
-goog.require('goog.math.Rect');
-goog.require('goog.math.Size');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('goog.userAgent');
+      return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
+             (isElement2 ? node2.sourceIndex : parent2.sourceIndex);
+    }
+  }
 
+  // For Safari, we compare ranges.
+  var doc = goog.dom.getOwnerDocument(node1);
 
-/**
- * @define {boolean} Whether we know at compile time that
- *     getBoundingClientRect() is present and bug-free on the browser.
- */
-goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false);
+  var range1, range2;
+  range1 = doc.createRange();
+  range1.selectNode(node1);
+  range1.collapse(true);
 
+  range2 = doc.createRange();
+  range2.selectNode(node2);
+  range2.collapse(true);
 
-/**
- * Sets a style value on an element.
- *
- * This function is not indended to patch issues in the browser's style
- * handling, but to allow easy programmatic access to setting dash-separated
- * style properties.  An example is setting a batch of properties from a data
- * object without overwriting old styles.  When possible, use native APIs:
- * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
- * elem.style.cssText = 'property1: value1; property2: value2'.
- *
- * @param {Element} element The element to change.
- * @param {string|Object} style If a string, a style name. If an object, a hash
- *     of style names to style values.
- * @param {string|number|boolean=} opt_value If style was a string, then this
- *     should be the value.
- */
-goog.style.setStyle = function(element, style, opt_value) {
-  if (goog.isString(style)) {
-    goog.style.setStyle_(element, opt_value, style);
-  } else {
-    goog.object.forEach(style, goog.partial(goog.style.setStyle_, element));
-  }
+  return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END,
+      range2);
 };
 
 
 /**
- * Sets a style value on an element, with parameters swapped to work with
- * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
- * necessary.
- * @param {Element} element The element to change.
- * @param {string|number|boolean|undefined} value Style value.
- * @param {string} style Style name.
+ * Utility function to compare the position of two nodes, when
+ * {@code textNode}'s parent is an ancestor of {@code node}.  If this entry
+ * condition is not met, this function will attempt to reference a null object.
+ * @param {!Node} textNode The textNode to compare.
+ * @param {Node} node The node to compare.
+ * @return {number} -1 if node is before textNode, +1 otherwise.
  * @private
  */
-goog.style.setStyle_ = function(element, value, style) {
-  var propertyName = goog.style.getVendorJsStyleName_(element, style);
-
-  if (propertyName) {
-    element.style[propertyName] = value;
+goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
+  var parent = textNode.parentNode;
+  if (parent == node) {
+    // If textNode is a child of node, then node comes first.
+    return -1;
   }
+  var sibling = node;
+  while (sibling.parentNode != parent) {
+    sibling = sibling.parentNode;
+  }
+  return goog.dom.compareSiblingOrder_(sibling, textNode);
 };
 
 
 /**
- * Returns the style property name in camel-case. If it does not exist and a
- * vendor-specific version of the property does exist, then return the vendor-
- * specific property name instead.
- * @param {Element} element The element to change.
- * @param {string} style Style name.
- * @return {string} Vendor-specific style.
+ * Utility function to compare the position of two nodes known to be non-equal
+ * siblings.
+ * @param {Node} node1 The first node to compare.
+ * @param {!Node} node2 The second node to compare.
+ * @return {number} -1 if node1 is before node2, +1 otherwise.
  * @private
  */
-goog.style.getVendorJsStyleName_ = function(element, style) {
-  var camelStyle = goog.string.toCamelCase(style);
-
-  if (element.style[camelStyle] === undefined) {
-    var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
-        goog.string.toTitleCase(camelStyle);
-
-    if (element.style[prefixedStyle] !== undefined) {
-      return prefixedStyle;
+goog.dom.compareSiblingOrder_ = function(node1, node2) {
+  var s = node2;
+  while ((s = s.previousSibling)) {
+    if (s == node1) {
+      // We just found node1 before node2.
+      return -1;
     }
   }
 
-  return camelStyle;
+  // Since we didn't find it, node1 must be after node2.
+  return 1;
 };
 
 
 /**
- * Returns the style property name in CSS notation. If it does not exist and a
- * vendor-specific version of the property does exist, then return the vendor-
- * specific property name instead.
- * @param {Element} element The element to change.
- * @param {string} style Style name.
- * @return {string} Vendor-specific style.
- * @private
+ * Find the deepest common ancestor of the given nodes.
+ * @param {...Node} var_args The nodes to find a common ancestor of.
+ * @return {Node} The common ancestor of the nodes, or null if there is none.
+ *     null will only be returned if two or more of the nodes are from different
+ *     documents.
  */
-goog.style.getVendorStyleName_ = function(element, style) {
-  var camelStyle = goog.string.toCamelCase(style);
-
-  if (element.style[camelStyle] === undefined) {
-    var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
-        goog.string.toTitleCase(camelStyle);
-
-    if (element.style[prefixedStyle] !== undefined) {
-      return goog.dom.vendor.getVendorPrefix() + '-' + style;
-    }
+goog.dom.findCommonAncestor = function(var_args) {
+  var i, count = arguments.length;
+  if (!count) {
+    return null;
+  } else if (count == 1) {
+    return arguments[0];
   }
 
-  return style;
-};
-
+  var paths = [];
+  var minLength = Infinity;
+  for (i = 0; i < count; i++) {
+    // Compute the list of ancestors.
+    var ancestors = [];
+    var node = arguments[i];
+    while (node) {
+      ancestors.unshift(node);
+      node = node.parentNode;
+    }
 
-/**
- * Retrieves an explicitly-set style value of a node. This returns '' if there
- * isn't a style attribute on the element or if this style property has not been
- * explicitly set in script.
- *
- * @param {Element} element Element to get style of.
- * @param {string} property Property to get, css-style (if you have a camel-case
- * property, use element.style[style]).
- * @return {string} Style value.
- */
-goog.style.getStyle = function(element, property) {
-  // element.style is '' for well-known properties which are unset.
-  // For for browser specific styles as 'filter' is undefined
-  // so we need to return '' explicitly to make it consistent across
-  // browsers.
-  var styleValue = element.style[goog.string.toCamelCase(property)];
-
-  // Using typeof here because of a bug in Safari 5.1, where this value
-  // was undefined, but === undefined returned false.
-  if (typeof(styleValue) !== 'undefined') {
-    return styleValue;
+    // Save the list for comparison.
+    paths.push(ancestors);
+    minLength = Math.min(minLength, ancestors.length);
   }
-
-  return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
-      '';
+  var output = null;
+  for (i = 0; i < minLength; i++) {
+    var first = paths[0][i];
+    for (var j = 1; j < count; j++) {
+      if (first != paths[j][i]) {
+        return output;
+      }
+    }
+    output = first;
+  }
+  return output;
 };
 
 
 /**
- * Retrieves a computed style value of a node. It returns empty string if the
- * value cannot be computed (which will be the case in Internet Explorer) or
- * "none" if the property requested is an SVG one and it has not been
- * explicitly set (firefox and webkit).
- *
- * @param {Element} element Element to get style of.
- * @param {string} property Property to get (camel-case).
- * @return {string} Style value.
+ * Returns the owner document for a node.
+ * @param {Node|Window} node The node to get the document for.
+ * @return {!Document} The document owning the node.
  */
-goog.style.getComputedStyle = function(element, property) {
-  var doc = goog.dom.getOwnerDocument(element);
-  if (doc.defaultView && doc.defaultView.getComputedStyle) {
-    var styles = doc.defaultView.getComputedStyle(element, null);
-    if (styles) {
-      // element.style[..] is undefined for browser specific styles
-      // as 'filter'.
-      return styles[property] || styles.getPropertyValue(property) || '';
-    }
-  }
-
-  return '';
+goog.dom.getOwnerDocument = function(node) {
+  // TODO(nnaze): Update param signature to be non-nullable.
+  goog.asserts.assert(node, 'Node cannot be null or undefined.');
+  return /** @type {!Document} */ (
+      node.nodeType == goog.dom.NodeType.DOCUMENT ? node :
+      node.ownerDocument || node.document);
 };
 
 
 /**
- * Gets the cascaded style value of a node, or null if the value cannot be
- * computed (only Internet Explorer can do this).
- *
- * @param {Element} element Element to get style of.
- * @param {string} style Property to get (camel-case).
- * @return {string} Style value.
+ * Cross-browser function for getting the document element of a frame or iframe.
+ * @param {Element} frame Frame element.
+ * @return {!Document} The frame content document.
  */
-goog.style.getCascadedStyle = function(element, style) {
-  // TODO(nicksantos): This should be documented to return null. #fixTypes
-  return element.currentStyle ? element.currentStyle[style] : null;
+goog.dom.getFrameContentDocument = function(frame) {
+  var doc = frame.contentDocument || frame.contentWindow.document;
+  return doc;
 };
 
 
 /**
- * Cross-browser pseudo get computed style. It returns the computed style where
- * available. If not available it tries the cascaded style value (IE
- * currentStyle) and in worst case the inline style value.  It shouldn't be
- * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
- * discussion.
- *
- * @param {Element} element Element to get style of.
- * @param {string} style Property to get (must be camelCase, not css-style.).
- * @return {string} Style value.
- * @private
+ * Cross-browser function for getting the window of a frame or iframe.
+ * @param {Element} frame Frame element.
+ * @return {Window} The window associated with the given frame.
  */
-goog.style.getStyle_ = function(element, style) {
-  return goog.style.getComputedStyle(element, style) ||
-         goog.style.getCascadedStyle(element, style) ||
-         (element.style && element.style[style]);
+goog.dom.getFrameContentWindow = function(frame) {
+  return frame.contentWindow ||
+      goog.dom.getWindow(goog.dom.getFrameContentDocument(frame));
 };
 
 
 /**
- * Retrieves the computed value of the box-sizing CSS attribute.
- * Browser support: http://caniuse.com/css3-boxsizing.
- * @param {!Element} element The element whose box-sizing to get.
- * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
- *     box-sizing is not supported (IE7 and below).
+ * Sets the text content of a node, with cross-browser support.
+ * @param {Node} node The node to change the text content of.
+ * @param {string|number} text The value that should replace the node's content.
  */
-goog.style.getComputedBoxSizing = function(element) {
-  return goog.style.getStyle_(element, 'boxSizing') ||
-      goog.style.getStyle_(element, 'MozBoxSizing') ||
-      goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
+goog.dom.setTextContent = function(node, text) {
+  goog.asserts.assert(node != null,
+      'goog.dom.setTextContent expects a non-null value for node');
+
+  if ('textContent' in node) {
+    node.textContent = text;
+  } else if (node.nodeType == goog.dom.NodeType.TEXT) {
+    node.data = text;
+  } else if (node.firstChild &&
+             node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
+    // If the first child is a text node we just change its data and remove the
+    // rest of the children.
+    while (node.lastChild != node.firstChild) {
+      node.removeChild(node.lastChild);
+    }
+    node.firstChild.data = text;
+  } else {
+    goog.dom.removeChildren(node);
+    var doc = goog.dom.getOwnerDocument(node);
+    node.appendChild(doc.createTextNode(String(text)));
+  }
 };
 
 
 /**
- * Retrieves the computed value of the position CSS attribute.
- * @param {Element} element The element to get the position of.
- * @return {string} Position value.
+ * Gets the outerHTML of a node, which islike innerHTML, except that it
+ * actually contains the HTML of the node itself.
+ * @param {Element} element The element to get the HTML of.
+ * @return {string} The outerHTML of the given element.
  */
-goog.style.getComputedPosition = function(element) {
-  return goog.style.getStyle_(element, 'position');
+goog.dom.getOuterHtml = function(element) {
+  // IE, Opera and WebKit all have outerHTML.
+  if ('outerHTML' in element) {
+    return element.outerHTML;
+  } else {
+    var doc = goog.dom.getOwnerDocument(element);
+    var div = doc.createElement('div');
+    div.appendChild(element.cloneNode(true));
+    return div.innerHTML;
+  }
 };
 
 
 /**
- * Retrieves the computed background color string for a given element. The
- * string returned is suitable for assigning to another element's
- * background-color, but is not guaranteed to be in any particular string
- * format. Accessing the color in a numeric form may not be possible in all
- * browsers or with all input.
- *
- * If the background color for the element is defined as a hexadecimal value,
- * the resulting string can be parsed by goog.color.parse in all supported
- * browsers.
+ * Finds the first descendant node that matches the filter function, using
+ * a depth first search. This function offers the most general purpose way
+ * of finding a matching element. You may also wish to consider
+ * {@code goog.dom.query} which can express many matching criteria using
+ * CSS selector expressions. These expressions often result in a more
+ * compact representation of the desired result.
+ * @see goog.dom.query
  *
- * Whether named colors like "red" or "lightblue" get translated into a
- * format which can be parsed is browser dependent. Calling this function on
- * transparent elements will return "transparent" in most browsers or
- * "rgba(0, 0, 0, 0)" in WebKit.
- * @param {Element} element The element to get the background color of.
- * @return {string} The computed string value of the background color.
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {Node|undefined} The found node or undefined if none is found.
  */
-goog.style.getBackgroundColor = function(element) {
-  return goog.style.getStyle_(element, 'backgroundColor');
+goog.dom.findNode = function(root, p) {
+  var rv = [];
+  var found = goog.dom.findNodes_(root, p, rv, true);
+  return found ? rv[0] : undefined;
 };
 
 
 /**
- * Retrieves the computed value of the overflow-x CSS attribute.
- * @param {Element} element The element to get the overflow-x of.
- * @return {string} The computed string value of the overflow-x attribute.
+ * Finds all the descendant nodes that match the filter function, using a
+ * a depth first search. This function offers the most general-purpose way
+ * of finding a set of matching elements. You may also wish to consider
+ * {@code goog.dom.query} which can express many matching criteria using
+ * CSS selector expressions. These expressions often result in a more
+ * compact representation of the desired result.
+
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {!Array<!Node>} The found nodes or an empty array if none are found.
  */
-goog.style.getComputedOverflowX = function(element) {
-  return goog.style.getStyle_(element, 'overflowX');
+goog.dom.findNodes = function(root, p) {
+  var rv = [];
+  goog.dom.findNodes_(root, p, rv, false);
+  return rv;
 };
 
 
 /**
- * Retrieves the computed value of the overflow-y CSS attribute.
- * @param {Element} element The element to get the overflow-y of.
- * @return {string} The computed string value of the overflow-y attribute.
+ * Finds the first or all the descendant nodes that match the filter function,
+ * using a depth first search.
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @param {!Array<!Node>} rv The found nodes are added to this array.
+ * @param {boolean} findOne If true we exit after the first found node.
+ * @return {boolean} Whether the search is complete or not. True in case findOne
+ *     is true and the node is found. False otherwise.
+ * @private
  */
-goog.style.getComputedOverflowY = function(element) {
-  return goog.style.getStyle_(element, 'overflowY');
+goog.dom.findNodes_ = function(root, p, rv, findOne) {
+  if (root != null) {
+    var child = root.firstChild;
+    while (child) {
+      if (p(child)) {
+        rv.push(child);
+        if (findOne) {
+          return true;
+        }
+      }
+      if (goog.dom.findNodes_(child, p, rv, findOne)) {
+        return true;
+      }
+      child = child.nextSibling;
+    }
+  }
+  return false;
 };
 
 
 /**
- * Retrieves the computed value of the z-index CSS attribute.
- * @param {Element} element The element to get the z-index of.
- * @return {string|number} The computed value of the z-index attribute.
+ * Map of tags whose content to ignore when calculating text length.
+ * @private {!Object<string, number>}
+ * @const
  */
-goog.style.getComputedZIndex = function(element) {
-  return goog.style.getStyle_(element, 'zIndex');
+goog.dom.TAGS_TO_IGNORE_ = {
+  'SCRIPT': 1,
+  'STYLE': 1,
+  'HEAD': 1,
+  'IFRAME': 1,
+  'OBJECT': 1
 };
 
 
 /**
- * Retrieves the computed value of the text-align CSS attribute.
- * @param {Element} element The element to get the text-align of.
- * @return {string} The computed string value of the text-align attribute.
+ * Map of tags which have predefined values with regard to whitespace.
+ * @private {!Object<string, string>}
+ * @const
  */
-goog.style.getComputedTextAlign = function(element) {
-  return goog.style.getStyle_(element, 'textAlign');
-};
+goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};
 
 
 /**
- * Retrieves the computed value of the cursor CSS attribute.
- * @param {Element} element The element to get the cursor of.
- * @return {string} The computed string value of the cursor attribute.
+ * Returns true if the element has a tab index that allows it to receive
+ * keyboard focus (tabIndex >= 0), false otherwise.  Note that some elements
+ * natively support keyboard focus, even if they have no tab index.
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element has a tab index that allows keyboard
+ *     focus.
+ * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
  */
-goog.style.getComputedCursor = function(element) {
-  return goog.style.getStyle_(element, 'cursor');
+goog.dom.isFocusableTabIndex = function(element) {
+  return goog.dom.hasSpecifiedTabIndex_(element) &&
+         goog.dom.isTabIndexFocusable_(element);
 };
 
 
 /**
- * Retrieves the computed value of the CSS transform attribute.
- * @param {Element} element The element to get the transform of.
- * @return {string} The computed string representation of the transform matrix.
+ * Enables or disables keyboard focus support on the element via its tab index.
+ * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
+ * (or elements that natively support keyboard focus, like form elements) can
+ * receive keyboard focus.  See http://go/tabindex for more info.
+ * @param {Element} element Element whose tab index is to be changed.
+ * @param {boolean} enable Whether to set or remove a tab index on the element
+ *     that supports keyboard focus.
  */
-goog.style.getComputedTransform = function(element) {
-  var property = goog.style.getVendorStyleName_(element, 'transform');
-  return goog.style.getStyle_(element, property) ||
-      goog.style.getStyle_(element, 'transform');
+goog.dom.setFocusableTabIndex = function(element, enable) {
+  if (enable) {
+    element.tabIndex = 0;
+  } else {
+    // Set tabIndex to -1 first, then remove it. This is a workaround for
+    // Safari (confirmed in version 4 on Windows). When removing the attribute
+    // without setting it to -1 first, the element remains keyboard focusable
+    // despite not having a tabIndex attribute anymore.
+    element.tabIndex = -1;
+    element.removeAttribute('tabIndex'); // Must be camelCase!
+  }
 };
 
 
 /**
- * Sets the top/left values of an element.  If no unit is specified in the
- * argument then it will add px. The second argument is required if the first
- * argument is a string or number and is ignored if the first argument
- * is a coordinate.
- * @param {Element} el Element to move.
- * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
- * @param {string|number=} opt_arg2 Top position.
+ * Returns true if the element can be focused, i.e. it has a tab index that
+ * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
+ * that natively supports keyboard focus.
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element allows keyboard focus.
  */
-goog.style.setPosition = function(el, arg1, opt_arg2) {
-  var x, y;
-  var buggyGeckoSubPixelPos = goog.userAgent.GECKO &&
-      (goog.userAgent.MAC || goog.userAgent.X11) &&
-      goog.userAgent.isVersionOrHigher('1.9');
-
-  if (arg1 instanceof goog.math.Coordinate) {
-    x = arg1.x;
-    y = arg1.y;
+goog.dom.isFocusable = function(element) {
+  var focusable;
+  // Some elements can have unspecified tab index and still receive focus.
+  if (goog.dom.nativelySupportsFocus_(element)) {
+    // Make sure the element is not disabled ...
+    focusable = !element.disabled &&
+        // ... and if a tab index is specified, it allows focus.
+        (!goog.dom.hasSpecifiedTabIndex_(element) ||
+         goog.dom.isTabIndexFocusable_(element));
   } else {
-    x = arg1;
-    y = opt_arg2;
+    focusable = goog.dom.isFocusableTabIndex(element);
   }
 
-  // Round to the nearest pixel for buggy sub-pixel support.
-  el.style.left = goog.style.getPixelStyleValue_(
-      /** @type {number|string} */ (x), buggyGeckoSubPixelPos);
-  el.style.top = goog.style.getPixelStyleValue_(
-      /** @type {number|string} */ (y), buggyGeckoSubPixelPos);
+  // IE requires elements to be visible in order to focus them.
+  return focusable && goog.userAgent.IE ?
+             goog.dom.hasNonZeroBoundingRect_(element) : focusable;
 };
 
 
 /**
- * Gets the offsetLeft and offsetTop properties of an element and returns them
- * in a Coordinate object
- * @param {Element} element Element.
- * @return {!goog.math.Coordinate} The position.
+ * Returns true if the element has a specified tab index.
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element has a specified tab index.
+ * @private
  */
-goog.style.getPosition = function(element) {
-  return new goog.math.Coordinate(element.offsetLeft, element.offsetTop);
+goog.dom.hasSpecifiedTabIndex_ = function(element) {
+  // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(),
+  // which returns an object with a 'specified' property if tabIndex is
+  // specified.  This works on other browsers, too.
+  var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
+  return goog.isDefAndNotNull(attrNode) && attrNode.specified;
 };
 
 
 /**
- * Returns the viewport element for a particular document
- * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
- *     of.
- * @return {Element} document.documentElement or document.body.
+ * Returns true if the element's tab index allows the element to be focused.
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element's tab index allows focus.
+ * @private
  */
-goog.style.getClientViewportElement = function(opt_node) {
-  var doc;
-  if (opt_node) {
-    doc = goog.dom.getOwnerDocument(opt_node);
-  } else {
-    doc = goog.dom.getDocument();
-  }
-
-  // In old IE versions the document.body represented the viewport
-  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
-      !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
-    return doc.body;
-  }
-  return doc.documentElement;
+goog.dom.isTabIndexFocusable_ = function(element) {
+  var index = element.tabIndex;
+  // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534.
+  return goog.isNumber(index) && index >= 0 && index < 32768;
 };
 
 
 /**
- * Calculates the viewport coordinates relative to the page/document
- * containing the node. The viewport may be the browser viewport for
- * non-iframe document, or the iframe container for iframe'd document.
- * @param {!Document} doc The document to use as the reference point.
- * @return {!goog.math.Coordinate} The page offset of the viewport.
+ * Returns true if the element is focusable even when tabIndex is not set.
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element natively supports focus.
+ * @private
  */
-goog.style.getViewportPageOffset = function(doc) {
-  var body = doc.body;
-  var documentElement = doc.documentElement;
-  var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
-  var scrollTop = body.scrollTop || documentElement.scrollTop;
-  return new goog.math.Coordinate(scrollLeft, scrollTop);
+goog.dom.nativelySupportsFocus_ = function(element) {
+  return element.tagName == goog.dom.TagName.A ||
+         element.tagName == goog.dom.TagName.INPUT ||
+         element.tagName == goog.dom.TagName.TEXTAREA ||
+         element.tagName == goog.dom.TagName.SELECT ||
+         element.tagName == goog.dom.TagName.BUTTON;
 };
 
 
 /**
- * Gets the client rectangle of the DOM element.
- *
- * getBoundingClientRect is part of a new CSS object model draft (with a
- * long-time presence in IE), replacing the error-prone parent offset
- * computation and the now-deprecated Gecko getBoxObjectFor.
- *
- * This utility patches common browser bugs in getBoundingClientRect. It
- * will fail if getBoundingClientRect is unsupported.
- *
- * If the element is not in the DOM, the result is undefined, and an error may
- * be thrown depending on user agent.
- *
- * @param {!Element} el The element whose bounding rectangle is being queried.
- * @return {Object} A native bounding rectangle with numerical left, top,
- *     right, and bottom.  Reported by Firefox to be of object type ClientRect.
+ * Returns true if the element has a bounding rectangle that would be visible
+ * (i.e. its width and height are greater than zero).
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element has a non-zero bounding rectangle.
  * @private
  */
-goog.style.getBoundingClientRect_ = function(el) {
-  var rect;
-  try {
-    rect = el.getBoundingClientRect();
-  } catch (e) {
-    // In IE < 9, calling getBoundingClientRect on an orphan element raises an
-    // "Unspecified Error". All other browsers return zeros.
-    return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
-  }
-
-  // Patch the result in IE only, so that this function can be inlined if
-  // compiled for non-IE.
-  if (goog.userAgent.IE && el.ownerDocument.body) {
-
-    // In IE, most of the time, 2 extra pixels are added to the top and left
-    // due to the implicit 2-pixel inset border.  In IE6/7 quirks mode and
-    // IE6 standards mode, this border can be overridden by setting the
-    // document element's border to zero -- thus, we cannot rely on the
-    // offset always being 2 pixels.
-
-    // In quirks mode, the offset can be determined by querying the body's
-    // clientLeft/clientTop, but in standards mode, it is found by querying
-    // the document element's clientLeft/clientTop.  Since we already called
-    // getBoundingClientRect we have already forced a reflow, so it is not
-    // too expensive just to query them all.
-
-    // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
-    var doc = el.ownerDocument;
-    rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
-    rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
-  }
-  return /** @type {Object} */ (rect);
+goog.dom.hasNonZeroBoundingRect_ = function(element) {
+  var rect = goog.isFunction(element['getBoundingClientRect']) ?
+      element.getBoundingClientRect() :
+      {'height': element.offsetHeight, 'width': element.offsetWidth};
+  return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0;
 };
 
 
 /**
- * Returns the first parent that could affect the position of a given element.
- * @param {Element} element The element to get the offset parent for.
- * @return {Element} The first offset parent or null if one cannot be found.
+ * Returns the text content of the current node, without markup and invisible
+ * symbols. New lines are stripped and whitespace is collapsed,
+ * such that each character would be visible.
+ *
+ * In browsers that support it, innerText is used.  Other browsers attempt to
+ * simulate it via node traversal.  Line breaks are canonicalized in IE.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The text content.
  */
-goog.style.getOffsetParent = function(element) {
-  // element.offsetParent does the right thing in IE7 and below.  In other
-  // browsers it only includes elements with position absolute, relative or
-  // fixed, not elements with overflow set to auto or scroll.
-  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
-    return element.offsetParent;
+goog.dom.getTextContent = function(node) {
+  var textContent;
+  // Note(arv): IE9, Opera, and Safari 3 support innerText but they include
+  // text nodes in script tags. So we revert to use a user agent test here.
+  if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) {
+    textContent = goog.string.canonicalizeNewlines(node.innerText);
+    // Unfortunately .innerText() returns text with &shy; symbols
+    // We need to filter it out and then remove duplicate whitespaces
+  } else {
+    var buf = [];
+    goog.dom.getTextContent_(node, buf, true);
+    textContent = buf.join('');
   }
 
-  var doc = goog.dom.getOwnerDocument(element);
-  var positionStyle = goog.style.getStyle_(element, 'position');
-  var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
-  for (var parent = element.parentNode; parent && parent != doc;
-       parent = parent.parentNode) {
-    positionStyle =
-        goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
-    skipStatic = skipStatic && positionStyle == 'static' &&
-                 parent != doc.documentElement && parent != doc.body;
-    if (!skipStatic && (parent.scrollWidth > parent.clientWidth ||
-                        parent.scrollHeight > parent.clientHeight ||
-                        positionStyle == 'fixed' ||
-                        positionStyle == 'absolute' ||
-                        positionStyle == 'relative')) {
-      return /** @type {!Element} */ (parent);
-    }
+  // Strip &shy; entities. goog.format.insertWordBreaks inserts them in Opera.
+  textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
+  // Strip &#8203; entities. goog.format.insertWordBreaks inserts them in IE8.
+  textContent = textContent.replace(/\u200B/g, '');
+
+  // Skip this replacement on old browsers with working innerText, which
+  // automatically turns &nbsp; into ' ' and / +/ into ' ' when reading
+  // innerText.
+  if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) {
+    textContent = textContent.replace(/ +/g, ' ');
   }
-  return null;
+  if (textContent != ' ') {
+    textContent = textContent.replace(/^\s*/, '');
+  }
+
+  return textContent;
 };
 
 
 /**
- * Calculates and returns the visible rectangle for a given element. Returns a
- * box describing the visible portion of the nearest scrollable offset ancestor.
- * Coordinates are given relative to the document.
+ * Returns the text content of the current node, without markup.
  *
- * @param {Element} element Element to get the visible rect for.
- * @return {goog.math.Box} Bounding elementBox describing the visible rect or
- *     null if scrollable ancestor isn't inside the visible viewport.
+ * Unlike {@code getTextContent} this method does not collapse whitespaces
+ * or normalize lines breaks.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The raw text content.
  */
-goog.style.getVisibleRectForElement = function(element) {
-  var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
-  var dom = goog.dom.getDomHelper(element);
-  var body = dom.getDocument().body;
-  var documentElement = dom.getDocument().documentElement;
-  var scrollEl = dom.getDocumentScrollElement();
-
-  // Determine the size of the visible rect by climbing the dom accounting for
-  // all scrollable containers.
-  for (var el = element; el = goog.style.getOffsetParent(el); ) {
-    // clientWidth is zero for inline block elements in IE.
-    // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
-    if ((!goog.userAgent.IE || el.clientWidth != 0) &&
-        (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
-        // body may have overflow set on it, yet we still get the entire
-        // viewport. In some browsers, el.offsetParent may be
-        // document.documentElement, so check for that too.
-        (el != body && el != documentElement &&
-            goog.style.getStyle_(el, 'overflow') != 'visible')) {
-      var pos = goog.style.getPageOffset(el);
-      var client = goog.style.getClientLeftTop(el);
-      pos.x += client.x;
-      pos.y += client.y;
-
-      visibleRect.top = Math.max(visibleRect.top, pos.y);
-      visibleRect.right = Math.min(visibleRect.right,
-                                   pos.x + el.clientWidth);
-      visibleRect.bottom = Math.min(visibleRect.bottom,
-                                    pos.y + el.clientHeight);
-      visibleRect.left = Math.max(visibleRect.left, pos.x);
-    }
-  }
+goog.dom.getRawTextContent = function(node) {
+  var buf = [];
+  goog.dom.getTextContent_(node, buf, false);
 
-  // Clip by window's viewport.
-  var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
-  visibleRect.left = Math.max(visibleRect.left, scrollX);
-  visibleRect.top = Math.max(visibleRect.top, scrollY);
-  var winSize = dom.getViewportSize();
-  visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
-  visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
-  return visibleRect.top >= 0 && visibleRect.left >= 0 &&
-         visibleRect.bottom > visibleRect.top &&
-         visibleRect.right > visibleRect.left ?
-         visibleRect : null;
+  return buf.join('');
 };
 
 
 /**
- * Calculate the scroll position of {@code container} with the minimum amount so
- * that the content and the borders of the given {@code element} become visible.
- * If the element is bigger than the container, its top left corner will be
- * aligned as close to the container's top left corner as possible.
+ * Recursive support function for text content retrieval.
  *
- * @param {Element} element The element to make visible.
- * @param {Element} container The container to scroll.
- * @param {boolean=} opt_center Whether to center the element in the container.
- *     Defaults to false.
- * @return {!goog.math.Coordinate} The new scroll position of the container,
- *     in form of goog.math.Coordinate(scrollLeft, scrollTop).
+ * @param {Node} node The node from which we are getting content.
+ * @param {Array<string>} buf string buffer.
+ * @param {boolean} normalizeWhitespace Whether to normalize whitespace.
+ * @private
  */
-goog.style.getContainerOffsetToScrollInto =
-    function(element, container, opt_center) {
-  // Absolute position of the element's border's top left corner.
-  var elementPos = goog.style.getPageOffset(element);
-  // Absolute position of the container's border's top left corner.
-  var containerPos = goog.style.getPageOffset(container);
-  var containerBorder = goog.style.getBorderBox(container);
-  // Relative pos. of the element's border box to the container's content box.
-  var relX = elementPos.x - containerPos.x - containerBorder.left;
-  var relY = elementPos.y - containerPos.y - containerBorder.top;
-  // How much the element can move in the container, i.e. the difference between
-  // the element's bottom-right-most and top-left-most position where it's
-  // fully visible.
-  var spaceX = container.clientWidth - element.offsetWidth;
-  var spaceY = container.clientHeight - element.offsetHeight;
-
-  var scrollLeft = container.scrollLeft;
-  var scrollTop = container.scrollTop;
-  if (opt_center) {
-    // All browsers round non-integer scroll positions down.
-    scrollLeft += relX - spaceX / 2;
-    scrollTop += relY - spaceY / 2;
+goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
+  if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
+    // ignore certain tags
+  } else if (node.nodeType == goog.dom.NodeType.TEXT) {
+    if (normalizeWhitespace) {
+      buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
+    } else {
+      buf.push(node.nodeValue);
+    }
+  } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
+    buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]);
   } else {
-    // This formula was designed to give the correct scroll values in the
-    // following cases:
-    // - element is higher than container (spaceY < 0) => scroll down by relY
-    // - element is not higher that container (spaceY >= 0):
-    //   - it is above container (relY < 0) => scroll up by abs(relY)
-    //   - it is below container (relY > spaceY) => scroll down by relY - spaceY
-    //   - it is in the container => don't scroll
-    scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
-    scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
+    var child = node.firstChild;
+    while (child) {
+      goog.dom.getTextContent_(child, buf, normalizeWhitespace);
+      child = child.nextSibling;
+    }
   }
-  return new goog.math.Coordinate(scrollLeft, scrollTop);
 };
 
 
 /**
- * Changes the scroll position of {@code container} with the minimum amount so
- * that the content and the borders of the given {@code element} become visible.
- * If the element is bigger than the container, its top left corner will be
- * aligned as close to the container's top left corner as possible.
+ * Returns the text length of the text contained in a node, without markup. This
+ * is equivalent to the selection length if the node was selected, or the number
+ * of cursor movements to traverse the node. Images & BRs take one space.  New
+ * lines are ignored.
  *
- * @param {Element} element The element to make visible.
- * @param {Element} container The container to scroll.
- * @param {boolean=} opt_center Whether to center the element in the container.
- *     Defaults to false.
+ * @param {Node} node The node whose text content length is being calculated.
+ * @return {number} The length of {@code node}'s text content.
  */
-goog.style.scrollIntoContainerView = function(element, container, opt_center) {
-  var offset =
-      goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
-  container.scrollLeft = offset.x;
-  container.scrollTop = offset.y;
+goog.dom.getNodeTextLength = function(node) {
+  return goog.dom.getTextContent(node).length;
 };
 
 
 /**
- * Returns clientLeft (width of the left border and, if the directionality is
- * right to left, the vertical scrollbar) and clientTop as a coordinate object.
+ * Returns the text offset of a node relative to one of its ancestors. The text
+ * length is the same as the length calculated by goog.dom.getNodeTextLength.
  *
- * @param {Element} el Element to get clientLeft for.
- * @return {!goog.math.Coordinate} Client left and top.
+ * @param {Node} node The node whose offset is being calculated.
+ * @param {Node=} opt_offsetParent The node relative to which the offset will
+ *     be calculated. Defaults to the node's owner document's body.
+ * @return {number} The text offset.
  */
-goog.style.getClientLeftTop = function(el) {
-  // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see
-  // https://bugzilla.mozilla.org/show_bug.cgi?id=111207
-  if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) {
-    var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth'));
-    if (goog.style.isRightToLeft(el)) {
-      var scrollbarWidth = el.offsetWidth - el.clientWidth - left -
-          parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth'));
-      left += scrollbarWidth;
+goog.dom.getNodeTextOffset = function(node, opt_offsetParent) {
+  var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body;
+  var buf = [];
+  while (node && node != root) {
+    var cur = node;
+    while ((cur = cur.previousSibling)) {
+      buf.unshift(goog.dom.getTextContent(cur));
     }
-    return new goog.math.Coordinate(left,
-        parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth')));
+    node = node.parentNode;
   }
-
-  return new goog.math.Coordinate(el.clientLeft, el.clientTop);
+  // Trim left to deal with FF cases when there might be line breaks and empty
+  // nodes at the front of the text
+  return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length;
 };
 
 
 /**
- * Returns a Coordinate object relative to the top-left of the HTML document.
- * Implemented as a single function to save having to do two recursive loops in
- * opera and safari just to get both coordinates.  If you just want one value do
- * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
- * note if you call both those methods the tree will be analysed twice.
- *
- * @param {Element} el Element to get the page offset for.
- * @return {!goog.math.Coordinate} The page offset.
+ * Returns the node at a given offset in a parent node.  If an object is
+ * provided for the optional third parameter, the node and the remainder of the
+ * offset will stored as properties of this object.
+ * @param {Node} parent The parent node.
+ * @param {number} offset The offset into the parent node.
+ * @param {Object=} opt_result Object to be used to store the return value. The
+ *     return value will be stored in the form {node: Node, remainder: number}
+ *     if this object is provided.
+ * @return {Node} The node at the given offset.
  */
-goog.style.getPageOffset = function(el) {
-  var box, doc = goog.dom.getOwnerDocument(el);
-  var positionStyle = goog.style.getStyle_(el, 'position');
-  // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
-  goog.asserts.assertObject(el, 'Parameter is required');
-
-  // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the
-  // position. When invoked for an element with position absolute and a negative
-  // position though it can be off by one. Therefor the recursive implementation
-  // is used in those (relatively rare) cases.
-  var BUGGY_GECKO_BOX_OBJECT =
-      !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS &&
-      goog.userAgent.GECKO && doc.getBoxObjectFor &&
-      !el.getBoundingClientRect && positionStyle == 'absolute' &&
-      (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0);
-
-  // NOTE(arv): If element is hidden (display none or disconnected or any the
-  // ancestors are hidden) we get (0,0) by default but we still do the
-  // accumulation of scroll position.
-
-  // TODO(arv): Should we check if the node is disconnected and in that case
-  //            return (0,0)?
-
-  var pos = new goog.math.Coordinate(0, 0);
-  var viewportElement = goog.style.getClientViewportElement(doc);
-  if (el == viewportElement) {
-    // viewport is always at 0,0 as that defined the coordinate system for this
-    // function - this avoids special case checks in the code below
-    return pos;
+goog.dom.getNodeAtOffset = function(parent, offset, opt_result) {
+  var stack = [parent], pos = 0, cur = null;
+  while (stack.length > 0 && pos < offset) {
+    cur = stack.pop();
+    if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) {
+      // ignore certain tags
+    } else if (cur.nodeType == goog.dom.NodeType.TEXT) {
+      var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' ');
+      pos += text.length;
+    } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
+      pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length;
+    } else {
+      for (var i = cur.childNodes.length - 1; i >= 0; i--) {
+        stack.push(cur.childNodes[i]);
+      }
+    }
+  }
+  if (goog.isObject(opt_result)) {
+    opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0;
+    opt_result.node = cur;
   }
 
-  // IE, Gecko 1.9+, and most modern WebKit.
-  if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
-      el.getBoundingClientRect) {
-    box = goog.style.getBoundingClientRect_(el);
-    // Must add the scroll coordinates in to get the absolute page offset
-    // of element since getBoundingClientRect returns relative coordinates to
-    // the viewport.
-    var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
-    pos.x = box.left + scrollCoord.x;
-    pos.y = box.top + scrollCoord.y;
-
-  // Gecko prior to 1.9.
-  } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) {
-    // Gecko ignores the scroll values for ancestors, up to 1.9.  See:
-    // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
-    // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
-
-    box = doc.getBoxObjectFor(el);
-    // TODO(user): Fix the off-by-one error when window is scrolled down
-    // or right more than 1 pixel. The viewport offset does not move in lock
-    // step with the window scroll; it moves in increments of 2px and at
-    // somewhat random intervals.
-    var vpBox = doc.getBoxObjectFor(viewportElement);
-    pos.x = box.screenX - vpBox.screenX;
-    pos.y = box.screenY - vpBox.screenY;
-
-  // Safari, Opera and Camino up to 1.0.4.
-  } else {
-    var parent = el;
-    do {
-      pos.x += parent.offsetLeft;
-      pos.y += parent.offsetTop;
-      // For safari/chrome, we need to add parent's clientLeft/Top as well.
-      if (parent != el) {
-        pos.x += parent.clientLeft || 0;
-        pos.y += parent.clientTop || 0;
-      }
-      // In Safari when hit a position fixed element the rest of the offsets
-      // are not correct.
-      if (goog.userAgent.WEBKIT &&
-          goog.style.getComputedPosition(parent) == 'fixed') {
-        pos.x += doc.body.scrollLeft;
-        pos.y += doc.body.scrollTop;
-        break;
-      }
-      parent = parent.offsetParent;
-    } while (parent && parent != el);
+  return cur;
+};
 
-    // Opera & (safari absolute) incorrectly account for body offsetTop.
-    if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT &&
-        positionStyle == 'absolute')) {
-      pos.y -= doc.body.offsetTop;
-    }
 
-    for (parent = el; (parent = goog.style.getOffsetParent(parent)) &&
-        parent != doc.body && parent != viewportElement; ) {
-      pos.x -= parent.scrollLeft;
-      // Workaround for a bug in Opera 9.2 (and earlier) where table rows may
-      // report an invalid scroll top value. The bug was fixed in Opera 9.5
-      // however as that version supports getBoundingClientRect it won't
-      // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965
-      if (!goog.userAgent.OPERA || parent.tagName != 'TR') {
-        pos.y -= parent.scrollTop;
-      }
+/**
+ * Returns true if the object is a {@code NodeList}.  To qualify as a NodeList,
+ * the object must have a numeric length property and an item function (which
+ * has type 'string' on IE for some reason).
+ * @param {Object} val Object to test.
+ * @return {boolean} Whether the object is a NodeList.
+ */
+goog.dom.isNodeList = function(val) {
+  // TODO(attila): Now the isNodeList is part of goog.dom we can use
+  // goog.userAgent to make this simpler.
+  // A NodeList must have a length property of type 'number' on all platforms.
+  if (val && typeof val.length == 'number') {
+    // A NodeList is an object everywhere except Safari, where it's a function.
+    if (goog.isObject(val)) {
+      // A NodeList must have an item function (on non-IE platforms) or an item
+      // property of type 'string' (on IE).
+      return typeof val.item == 'function' || typeof val.item == 'string';
+    } else if (goog.isFunction(val)) {
+      // On Safari, a NodeList is a function with an item property that is also
+      // a function.
+      return typeof val.item == 'function';
     }
   }
 
-  return pos;
+  // Not a NodeList.
+  return false;
 };
 
 
 /**
- * Returns the left coordinate of an element relative to the HTML document
- * @param {Element} el Elements.
- * @return {number} The left coordinate.
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * tag name and/or class name. If the passed element matches the specified
+ * criteria, the element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
+ *     null/undefined to match only based on class name).
+ * @param {?string=} opt_class The class name to match (or null/undefined to
+ *     match only based on tag name).
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ *     dom.
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ *     null if no match is found.
  */
-goog.style.getPageOffsetLeft = function(el) {
-  return goog.style.getPageOffset(el).x;
+goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class,
+    opt_maxSearchSteps) {
+  if (!opt_tag && !opt_class) {
+    return null;
+  }
+  var tagName = opt_tag ? opt_tag.toUpperCase() : null;
+  return /** @type {Element} */ (goog.dom.getAncestor(element,
+      function(node) {
+        return (!tagName || node.nodeName == tagName) &&
+               (!opt_class || goog.isString(node.className) &&
+                   goog.array.contains(node.className.split(/\s+/), opt_class));
+      }, true, opt_maxSearchSteps));
 };
 
 
 /**
- * Returns the top coordinate of an element relative to the HTML document
- * @param {Element} el Elements.
- * @return {number} The top coordinate.
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * class name. If the passed element matches the specified criteria, the
+ * element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {string} className The class name to match.
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ *     dom.
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ *     null if none match.
  */
-goog.style.getPageOffsetTop = function(el) {
-  return goog.style.getPageOffset(el).y;
+goog.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) {
+  return goog.dom.getAncestorByTagNameAndClass(element, null, className,
+      opt_maxSearchSteps);
 };
 
 
 /**
- * Returns a Coordinate object relative to the top-left of an HTML document
- * in an ancestor frame of this element. Used for measuring the position of
- * an element inside a frame relative to a containing frame.
- *
- * @param {Element} el Element to get the page offset for.
- * @param {Window} relativeWin The window to measure relative to. If relativeWin
- *     is not in the ancestor frame chain of the element, we measure relative to
- *     the top-most window.
- * @return {!goog.math.Coordinate} The page offset.
+ * Walks up the DOM hierarchy returning the first ancestor that passes the
+ * matcher function.
+ * @param {Node} element The DOM node to start with.
+ * @param {function(Node) : boolean} matcher A function that returns true if the
+ *     passed node matches the desired criteria.
+ * @param {boolean=} opt_includeNode If true, the node itself is included in
+ *     the search (the first call to the matcher will pass startElement as
+ *     the node to test).
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ *     dom.
+ * @return {Node} DOM node that matched the matcher, or null if there was
+ *     no match.
  */
-goog.style.getFramedPageOffset = function(el, relativeWin) {
-  var position = new goog.math.Coordinate(0, 0);
+goog.dom.getAncestor = function(
+    element, matcher, opt_includeNode, opt_maxSearchSteps) {
+  if (!opt_includeNode) {
+    element = element.parentNode;
+  }
+  var ignoreSearchSteps = opt_maxSearchSteps == null;
+  var steps = 0;
+  while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) {
+    if (matcher(element)) {
+      return element;
+    }
+    element = element.parentNode;
+    steps++;
+  }
+  // Reached the root of the DOM without a match
+  return null;
+};
 
-  // Iterate up the ancestor frame chain, keeping track of the current window
-  // and the current element in that window.
-  var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
-  var currentEl = el;
-  do {
-    // if we're at the top window, we want to get the page offset.
-    // if we're at an inner frame, we only want to get the window position
-    // so that we can determine the actual page offset in the context of
-    // the outer window.
-    var offset = currentWin == relativeWin ?
-        goog.style.getPageOffset(currentEl) :
-        goog.style.getClientPositionForElement_(
-            goog.asserts.assert(currentEl));
 
-    position.x += offset.x;
-    position.y += offset.y;
-  } while (currentWin && currentWin != relativeWin &&
-      (currentEl = currentWin.frameElement) &&
-      (currentWin = currentWin.parent));
+/**
+ * Determines the active element in the given document.
+ * @param {Document} doc The document to look in.
+ * @return {Element} The active element.
+ */
+goog.dom.getActiveElement = function(doc) {
+  try {
+    return doc && doc.activeElement;
+  } catch (e) {
+    // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE
+    // throws an exception. I'm not 100% sure why, but I suspect it chokes
+    // on document.activeElement if the activeElement has been recently
+    // removed from the DOM by a JS operation.
+    //
+    // We assume that an exception here simply means
+    // "there is no active element."
+  }
 
-  return position;
+  return null;
 };
 
 
 /**
- * Translates the specified rect relative to origBase page, for newBase page.
- * If origBase and newBase are the same, this function does nothing.
+ * Gives the current devicePixelRatio.
  *
- * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
- *     and it will have the translated result.
- * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
- * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
- *     coordinate.  This must be a DOM for an ancestor frame of origBase
- *     or the same as origBase.
+ * By default, this is the value of window.devicePixelRatio (which should be
+ * preferred if present).
+ *
+ * If window.devicePixelRatio is not present, the ratio is calculated with
+ * window.matchMedia, if present. Otherwise, gives 1.0.
+ *
+ * Some browsers (including Chrome) consider the browser zoom level in the pixel
+ * ratio, so the value may change across multiple calls.
+ *
+ * @return {number} The number of actual pixels per virtual pixel.
  */
-goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
-  if (origBase.getDocument() != newBase.getDocument()) {
-    var body = origBase.getDocument().body;
-    var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
-
-    // Adjust Body's margin.
-    pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
+goog.dom.getPixelRatio = function() {
+  var win = goog.dom.getWindow();
 
-    if (goog.userAgent.IE && !origBase.isCss1CompatMode()) {
-      pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
-    }
+  // devicePixelRatio does not work on Mobile firefox.
+  // TODO(user): Enable this check on a known working mobile Gecko version.
+  // Filed a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=896804
+  var isFirefoxMobile = goog.userAgent.GECKO && goog.userAgent.MOBILE;
 
-    rect.left += pos.x;
-    rect.top += pos.y;
+  if (goog.isDef(win.devicePixelRatio) && !isFirefoxMobile) {
+    return win.devicePixelRatio;
+  } else if (win.matchMedia) {
+    return goog.dom.matchesPixelRatio_(.75) ||
+           goog.dom.matchesPixelRatio_(1.5) ||
+           goog.dom.matchesPixelRatio_(2) ||
+           goog.dom.matchesPixelRatio_(3) || 1;
   }
+  return 1;
 };
 
 
 /**
- * Returns the position of an element relative to another element in the
- * document.  A relative to B
- * @param {Element|Event|goog.events.Event} a Element or mouse event whose
- *     position we're calculating.
- * @param {Element|Event|goog.events.Event} b Element or mouse event position
- *     is relative to.
- * @return {!goog.math.Coordinate} The relative position.
+ * Calculates a mediaQuery to check if the current device supports the
+ * given actual to virtual pixel ratio.
+ * @param {number} pixelRatio The ratio of actual pixels to virtual pixels.
+ * @return {number} pixelRatio if applicable, otherwise 0.
+ * @private
  */
-goog.style.getRelativePosition = function(a, b) {
-  var ap = goog.style.getClientPosition(a);
-  var bp = goog.style.getClientPosition(b);
-  return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
+goog.dom.matchesPixelRatio_ = function(pixelRatio) {
+  var win = goog.dom.getWindow();
+  var query = ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' +
+               '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' +
+               '(min-resolution: ' + pixelRatio + 'dppx)');
+  return win.matchMedia(query).matches ? pixelRatio : 0;
 };
 
 
+
 /**
- * Returns the position of the event or the element's border box relative to
- * the client viewport.
- * @param {!Element} el Element whose position to get.
- * @return {!goog.math.Coordinate} The position.
- * @private
- */
-goog.style.getClientPositionForElement_ = function(el) {
-  var pos;
-  if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
-      el.getBoundingClientRect) {
-    // IE, Gecko 1.9+, and most modern WebKit
-    var box = goog.style.getBoundingClientRect_(el);
-    pos = new goog.math.Coordinate(box.left, box.top);
-  } else {
-    var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll();
-    var pageCoord = goog.style.getPageOffset(el);
-    pos = new goog.math.Coordinate(
-        pageCoord.x - scrollCoord.x,
-        pageCoord.y - scrollCoord.y);
-  }
-
-  // Gecko below version 12 doesn't add CSS translation to the client position
-  // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do
-  // so manually.
-  if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) {
-    return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el));
-  } else {
-    return pos;
-  }
+ * Create an instance of a DOM helper with a new document object.
+ * @param {Document=} opt_document Document object to associate with this
+ *     DOM helper.
+ * @constructor
+ */
+goog.dom.DomHelper = function(opt_document) {
+  /**
+   * Reference to the document object to use
+   * @type {!Document}
+   * @private
+   */
+  this.document_ = opt_document || goog.global.document || document;
 };
 
 
 /**
- * Returns the position of the event or the element's border box relative to
- * the client viewport.
- * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
- * @return {!goog.math.Coordinate} The position.
+ * Gets the dom helper object for the document where the element resides.
+ * @param {Node=} opt_node If present, gets the DomHelper for this node.
+ * @return {!goog.dom.DomHelper} The DomHelper.
  */
-goog.style.getClientPosition = function(el) {
-  goog.asserts.assert(el);
-  if (el.nodeType == goog.dom.NodeType.ELEMENT) {
-    return goog.style.getClientPositionForElement_(
-        /** @type {!Element} */ (el));
-  } else {
-    var isAbstractedEvent = goog.isFunction(el.getBrowserEvent);
-    var be = /** @type {!goog.events.BrowserEvent} */ (el);
-    var targetEvent = el;
+goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;
 
-    if (el.targetTouches) {
-      targetEvent = el.targetTouches[0];
-    } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches) {
-      targetEvent = be.getBrowserEvent().targetTouches[0];
-    }
 
-    return new goog.math.Coordinate(
-        targetEvent.clientX,
-        targetEvent.clientY);
-  }
+/**
+ * Sets the document object.
+ * @param {!Document} document Document object.
+ */
+goog.dom.DomHelper.prototype.setDocument = function(document) {
+  this.document_ = document;
 };
 
 
 /**
- * Moves an element to the given coordinates relative to the client viewport.
- * @param {Element} el Absolutely positioned element to set page offset for.
- *     It must be in the document.
- * @param {number|goog.math.Coordinate} x Left position of the element's margin
- *     box or a coordinate object.
- * @param {number=} opt_y Top position of the element's margin box.
+ * Gets the document object being used by the dom library.
+ * @return {!Document} Document object.
  */
-goog.style.setPageOffset = function(el, x, opt_y) {
-  // Get current pageoffset
-  var cur = goog.style.getPageOffset(el);
-
-  if (x instanceof goog.math.Coordinate) {
-    opt_y = x.y;
-    x = x.x;
-  }
-
-  // NOTE(arv): We cannot allow strings for x and y. We could but that would
-  // require us to manually transform between different units
+goog.dom.DomHelper.prototype.getDocument = function() {
+  return this.document_;
+};
 
-  // Work out deltas
-  var dx = x - cur.x;
-  var dy = opt_y - cur.y;
 
-  // Set position to current left/top + delta
-  goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy);
+/**
+ * Alias for {@code getElementById}. If a DOM node is passed in then we just
+ * return that.
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
+ */
+goog.dom.DomHelper.prototype.getElement = function(element) {
+  return goog.dom.getElementHelper_(this.document_, element);
 };
 
 
 /**
- * Sets the width/height values of an element.  If an argument is numeric,
- * or a goog.math.Size is passed, it is assumed to be pixels and will add
- * 'px' after converting it to an integer in string form. (This just sets the
- * CSS width and height properties so it might set content-box or border-box
- * size depending on the box model the browser is using.)
+ * Gets an element by id, asserting that the element is found.
  *
- * @param {Element} element Element to set the size of.
- * @param {string|number|goog.math.Size} w Width of the element, or a
- *     size object.
- * @param {string|number=} opt_h Height of the element. Required if w is not a
- *     size object.
+ * This is used when an element is expected to exist, and should fail with
+ * an assertion error if it does not (if assertions are enabled).
+ *
+ * @param {string} id Element ID.
+ * @return {!Element} The element with the given ID, if it exists.
  */
-goog.style.setSize = function(element, w, opt_h) {
-  var h;
-  if (w instanceof goog.math.Size) {
-    h = w.height;
-    w = w.width;
-  } else {
-    if (opt_h == undefined) {
-      throw Error('missing height argument');
-    }
-    h = opt_h;
-  }
-
-  goog.style.setWidth(element, /** @type {string|number} */ (w));
-  goog.style.setHeight(element, /** @type {string|number} */ (h));
+goog.dom.DomHelper.prototype.getRequiredElement = function(id) {
+  return goog.dom.getRequiredElementHelper_(this.document_, id);
 };
 
 
 /**
- * Helper function to create a string to be set into a pixel-value style
- * property of an element. Can round to the nearest integer value.
- *
- * @param {string|number} value The style value to be used. If a number,
- *     'px' will be appended, otherwise the value will be applied directly.
- * @param {boolean} round Whether to round the nearest integer (if property
- *     is a number).
- * @return {string} The string value for the property.
- * @private
+ * Alias for {@code getElement}.
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
+ * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead.
  */
-goog.style.getPixelStyleValue_ = function(value, round) {
-  if (typeof value == 'number') {
-    value = (round ? Math.round(value) : value) + 'px';
-  }
-
-  return value;
-};
+goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
 
 
 /**
- * Set the height of an element.  Sets the element's style property.
- * @param {Element} element Element to set the height of.
- * @param {string|number} height The height value to set.  If a number, 'px'
- *     will be appended, otherwise the value will be applied directly.
+ * Looks up elements by both tag and class name, using browser native functions
+ * ({@code querySelectorAll}, {@code getElementsByTagName} or
+ * {@code getElementsByClassName}) where possible. The returned array is a live
+ * NodeList or a static list depending on the code path taken.
+ *
+ * @see goog.dom.query
+ *
+ * @param {?string=} opt_tag Element tag name or * for all tags.
+ * @param {?string=} opt_class Optional class name.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ *     property and numerical indices are guaranteed to exist).
  */
-goog.style.setHeight = function(element, height) {
-  element.style.height = goog.style.getPixelStyleValue_(height, true);
+goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag,
+                                                                     opt_class,
+                                                                     opt_el) {
+  return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag,
+                                                opt_class, opt_el);
 };
 
 
 /**
- * Set the width of an element.  Sets the element's style property.
- * @param {Element} element Element to set the width of.
- * @param {string|number} width The width value to set.  If a number, 'px'
- *     will be appended, otherwise the value will be applied directly.
+ * Returns an array of all the elements with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {Element|Document=} opt_el Optional element to look in.
+ * @return { {length: number} } The items found with the class name provided.
  */
-goog.style.setWidth = function(element, width) {
-  element.style.width = goog.style.getPixelStyleValue_(width, true);
+goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
+  var doc = opt_el || this.document_;
+  return goog.dom.getElementsByClass(className, doc);
 };
 
 
 /**
- * Gets the height and width of an element, even if its display is none.
- *
- * Specifically, this returns the height and width of the border box,
- * irrespective of the box model in effect.
- *
- * Note that this function does not take CSS transforms into account. Please see
- * {@code goog.style.getTransformedSize}.
- * @param {Element} element Element to get size of.
- * @return {!goog.math.Size} Object with width/height properties.
+ * Returns the first element we find matching the provided class name.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {(Element|Document)=} opt_el Optional element to look in.
+ * @return {Element} The first item found with the class name provided.
  */
-goog.style.getSize = function(element) {
-  return goog.style.evaluateWithTemporaryDisplay_(
-      goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
+goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
+  var doc = opt_el || this.document_;
+  return goog.dom.getElementByClass(className, doc);
 };
 
 
 /**
- * Call {@code fn} on {@code element} such that {@code element}'s dimensions are
- * accurate when it's passed to {@code fn}.
- * @param {function(!Element): T} fn Function to call with {@code element} as
- *     an argument after temporarily changing {@code element}'s display such
- *     that its dimensions are accurate.
- * @param {!Element} element Element (which may have display none) to use as
- *     argument to {@code fn}.
- * @return {T} Value returned by calling {@code fn} with {@code element}.
- * @template T
- * @private
+ * Ensures an element with the given className exists, and then returns the
+ * first element with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {(!Element|!Document)=} opt_root Optional element or document to look
+ *     in.
+ * @return {!Element} The first item found with the class name provided.
+ * @throws {goog.asserts.AssertionError} Thrown if no element is found.
  */
-goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
-  if (goog.style.getStyle_(element, 'display') != 'none') {
-    return fn(element);
-  }
-
-  var style = element.style;
-  var originalDisplay = style.display;
-  var originalVisibility = style.visibility;
-  var originalPosition = style.position;
+goog.dom.DomHelper.prototype.getRequiredElementByClass = function(className,
+                                                                  opt_root) {
+  var root = opt_root || this.document_;
+  return goog.dom.getRequiredElementByClass(className, root);
+};
 
-  style.visibility = 'hidden';
-  style.position = 'absolute';
-  style.display = 'inline';
 
-  var retVal = fn(element);
+/**
+ * Alias for {@code getElementsByTagNameAndClass}.
+ * @deprecated Use DomHelper getElementsByTagNameAndClass.
+ * @see goog.dom.query
+ *
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {Element=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ *     property and numerical indices are guaranteed to exist).
+ */
+goog.dom.DomHelper.prototype.$$ =
+    goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
 
-  style.display = originalDisplay;
-  style.position = originalPosition;
-  style.visibility = originalVisibility;
 
-  return retVal;
-};
+/**
+ * Sets a number of properties on a node.
+ * @param {Element} element DOM node to set properties on.
+ * @param {Object} properties Hash of property:value pairs.
+ */
+goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;
 
 
 /**
- * Gets the height and width of an element when the display is not none.
- * @param {Element} element Element to get size of.
- * @return {!goog.math.Size} Object with width/height properties.
- * @private
+ * Gets the dimensions of the viewport.
+ * @param {Window=} opt_window Optional window element to test. Defaults to
+ *     the window of the Dom Helper.
+ * @return {!goog.math.Size} Object with values 'width' and 'height'.
  */
-goog.style.getSizeWithDisplay_ = function(element) {
-  var offsetWidth = element.offsetWidth;
-  var offsetHeight = element.offsetHeight;
-  var webkitOffsetsZero =
-      goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
-  if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
-      element.getBoundingClientRect) {
-    // Fall back to calling getBoundingClientRect when offsetWidth or
-    // offsetHeight are not defined, or when they are zero in WebKit browsers.
-    // This makes sure that we return for the correct size for SVG elements, but
-    // will still return 0 on Webkit prior to 534.8, see
-    // http://trac.webkit.org/changeset/67252.
-    var clientRect = goog.style.getBoundingClientRect_(element);
-    return new goog.math.Size(clientRect.right - clientRect.left,
-        clientRect.bottom - clientRect.top);
-  }
-  return new goog.math.Size(offsetWidth, offsetHeight);
+goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
+  // TODO(arv): This should not take an argument. That breaks the rule of a
+  // a DomHelper representing a single frame/window/document.
+  return goog.dom.getViewportSize(opt_window || this.getWindow());
 };
 
 
 /**
- * Gets the height and width of an element, post transform, even if its display
- * is none.
+ * Calculates the height of the document.
  *
- * This is like {@code goog.style.getSize}, except:
- * <ol>
- * <li>Takes webkitTransforms such as rotate and scale into account.
- * <li>Will return null if {@code element} doesn't respond to
- *     {@code getBoundingClientRect}.
- * <li>Currently doesn't make sense on non-WebKit browsers which don't support
- *    webkitTransforms.
- * </ol>
- * @param {!Element} element Element to get size of.
- * @return {goog.math.Size} Object with width/height properties.
+ * @return {number} The height of the document.
  */
-goog.style.getTransformedSize = function(element) {
-  if (!element.getBoundingClientRect) {
-    return null;
-  }
-
-  var clientRect = goog.style.evaluateWithTemporaryDisplay_(
-      goog.style.getBoundingClientRect_, element);
-  return new goog.math.Size(clientRect.right - clientRect.left,
-      clientRect.bottom - clientRect.top);
+goog.dom.DomHelper.prototype.getDocumentHeight = function() {
+  return goog.dom.getDocumentHeight_(this.getWindow());
 };
 
 
 /**
- * Returns a bounding rectangle for a given element in page space.
- * @param {Element} element Element to get bounds of. Must not be display none.
- * @return {!goog.math.Rect} Bounding rectangle for the element.
+ * Typedef for use with goog.dom.createDom and goog.dom.append.
+ * @typedef {Object|string|Array|NodeList}
  */
-goog.style.getBounds = function(element) {
-  var o = goog.style.getPageOffset(element);
-  var s = goog.style.getSize(element);
-  return new goog.math.Rect(o.x, o.y, s.width, s.height);
-};
+goog.dom.Appendable;
 
 
 /**
- * Converts a CSS selector in the form style-property to styleProperty.
- * @param {*} selector CSS Selector.
- * @return {string} Camel case selector.
- * @deprecated Use goog.string.toCamelCase instead.
+ * Returns a dom node with a set of attributes.  This function accepts varargs
+ * for subsequent nodes to be added.  Subsequent nodes will be added to the
+ * first node as childNodes.
+ *
+ * So:
+ * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
+ * would return a div with two child paragraphs
+ *
+ * An easy way to move all child nodes of an existing element to a new parent
+ * element is:
+ * <code>createDom('div', null, oldElement.childNodes);</code>
+ * which will remove all child nodes from the old element and add them as
+ * child nodes of the new DIV.
+ *
+ * @param {string} tagName Tag to create.
+ * @param {Object|string=} opt_attributes If object, then a map of name-value
+ *     pairs for attributes. If a string, then this is the className of the new
+ *     element.
+ * @param {...goog.dom.Appendable} var_args Further DOM nodes or
+ *     strings for text nodes. If one of the var_args is an array or
+ *     NodeList, its elements will be added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
  */
-goog.style.toCamelCase = function(selector) {
-  return goog.string.toCamelCase(String(selector));
+goog.dom.DomHelper.prototype.createDom = function(tagName,
+                                                  opt_attributes,
+                                                  var_args) {
+  return goog.dom.createDom_(this.document_, arguments);
 };
 
 
 /**
- * Converts a CSS selector in the form styleProperty to style-property.
- * @param {string} selector Camel case selector.
- * @return {string} Selector cased.
- * @deprecated Use goog.string.toSelectorCase instead.
+ * Alias for {@code createDom}.
+ * @param {string} tagName Tag to create.
+ * @param {(Object|string)=} opt_attributes If object, then a map of name-value
+ *     pairs for attributes. If a string, then this is the className of the new
+ *     element.
+ * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for
+ *     text nodes.  If one of the var_args is an array, its children will be
+ *     added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
+ * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead.
  */
-goog.style.toSelectorCase = function(selector) {
-  return goog.string.toSelectorCase(selector);
-};
+goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
 
 
 /**
- * Gets the opacity of a node (x-browser). This gets the inline style opacity
- * of the node, and does not take into account the cascaded or the computed
- * style for this node.
- * @param {Element} el Element whose opacity has to be found.
- * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
- *     if the opacity is not set.
+ * Creates a new element.
+ * @param {string} name Tag name.
+ * @return {!Element} The new element.
  */
-goog.style.getOpacity = function(el) {
-  var style = el.style;
-  var result = '';
-  if ('opacity' in style) {
-    result = style.opacity;
-  } else if ('MozOpacity' in style) {
-    result = style.MozOpacity;
-  } else if ('filter' in style) {
-    var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
-    if (match) {
-      result = String(match[1] / 100);
-    }
-  }
-  return result == '' ? result : Number(result);
+goog.dom.DomHelper.prototype.createElement = function(name) {
+  return this.document_.createElement(name);
 };
 
 
 /**
- * Sets the opacity of a node (x-browser).
- * @param {Element} el Elements whose opacity has to be set.
- * @param {number|string} alpha Opacity between 0 and 1 or an empty string
- *     {@code ''} to clear the opacity.
+ * Creates a new text node.
+ * @param {number|string} content Content.
+ * @return {!Text} The new text node.
  */
-goog.style.setOpacity = function(el, alpha) {
-  var style = el.style;
-  if ('opacity' in style) {
-    style.opacity = alpha;
-  } else if ('MozOpacity' in style) {
-    style.MozOpacity = alpha;
-  } else if ('filter' in style) {
-    // TODO(arv): Overwriting the filter might have undesired side effects.
-    if (alpha === '') {
-      style.filter = '';
-    } else {
-      style.filter = 'alpha(opacity=' + alpha * 100 + ')';
-    }
-  }
+goog.dom.DomHelper.prototype.createTextNode = function(content) {
+  return this.document_.createTextNode(String(content));
 };
 
 
 /**
- * Sets the background of an element to a transparent image in a browser-
- * independent manner.
- *
- * This function does not support repeating backgrounds or alternate background
- * positions to match the behavior of Internet Explorer. It also does not
- * support sizingMethods other than crop since they cannot be replicated in
- * browsers other than Internet Explorer.
- *
- * @param {Element} el The element to set background on.
- * @param {string} src The image source URL.
+ * Create a table.
+ * @param {number} rows The number of rows in the table.  Must be >= 1.
+ * @param {number} columns The number of columns in the table.  Must be >= 1.
+ * @param {boolean=} opt_fillWithNbsp If true, fills table entries with
+ *     {@code goog.string.Unicode.NBSP} characters.
+ * @return {!HTMLElement} The created table.
  */
-goog.style.setTransparentBackgroundImage = function(el, src) {
-  var style = el.style;
-  // It is safe to use the style.filter in IE only. In Safari 'filter' is in
-  // style object but access to style.filter causes it to throw an exception.
-  // Note: IE8 supports images with an alpha channel.
-  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
-    // See TODO in setOpacity.
-    style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
-        'src="' + src + '", sizingMethod="crop")';
-  } else {
-    // Set style properties individually instead of using background shorthand
-    // to prevent overwriting a pre-existing background color.
-    style.backgroundImage = 'url(' + src + ')';
-    style.backgroundPosition = 'top left';
-    style.backgroundRepeat = 'no-repeat';
-  }
+goog.dom.DomHelper.prototype.createTable = function(rows, columns,
+    opt_fillWithNbsp) {
+  return goog.dom.createTable_(this.document_, rows, columns,
+      !!opt_fillWithNbsp);
 };
 
 
 /**
- * Clears the background image of an element in a browser independent manner.
- * @param {Element} el The element to clear background image for.
+ * Converts an HTML into a node or a document fragment. A single Node is used if
+ * {@code html} only generates a single node. If {@code html} generates multiple
+ * nodes then these are put inside a {@code DocumentFragment}.
+ * @param {!goog.html.SafeHtml} html The HTML markup to convert.
+ * @return {!Node} The resulting node.
  */
-goog.style.clearTransparentBackgroundImage = function(el) {
-  var style = el.style;
-  if ('filter' in style) {
-    // See TODO in setOpacity.
-    style.filter = '';
-  } else {
-    // Set style properties individually instead of using background shorthand
-    // to prevent overwriting a pre-existing background color.
-    style.backgroundImage = 'none';
-  }
+goog.dom.DomHelper.prototype.safeHtmlToNode = function(html) {
+  return goog.dom.safeHtmlToNode_(this.document_, html);
 };
 
 
 /**
- * Shows or hides an element from the page. Hiding the element is done by
- * setting the display property to "none", removing the element from the
- * rendering hierarchy so it takes up no space. To show the element, the default
- * inherited display property is restored (defined either in stylesheets or by
- * the browser's default style rules.)
- *
- * Caveat 1: if the inherited display property for the element is set to "none"
- * by the stylesheets, that is the property that will be restored by a call to
- * showElement(), effectively toggling the display between "none" and "none".
+ * Converts an HTML string into a node or a document fragment.  A single Node
+ * is used if the {@code htmlString} only generates a single node.  If the
+ * {@code htmlString} generates multiple nodes then these are put inside a
+ * {@code DocumentFragment}.
  *
- * Caveat 2: if the element display style is set inline (by setting either
- * element.style.display or a style attribute in the HTML), a call to
- * showElement will clear that setting and defer to the inherited style in the
- * stylesheet.
- * @param {Element} el Element to show or hide.
- * @param {*} display True to render the element in its default style,
- *     false to disable rendering the element.
- * @deprecated Use goog.style.setElementShown instead.
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting node.
  */
-goog.style.showElement = function(el, display) {
-  goog.style.setElementShown(el, display);
+goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) {
+  return goog.dom.htmlToDocumentFragment_(this.document_, htmlString);
 };
 
 
 /**
- * Shows or hides an element from the page. Hiding the element is done by
- * setting the display property to "none", removing the element from the
- * rendering hierarchy so it takes up no space. To show the element, the default
- * inherited display property is restored (defined either in stylesheets or by
- * the browser's default style rules).
- *
- * Caveat 1: if the inherited display property for the element is set to "none"
- * by the stylesheets, that is the property that will be restored by a call to
- * setElementShown(), effectively toggling the display between "none" and
- * "none".
- *
- * Caveat 2: if the element display style is set inline (by setting either
- * element.style.display or a style attribute in the HTML), a call to
- * setElementShown will clear that setting and defer to the inherited style in
- * the stylesheet.
- * @param {Element} el Element to show or hide.
- * @param {*} isShown True to render the element in its default style,
- *     false to disable rendering the element.
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @return {boolean} True if in CSS1-compatible mode.
  */
-goog.style.setElementShown = function(el, isShown) {
-  el.style.display = isShown ? '' : 'none';
+goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
+  return goog.dom.isCss1CompatMode_(this.document_);
 };
 
 
 /**
- * Test whether the given element has been shown or hidden via a call to
- * {@link #setElementShown}.
- *
- * Note this is strictly a companion method for a call
- * to {@link #setElementShown} and the same caveats apply; in particular, this
- * method does not guarantee that the return value will be consistent with
- * whether or not the element is actually visible.
- *
- * @param {Element} el The element to test.
- * @return {boolean} Whether the element has been shown.
- * @see #setElementShown
+ * Gets the window object associated with the document.
+ * @return {!Window} The window associated with the given document.
  */
-goog.style.isElementShown = function(el) {
-  return el.style.display != 'none';
+goog.dom.DomHelper.prototype.getWindow = function() {
+  return goog.dom.getWindow_(this.document_);
 };
 
 
 /**
- * Installs the styles string into the window that contains opt_element.  If
- * opt_element is null, the main window is used.
- * @param {string} stylesString The style string to install.
- * @param {Node=} opt_node Node whose parent document should have the
- *     styles installed.
- * @return {Element|StyleSheet} The style element created.
+ * Gets the document scroll element.
+ * @return {!Element} Scrolling element.
  */
-goog.style.installStyles = function(stylesString, opt_node) {
-  var dh = goog.dom.getDomHelper(opt_node);
-  var styleSheet = null;
-
-  // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
-  // undefined as of IE 11.
-  var doc = dh.getDocument();
-  if (goog.userAgent.IE && doc.createStyleSheet) {
-    styleSheet = doc.createStyleSheet();
-    goog.style.setStyles(styleSheet, stylesString);
-  } else {
-    var head = dh.getElementsByTagNameAndClass('head')[0];
-
-    // In opera documents are not guaranteed to have a head element, thus we
-    // have to make sure one exists before using it.
-    if (!head) {
-      var body = dh.getElementsByTagNameAndClass('body')[0];
-      head = dh.createDom('head');
-      body.parentNode.insertBefore(head, body);
-    }
-    styleSheet = dh.createDom('style');
-    // NOTE(user): Setting styles after the style element has been appended
-    // to the head results in a nasty Webkit bug in certain scenarios. Please
-    // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
-    // details.
-    goog.style.setStyles(styleSheet, stylesString);
-    dh.appendChild(head, styleSheet);
-  }
-  return styleSheet;
+goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
+  return goog.dom.getDocumentScrollElement_(this.document_);
 };
 
 
 /**
- * Removes the styles added by {@link #installStyles}.
- * @param {Element|StyleSheet} styleSheet The value returned by
- *     {@link #installStyles}.
+ * Gets the document scroll distance as a coordinate object.
+ * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'.
  */
-goog.style.uninstallStyles = function(styleSheet) {
-  var node = styleSheet.ownerNode || styleSheet.owningElement ||
-      /** @type {Element} */ (styleSheet);
-  goog.dom.removeNode(node);
+goog.dom.DomHelper.prototype.getDocumentScroll = function() {
+  return goog.dom.getDocumentScroll_(this.document_);
 };
 
 
 /**
- * Sets the content of a style element.  The style element can be any valid
- * style element.  This element will have its content completely replaced by
- * the new stylesString.
- * @param {Element|StyleSheet} element A stylesheet element as returned by
- *     installStyles.
- * @param {string} stylesString The new content of the stylesheet.
+ * Determines the active element in the given document.
+ * @param {Document=} opt_doc The document to look in.
+ * @return {Element} The active element.
  */
-goog.style.setStyles = function(element, stylesString) {
-  if (goog.userAgent.IE && goog.isDef(element.cssText)) {
-    // Adding the selectors individually caused the browser to hang if the
-    // selector was invalid or there were CSS comments.  Setting the cssText of
-    // the style node works fine and ignores CSS that IE doesn't understand.
-    // However IE >= 11 doesn't support cssText any more, so we make sure that
-    // cssText is a defined property and otherwise fall back to innerHTML.
-    element.cssText = stylesString;
-  } else {
-    element.innerHTML = stylesString;
-  }
+goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) {
+  return goog.dom.getActiveElement(opt_doc || this.document_);
 };
 
 
 /**
- * Sets 'white-space: pre-wrap' for a node (x-browser).
- *
- * There are as many ways of specifying pre-wrap as there are browsers.
- *
- * CSS3/IE8: white-space: pre-wrap;
- * Mozilla:  white-space: -moz-pre-wrap;
- * Opera:    white-space: -o-pre-wrap;
- * IE6/7:    white-space: pre; word-wrap: break-word;
- *
- * @param {Element} el Element to enable pre-wrap for.
+ * Appends a child to a node.
+ * @param {Node} parent Parent.
+ * @param {Node} child Child.
  */
-goog.style.setPreWrap = function(el) {
-  var style = el.style;
-  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
-    style.whiteSpace = 'pre';
-    style.wordWrap = 'break-word';
-  } else if (goog.userAgent.GECKO) {
-    style.whiteSpace = '-moz-pre-wrap';
-  } else {
-    style.whiteSpace = 'pre-wrap';
-  }
-};
+goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
 
 
 /**
- * Sets 'display: inline-block' for an element (cross-browser).
- * @param {Element} el Element to which the inline-block display style is to be
- *    applied.
- * @see ../demos/inline_block_quirks.html
- * @see ../demos/inline_block_standards.html
+ * Appends a node with text or other nodes.
+ * @param {!Node} parent The node to append nodes to.
+ * @param {...goog.dom.Appendable} var_args The things to append to the node.
+ *     If this is a Node it is appended as is.
+ *     If this is a string then a text node is appended.
+ *     If this is an array like object then fields 0 to length - 1 are appended.
  */
-goog.style.setInlineBlock = function(el) {
-  var style = el.style;
-  // Without position:relative, weirdness ensues.  Just accept it and move on.
-  style.position = 'relative';
-
-  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
-    // IE8 supports inline-block so fall through to the else
-    // Zoom:1 forces hasLayout, display:inline gives inline behavior.
-    style.zoom = '1';
-    style.display = 'inline';
-  } else if (goog.userAgent.GECKO) {
-    // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box
-    // is close enough.
-    style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' :
-        '-moz-inline-box';
-  } else {
-    // Opera, Webkit, and Safari seem to do OK with the standard inline-block
-    // style.
-    style.display = 'inline-block';
-  }
-};
+goog.dom.DomHelper.prototype.append = goog.dom.append;
 
 
 /**
- * Returns true if the element is using right to left (rtl) direction.
- * @param {Element} el  The element to test.
- * @return {boolean} True for right to left, false for left to right.
+ * Determines if the given node can contain children, intended to be used for
+ * HTML generation.
+ *
+ * @param {Node} node The node to check.
+ * @return {boolean} Whether the node can contain children.
  */
-goog.style.isRightToLeft = function(el) {
-  return 'rtl' == goog.style.getStyle_(el, 'direction');
-};
+goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren;
 
 
 /**
- * The CSS style property corresponding to an element being
- * unselectable on the current browser platform (null if none).
- * Opera and IE instead use a DOM attribute 'unselectable'.
- * @type {?string}
- * @private
+ * Removes all the child nodes on a DOM node.
+ * @param {Node} node Node to remove children from.
  */
-goog.style.unselectableStyle_ =
-    goog.userAgent.GECKO ? 'MozUserSelect' :
-    goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
-    null;
+goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;
 
 
 /**
- * Returns true if the element is set to be unselectable, false otherwise.
- * Note that on some platforms (e.g. Mozilla), even if an element isn't set
- * to be unselectable, it will behave as such if any of its ancestors is
- * unselectable.
- * @param {Element} el  Element to check.
- * @return {boolean}  Whether the element is set to be unselectable.
+ * Inserts a new node before an existing reference node (i.e., as the previous
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert before.
  */
-goog.style.isUnselectable = function(el) {
-  if (goog.style.unselectableStyle_) {
-    return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
-  } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
-    return el.getAttribute('unselectable') == 'on';
-  }
-  return false;
-};
+goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;
 
 
 /**
- * Makes the element and its descendants selectable or unselectable.  Note
- * that on some platforms (e.g. Mozilla), even if an element isn't set to
- * be unselectable, it will behave as such if any of its ancestors is
- * unselectable.
- * @param {Element} el  The element to alter.
- * @param {boolean} unselectable  Whether the element and its descendants
- *     should be made unselectable.
- * @param {boolean=} opt_noRecurse  Whether to only alter the element's own
- *     selectable state, and leave its descendants alone; defaults to false.
+ * Inserts a new node after an existing reference node (i.e., as the next
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert after.
  */
-goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
-  // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
-  var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
-  var name = goog.style.unselectableStyle_;
-  if (name) {
-    // Add/remove the appropriate CSS style to/from the element and its
-    // descendants.
-    var value = unselectable ? 'none' : '';
-    el.style[name] = value;
-    if (descendants) {
-      for (var i = 0, descendant; descendant = descendants[i]; i++) {
-        descendant.style[name] = value;
-      }
-    }
-  } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
-    // Toggle the 'unselectable' attribute on the element and its descendants.
-    var value = unselectable ? 'on' : '';
-    el.setAttribute('unselectable', value);
-    if (descendants) {
-      for (var i = 0, descendant; descendant = descendants[i]; i++) {
-        descendant.setAttribute('unselectable', value);
-      }
-    }
-  }
-};
+goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
 
 
 /**
- * Gets the border box size for an element.
- * @param {Element} element  The element to get the size for.
- * @return {!goog.math.Size} The border box size.
+ * Insert a child at a given index. If index is larger than the number of child
+ * nodes that the parent currently has, the node is inserted as the last child
+ * node.
+ * @param {Element} parent The element into which to insert the child.
+ * @param {Node} child The element to insert.
+ * @param {number} index The index at which to insert the new child node. Must
+ *     not be negative.
  */
-goog.style.getBorderBoxSize = function(element) {
-  return new goog.math.Size(element.offsetWidth, element.offsetHeight);
-};
+goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt;
 
 
 /**
- * Sets the border box size of an element. This is potentially expensive in IE
- * if the document is CSS1Compat mode
- * @param {Element} element  The element to set the size on.
- * @param {goog.math.Size} size  The new size.
+ * Removes a node from its parent.
+ * @param {Node} node The node to remove.
+ * @return {Node} The node removed if removed; else, null.
  */
-goog.style.setBorderBoxSize = function(element, size) {
-  var doc = goog.dom.getOwnerDocument(element);
-  var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
-
-  if (goog.userAgent.IE &&
-      (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
-    var style = element.style;
-    if (isCss1CompatMode) {
-      var paddingBox = goog.style.getPaddingBox(element);
-      var borderBox = goog.style.getBorderBox(element);
-      style.pixelWidth = size.width - borderBox.left - paddingBox.left -
-                         paddingBox.right - borderBox.right;
-      style.pixelHeight = size.height - borderBox.top - paddingBox.top -
-                          paddingBox.bottom - borderBox.bottom;
-    } else {
-      style.pixelWidth = size.width;
-      style.pixelHeight = size.height;
-    }
-  } else {
-    goog.style.setBoxSizingSize_(element, size, 'border-box');
-  }
-};
+goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
 
 
 /**
- * Gets the content box size for an element.  This is potentially expensive in
- * all browsers.
- * @param {Element} element  The element to get the size for.
- * @return {!goog.math.Size} The content box size.
+ * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
+ * parent.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} oldNode Node to replace.
  */
-goog.style.getContentBoxSize = function(element) {
-  var doc = goog.dom.getOwnerDocument(element);
-  var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
-  if (ieCurrentStyle &&
-      goog.dom.getDomHelper(doc).isCss1CompatMode() &&
-      ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
-      !ieCurrentStyle.boxSizing) {
-    // If IE in CSS1Compat mode than just use the width and height.
-    // If we have a boxSizing then fall back on measuring the borders etc.
-    var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width,
-                                            'width', 'pixelWidth');
-    var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height,
-                                             'height', 'pixelHeight');
-    return new goog.math.Size(width, height);
-  } else {
-    var borderBoxSize = goog.style.getBorderBoxSize(element);
-    var paddingBox = goog.style.getPaddingBox(element);
-    var borderBox = goog.style.getBorderBox(element);
-    return new goog.math.Size(borderBoxSize.width -
-                              borderBox.left - paddingBox.left -
-                              paddingBox.right - borderBox.right,
-                              borderBoxSize.height -
-                              borderBox.top - paddingBox.top -
-                              paddingBox.bottom - borderBox.bottom);
-  }
-};
+goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
 
 
 /**
- * Sets the content box size of an element. This is potentially expensive in IE
- * if the document is BackCompat mode.
- * @param {Element} element  The element to set the size on.
- * @param {goog.math.Size} size  The new size.
+ * Flattens an element. That is, removes it and replace it with its children.
+ * @param {Element} element The element to flatten.
+ * @return {Element|undefined} The original element, detached from the document
+ *     tree, sans children, or undefined if the element was already not in the
+ *     document.
  */
-goog.style.setContentBoxSize = function(element, size) {
-  var doc = goog.dom.getOwnerDocument(element);
-  var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
-  if (goog.userAgent.IE &&
-      (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
-    var style = element.style;
-    if (isCss1CompatMode) {
-      style.pixelWidth = size.width;
-      style.pixelHeight = size.height;
-    } else {
-      var paddingBox = goog.style.getPaddingBox(element);
-      var borderBox = goog.style.getBorderBox(element);
-      style.pixelWidth = size.width + borderBox.left + paddingBox.left +
-                         paddingBox.right + borderBox.right;
-      style.pixelHeight = size.height + borderBox.top + paddingBox.top +
-                          paddingBox.bottom + borderBox.bottom;
-    }
-  } else {
-    goog.style.setBoxSizingSize_(element, size, 'content-box');
-  }
-};
+goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
 
 
 /**
- * Helper function that sets the box sizing as well as the width and height
- * @param {Element} element  The element to set the size on.
- * @param {goog.math.Size} size  The new size to set.
- * @param {string} boxSizing  The box-sizing value.
- * @private
+ * Returns an array containing just the element children of the given element.
+ * @param {Element} element The element whose element children we want.
+ * @return {!(Array|NodeList)} An array or array-like list of just the element
+ *     children of the given element.
  */
-goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
-  var style = element.style;
-  if (goog.userAgent.GECKO) {
-    style.MozBoxSizing = boxSizing;
-  } else if (goog.userAgent.WEBKIT) {
-    style.WebkitBoxSizing = boxSizing;
-  } else {
-    // Includes IE8 and Opera 9.50+
-    style.boxSizing = boxSizing;
-  }
-
-  // Setting this to a negative value will throw an exception on IE
-  // (and doesn't do anything different than setting it to 0).
-  style.width = Math.max(size.width, 0) + 'px';
-  style.height = Math.max(size.height, 0) + 'px';
-};
+goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren;
 
 
 /**
- * IE specific function that converts a non pixel unit to pixels.
- * @param {Element} element  The element to convert the value for.
- * @param {string} value  The current value as a string. The value must not be
- *     ''.
- * @param {string} name  The CSS property name to use for the converstion. This
- *     should be 'left', 'top', 'width' or 'height'.
- * @param {string} pixelName  The CSS pixel property name to use to get the
- *     value in pixels.
- * @return {number} The value in pixels.
- * @private
+ * Returns the first child node that is an element.
+ * @param {Node} node The node to get the first child element of.
+ * @return {Element} The first child node of {@code node} that is an element.
  */
-goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
-  // Try if we already have a pixel value. IE does not do half pixels so we
-  // only check if it matches a number followed by 'px'.
-  if (/^\d+px?$/.test(value)) {
-    return parseInt(value, 10);
-  } else {
-    var oldStyleValue = element.style[name];
-    var oldRuntimeValue = element.runtimeStyle[name];
-    // set runtime style to prevent changes
-    element.runtimeStyle[name] = element.currentStyle[name];
-    element.style[name] = value;
-    var pixelValue = element.style[pixelName];
-    // restore
-    element.style[name] = oldStyleValue;
-    element.runtimeStyle[name] = oldRuntimeValue;
-    return pixelValue;
-  }
-};
+goog.dom.DomHelper.prototype.getFirstElementChild =
+    goog.dom.getFirstElementChild;
 
 
 /**
- * Helper function for getting the pixel padding or margin for IE.
- * @param {Element} element  The element to get the padding for.
- * @param {string} propName  The property name.
- * @return {number} The pixel padding.
- * @private
+ * Returns the last child node that is an element.
+ * @param {Node} node The node to get the last child element of.
+ * @return {Element} The last child node of {@code node} that is an element.
  */
-goog.style.getIePixelDistance_ = function(element, propName) {
-  var value = goog.style.getCascadedStyle(element, propName);
-  return value ?
-      goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
-};
+goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild;
 
 
 /**
- * Gets the computed paddings or margins (on all sides) in pixels.
- * @param {Element} element  The element to get the padding for.
- * @param {string} stylePrefix  Pass 'padding' to retrieve the padding box,
- *     or 'margin' to retrieve the margin box.
- * @return {!goog.math.Box} The computed paddings or margins.
- * @private
+ * Returns the first next sibling that is an element.
+ * @param {Node} node The node to get the next sibling element of.
+ * @return {Element} The next sibling of {@code node} that is an element.
  */
-goog.style.getBox_ = function(element, stylePrefix) {
-  if (goog.userAgent.IE) {
-    var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
-    var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
-    var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
-    var bottom = goog.style.getIePixelDistance_(
-        element, stylePrefix + 'Bottom');
-    return new goog.math.Box(top, right, bottom, left);
-  } else {
-    // On non-IE browsers, getComputedStyle is always non-null.
-    var left = /** @type {string} */ (
-        goog.style.getComputedStyle(element, stylePrefix + 'Left'));
-    var right = /** @type {string} */ (
-        goog.style.getComputedStyle(element, stylePrefix + 'Right'));
-    var top = /** @type {string} */ (
-        goog.style.getComputedStyle(element, stylePrefix + 'Top'));
-    var bottom = /** @type {string} */ (
-        goog.style.getComputedStyle(element, stylePrefix + 'Bottom'));
+goog.dom.DomHelper.prototype.getNextElementSibling =
+    goog.dom.getNextElementSibling;
 
-    // NOTE(arv): Gecko can return floating point numbers for the computed
-    // style values.
-    return new goog.math.Box(parseFloat(top),
-                             parseFloat(right),
-                             parseFloat(bottom),
-                             parseFloat(left));
-  }
-};
+
+/**
+ * Returns the first previous sibling that is an element.
+ * @param {Node} node The node to get the previous sibling element of.
+ * @return {Element} The first previous sibling of {@code node} that is
+ *     an element.
+ */
+goog.dom.DomHelper.prototype.getPreviousElementSibling =
+    goog.dom.getPreviousElementSibling;
 
 
 /**
- * Gets the computed paddings (on all sides) in pixels.
- * @param {Element} element  The element to get the padding for.
- * @return {!goog.math.Box} The computed paddings.
+ * Returns the next node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The next node in the DOM tree, or null if this was the last
+ *     node.
  */
-goog.style.getPaddingBox = function(element) {
-  return goog.style.getBox_(element, 'padding');
-};
+goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;
 
 
 /**
- * Gets the computed margins (on all sides) in pixels.
- * @param {Element} element  The element to get the margins for.
- * @return {!goog.math.Box} The computed margins.
+ * Returns the previous node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The previous node in the DOM tree, or null if this was the
+ *     first node.
  */
-goog.style.getMarginBox = function(element) {
-  return goog.style.getBox_(element, 'margin');
-};
+goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;
 
 
 /**
- * A map used to map the border width keywords to a pixel width.
- * @type {Object}
- * @private
+ * Whether the object looks like a DOM node.
+ * @param {?} obj The object being tested for node likeness.
+ * @return {boolean} Whether the object looks like a DOM node.
  */
-goog.style.ieBorderWidthKeywords_ = {
-  'thin': 2,
-  'medium': 4,
-  'thick': 6
-};
+goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
 
 
 /**
- * Helper function for IE to get the pixel border.
- * @param {Element} element  The element to get the pixel border for.
- * @param {string} prop  The part of the property name.
- * @return {number} The value in pixels.
- * @private
+ * Whether the object looks like an Element.
+ * @param {?} obj The object being tested for Element likeness.
+ * @return {boolean} Whether the object looks like an Element.
  */
-goog.style.getIePixelBorder_ = function(element, prop) {
-  if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
-    return 0;
-  }
-  var width = goog.style.getCascadedStyle(element, prop + 'Width');
-  if (width in goog.style.ieBorderWidthKeywords_) {
-    return goog.style.ieBorderWidthKeywords_[width];
-  }
-  return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
-};
+goog.dom.DomHelper.prototype.isElement = goog.dom.isElement;
 
 
 /**
- * Gets the computed border widths (on all sides) in pixels
- * @param {Element} element  The element to get the border widths for.
- * @return {!goog.math.Box} The computed border widths.
+ * Returns true if the specified value is a Window object. This includes the
+ * global window for HTML pages, and iframe windows.
+ * @param {?} obj Variable to test.
+ * @return {boolean} Whether the variable is a window.
  */
-goog.style.getBorderBox = function(element) {
-  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
-    var left = goog.style.getIePixelBorder_(element, 'borderLeft');
-    var right = goog.style.getIePixelBorder_(element, 'borderRight');
-    var top = goog.style.getIePixelBorder_(element, 'borderTop');
-    var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
-    return new goog.math.Box(top, right, bottom, left);
-  } else {
-    // On non-IE browsers, getComputedStyle is always non-null.
-    var left = /** @type {string} */ (
-        goog.style.getComputedStyle(element, 'borderLeftWidth'));
-    var right = /** @type {string} */ (
-        goog.style.getComputedStyle(element, 'borderRightWidth'));
-    var top = /** @type {string} */ (
-        goog.style.getComputedStyle(element, 'borderTopWidth'));
-    var bottom = /** @type {string} */ (
-        goog.style.getComputedStyle(element, 'borderBottomWidth'));
+goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow;
 
-    return new goog.math.Box(parseFloat(top),
-                             parseFloat(right),
-                             parseFloat(bottom),
-                             parseFloat(left));
-  }
-};
+
+/**
+ * Returns an element's parent, if it's an Element.
+ * @param {Element} element The DOM element.
+ * @return {Element} The parent, or null if not an Element.
+ */
+goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement;
 
 
 /**
- * Returns the font face applied to a given node. Opera and IE should return
- * the font actually displayed. Firefox returns the author's most-preferred
- * font (whether the browser is capable of displaying it or not.)
- * @param {Element} el  The element whose font family is returned.
- * @return {string} The font family applied to el.
+ * Whether a node contains another node.
+ * @param {Node} parent The node that should contain the other node.
+ * @param {Node} descendant The node to test presence of.
+ * @return {boolean} Whether the parent node contains the descendent node.
  */
-goog.style.getFontFamily = function(el) {
-  var doc = goog.dom.getOwnerDocument(el);
-  var font = '';
-  // The moveToElementText method from the TextRange only works if the element
-  // is attached to the owner document.
-  if (doc.body.createTextRange && goog.dom.contains(doc, el)) {
-    var range = doc.body.createTextRange();
-    range.moveToElementText(el);
-    /** @preserveTry */
-    try {
-      font = range.queryCommandValue('FontName');
-    } catch (e) {
-      // This is a workaround for a awkward exception.
-      // On some IE, there is an exception coming from it.
-      // The error description from this exception is:
-      // This window has already been registered as a drop target
-      // This is bogus description, likely due to a bug in ie.
-      font = '';
-    }
-  }
-  if (!font) {
-    // Note if for some reason IE can't derive FontName with a TextRange, we
-    // fallback to using currentStyle
-    font = goog.style.getStyle_(el, 'fontFamily');
-  }
+goog.dom.DomHelper.prototype.contains = goog.dom.contains;
 
-  // Firefox returns the applied font-family string (author's list of
-  // preferred fonts.) We want to return the most-preferred font, in lieu of
-  // the *actually* applied font.
-  var fontsArray = font.split(',');
-  if (fontsArray.length > 1) font = fontsArray[0];
 
-  // Sanitize for x-browser consistency:
-  // Strip quotes because browsers aren't consistent with how they're
-  // applied; Opera always encloses, Firefox sometimes, and IE never.
-  return goog.string.stripQuotes(font, '"\'');
-};
+/**
+ * Compares the document order of two nodes, returning 0 if they are the same
+ * node, a negative number if node1 is before node2, and a positive number if
+ * node2 is before node1.  Note that we compare the order the tags appear in the
+ * document so in the tree <b><i>text</i></b> the B node is considered to be
+ * before the I node.
+ *
+ * @param {Node} node1 The first node to compare.
+ * @param {Node} node2 The second node to compare.
+ * @return {number} 0 if the nodes are the same node, a negative number if node1
+ *     is before node2, and a positive number if node2 is before node1.
+ */
+goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder;
 
 
 /**
- * Regular expression used for getLengthUnits.
- * @type {RegExp}
- * @private
+ * Find the deepest common ancestor of the given nodes.
+ * @param {...Node} var_args The nodes to find a common ancestor of.
+ * @return {Node} The common ancestor of the nodes, or null if there is none.
+ *     null will only be returned if two or more of the nodes are from different
+ *     documents.
  */
-goog.style.lengthUnitRegex_ = /[^\d]+$/;
+goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor;
 
 
 /**
- * Returns the units used for a CSS length measurement.
- * @param {string} value  A CSS length quantity.
- * @return {?string} The units of measurement.
+ * Returns the owner document for a node.
+ * @param {Node} node The node to get the document for.
+ * @return {!Document} The document owning the node.
  */
-goog.style.getLengthUnits = function(value) {
-  var units = value.match(goog.style.lengthUnitRegex_);
-  return units && units[0] || null;
-};
+goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
 
 
 /**
- * Map of absolute CSS length units
- * @type {Object}
- * @private
+ * Cross browser function for getting the document element of an iframe.
+ * @param {Element} iframe Iframe element.
+ * @return {!Document} The frame content document.
  */
-goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
-  'cm' : 1,
-  'in' : 1,
-  'mm' : 1,
-  'pc' : 1,
-  'pt' : 1
-};
+goog.dom.DomHelper.prototype.getFrameContentDocument =
+    goog.dom.getFrameContentDocument;
 
 
 /**
- * Map of relative CSS length units that can be accurately converted to px
- * font-size values using getIePixelValue_. Only units that are defined in
- * relation to a font size are convertible (%, small, etc. are not).
- * @type {Object}
- * @private
+ * Cross browser function for getting the window of a frame or iframe.
+ * @param {Element} frame Frame element.
+ * @return {Window} The window associated with the given frame.
  */
-goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
-  'em' : 1,
-  'ex' : 1
-};
+goog.dom.DomHelper.prototype.getFrameContentWindow =
+    goog.dom.getFrameContentWindow;
 
 
 /**
- * Returns the font size, in pixels, of text in an element.
- * @param {Element} el  The element whose font size is returned.
- * @return {number} The font size (in pixels).
+ * Sets the text content of a node, with cross-browser support.
+ * @param {Node} node The node to change the text content of.
+ * @param {string|number} text The value that should replace the node's content.
  */
-goog.style.getFontSize = function(el) {
-  var fontSize = goog.style.getStyle_(el, 'fontSize');
-  var sizeUnits = goog.style.getLengthUnits(fontSize);
-  if (fontSize && 'px' == sizeUnits) {
-    // NOTE(user): This could be parseFloat instead, but IE doesn't return
-    // decimal fractions in getStyle_ and Firefox reports the fractions, but
-    // ignores them when rendering. Interestingly enough, when we force the
-    // issue and size something to e.g., 50% of 25px, the browsers round in
-    // opposite directions with Firefox reporting 12px and IE 13px. I punt.
-    return parseInt(fontSize, 10);
-  }
+goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent;
 
-  // In IE, we can convert absolute length units to a px value using
-  // goog.style.getIePixelValue_. Units defined in relation to a font size
-  // (em, ex) are applied relative to the element's parentNode and can also
-  // be converted.
-  if (goog.userAgent.IE) {
-    if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
-      return goog.style.getIePixelValue_(el,
-                                         fontSize,
-                                         'left',
-                                         'pixelLeft');
-    } else if (el.parentNode &&
-               el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
-               sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
-      // Check the parent size - if it is the same it means the relative size
-      // value is inherited and we therefore don't want to count it twice.  If
-      // it is different, this element either has explicit style or has a CSS
-      // rule applying to it.
-      var parentElement = /** @type {Element} */ (el.parentNode);
-      var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
-      return goog.style.getIePixelValue_(parentElement,
-                                         fontSize == parentSize ?
-                                             '1em' : fontSize,
-                                         'left',
-                                         'pixelLeft');
-    }
-  }
 
-  // Sometimes we can't cleanly find the font size (some units relative to a
-  // node's parent's font size are difficult: %, smaller et al), so we create
-  // an invisible, absolutely-positioned span sized to be the height of an 'M'
-  // rendered in its parent's (i.e., our target element's) font size. This is
-  // the definition of CSS's font size attribute.
-  var sizeElement = goog.dom.createDom(
-      'span',
-      {'style': 'visibility:hidden;position:absolute;' +
-            'line-height:0;padding:0;margin:0;border:0;height:1em;'});
-  goog.dom.appendChild(el, sizeElement);
-  fontSize = sizeElement.offsetHeight;
-  goog.dom.removeNode(sizeElement);
+/**
+ * Gets the outerHTML of a node, which islike innerHTML, except that it
+ * actually contains the HTML of the node itself.
+ * @param {Element} element The element to get the HTML of.
+ * @return {string} The outerHTML of the given element.
+ */
+goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml;
 
-  return fontSize;
-};
+
+/**
+ * Finds the first descendant node that matches the filter function. This does
+ * a depth first search.
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {Node|undefined} The found node or undefined if none is found.
+ */
+goog.dom.DomHelper.prototype.findNode = goog.dom.findNode;
 
 
 /**
- * Parses a style attribute value.  Converts CSS property names to camel case.
- * @param {string} value The style attribute value.
- * @return {!Object} Map of CSS properties to string values.
+ * Finds all the descendant nodes that matches the filter function. This does a
+ * depth first search.
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {Array<Node>} The found nodes or an empty array if none are found.
  */
-goog.style.parseStyleAttribute = function(value) {
-  var result = {};
-  goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
-    var keyValue = pair.split(/\s*:\s*/);
-    if (keyValue.length == 2) {
-      result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1];
-    }
-  });
-  return result;
-};
+goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
 
 
 /**
- * Reverse of parseStyleAttribute; that is, takes a style object and returns the
- * corresponding attribute value.  Converts camel case property names to proper
- * CSS selector names.
- * @param {Object} obj Map of CSS properties to values.
- * @return {string} The style attribute value.
+ * Returns true if the element has a tab index that allows it to receive
+ * keyboard focus (tabIndex >= 0), false otherwise.  Note that some elements
+ * natively support keyboard focus, even if they have no tab index.
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element has a tab index that allows keyboard
+ *     focus.
  */
-goog.style.toStyleAttribute = function(obj) {
-  var buffer = [];
-  goog.object.forEach(obj, function(value, key) {
-    buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
-  });
-  return buffer.join('');
-};
+goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;
 
 
 /**
- * Sets CSS float property on an element.
- * @param {Element} el The element to set float property on.
- * @param {string} value The value of float CSS property to set on this element.
+ * Enables or disables keyboard focus support on the element via its tab index.
+ * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
+ * (or elements that natively support keyboard focus, like form elements) can
+ * receive keyboard focus.  See http://go/tabindex for more info.
+ * @param {Element} element Element whose tab index is to be changed.
+ * @param {boolean} enable Whether to set or remove a tab index on the element
+ *     that supports keyboard focus.
  */
-goog.style.setFloat = function(el, value) {
-  el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
-};
+goog.dom.DomHelper.prototype.setFocusableTabIndex =
+    goog.dom.setFocusableTabIndex;
 
 
 /**
- * Gets value of explicitly-set float CSS property on an element.
- * @param {Element} el The element to get float property of.
- * @return {string} The value of explicitly-set float CSS property on this
- *     element.
+ * Returns true if the element can be focused, i.e. it has a tab index that
+ * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
+ * that natively supports keyboard focus.
+ * @param {!Element} element Element to check.
+ * @return {boolean} Whether the element allows keyboard focus.
  */
-goog.style.getFloat = function(el) {
-  return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
-};
+goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable;
 
 
 /**
- * Returns the scroll bar width (represents the width of both horizontal
- * and vertical scroll).
+ * Returns the text contents of the current node, without markup. New lines are
+ * stripped and whitespace is collapsed, such that each character would be
+ * visible.
  *
- * @param {string=} opt_className An optional class name (or names) to apply
- *     to the invisible div created to measure the scrollbar. This is necessary
- *     if some scrollbars are styled differently than others.
- * @return {number} The scroll bar width in px.
+ * In browsers that support it, innerText is used.  Other browsers attempt to
+ * simulate it via node traversal.  Line breaks are canonicalized in IE.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The text content.
  */
-goog.style.getScrollbarWidth = function(opt_className) {
-  // Add two hidden divs.  The child div is larger than the parent and
-  // forces scrollbars to appear on it.
-  // Using overflow:scroll does not work consistently with scrollbars that
-  // are styled with ::-webkit-scrollbar.
-  var outerDiv = goog.dom.createElement('div');
-  if (opt_className) {
-    outerDiv.className = opt_className;
-  }
-  outerDiv.style.cssText = 'overflow:auto;' +
-      'position:absolute;top:0;width:100px;height:100px';
-  var innerDiv = goog.dom.createElement('div');
-  goog.style.setSize(innerDiv, '200px', '200px');
-  outerDiv.appendChild(innerDiv);
-  goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
-  var width = outerDiv.offsetWidth - outerDiv.clientWidth;
-  goog.dom.removeNode(outerDiv);
-  return width;
-};
+goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
 
 
 /**
- * Regular expression to extract x and y translation components from a CSS
- * transform Matrix representation.
+ * Returns the text length of the text contained in a node, without markup. This
+ * is equivalent to the selection length if the node was selected, or the number
+ * of cursor movements to traverse the node. Images & BRs take one space.  New
+ * lines are ignored.
  *
- * @type {!RegExp}
- * @const
- * @private
+ * @param {Node} node The node whose text content length is being calculated.
+ * @return {number} The length of {@code node}'s text content.
  */
-goog.style.MATRIX_TRANSLATION_REGEX_ =
-    new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
-               '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
-               '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');
+goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
 
 
 /**
- * Returns the x,y translation component of any CSS transforms applied to the
- * element, in pixels.
+ * Returns the text offset of a node relative to one of its ancestors. The text
+ * length is the same as the length calculated by
+ * {@code goog.dom.getNodeTextLength}.
  *
- * @param {!Element} element The element to get the translation of.
- * @return {!goog.math.Coordinate} The CSS translation of the element in px.
+ * @param {Node} node The node whose offset is being calculated.
+ * @param {Node=} opt_offsetParent Defaults to the node's owner document's body.
+ * @return {number} The text offset.
  */
-goog.style.getCssTranslation = function(element) {
-  var transform = goog.style.getComputedTransform(element);
-  if (!transform) {
-    return new goog.math.Coordinate(0, 0);
-  }
-  var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
-  if (!matches) {
-    return new goog.math.Coordinate(0, 0);
-  }
-  return new goog.math.Coordinate(parseFloat(matches[1]),
-                                  parseFloat(matches[2]));
-};
+goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
+
+
+/**
+ * Returns the node at a given offset in a parent node.  If an object is
+ * provided for the optional third parameter, the node and the remainder of the
+ * offset will stored as properties of this object.
+ * @param {Node} parent The parent node.
+ * @param {number} offset The offset into the parent node.
+ * @param {Object=} opt_result Object to be used to store the return value. The
+ *     return value will be stored in the form {node: Node, remainder: number}
+ *     if this object is provided.
+ * @return {Node} The node at the given offset.
+ */
+goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset;
+
+
+/**
+ * Returns true if the object is a {@code NodeList}.  To qualify as a NodeList,
+ * the object must have a numeric length property and an item function (which
+ * has type 'string' on IE for some reason).
+ * @param {Object} val Object to test.
+ * @return {boolean} Whether the object is a NodeList.
+ */
+goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList;
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * tag name and/or class name. If the passed element matches the specified
+ * criteria, the element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
+ *     null/undefined to match only based on class name).
+ * @param {?string=} opt_class The class name to match (or null/undefined to
+ *     match only based on tag name).
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ *     dom.
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ *     null if no match is found.
+ */
+goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass =
+    goog.dom.getAncestorByTagNameAndClass;
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * class name. If the passed element matches the specified criteria, the
+ * element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {string} class The class name to match.
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ *     dom.
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ *     null if none match.
+ */
+goog.dom.DomHelper.prototype.getAncestorByClass =
+    goog.dom.getAncestorByClass;
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that passes the
+ * matcher function.
+ * @param {Node} element The DOM node to start with.
+ * @param {function(Node) : boolean} matcher A function that returns true if the
+ *     passed node matches the desired criteria.
+ * @param {boolean=} opt_includeNode If true, the node itself is included in
+ *     the search (the first call to the matcher will pass startElement as
+ *     the node to test).
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ *     dom.
+ * @return {Node} DOM node that matched the matcher, or null if there was
+ *     no match.
+ */
+goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor;
 
 // FIXME add tests for browser features (Modernizr?)
 
@@ -26305,7 +27473,6 @@ goog.provide('ol.dom.BrowserFeature');
 goog.require('goog.asserts');
 goog.require('goog.dom');
 goog.require('goog.dom.TagName');
-goog.require('goog.style');
 goog.require('goog.userAgent');
 goog.require('goog.vec.Mat4');
 goog.require('ol');
@@ -26479,7 +27646,7 @@ ol.dom.setOpacity = function(element, value) {
       element.style.zIndex = 0;
     }
   } else {
-    goog.style.setOpacity(element, value);
+    element.style.opacity = value;
   }
 };
 
@@ -26714,8 +27881,7 @@ ol.has.CANVAS = ol.ENABLE_CANVAS && (
  * @type {boolean}
  * @api stable
  */
-ol.has.DEVICE_ORIENTATION =
-    'DeviceOrientationEvent' in goog.global;
+ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in goog.global;
 
 
 /**
@@ -26757,8 +27923,7 @@ ol.has.POINTER = 'PointerEvent' in goog.global;
  * @const
  * @type {boolean}
  */
-ol.has.MSPOINTER =
-    !!(goog.global.navigator.msPointerEnabled);
+ol.has.MSPOINTER = !!(goog.global.navigator.msPointerEnabled);
 
 
 /**
@@ -26767,24 +27932,35 @@ ol.has.MSPOINTER =
  * @type {boolean}
  * @api stable
  */
-ol.has.WEBGL = ol.ENABLE_WEBGL && (
-    /**
-     * @return {boolean} WebGL supported.
-     */
-    function() {
-      if (!('WebGLRenderingContext' in goog.global)) {
-        return false;
-      }
+ol.has.WEBGL;
+
+
+(function() {
+  if (ol.ENABLE_WEBGL) {
+    var hasWebGL = false;
+    var textureSize;
+    var /** @type {Array.<string>} */ extensions = [];
+
+    if ('WebGLRenderingContext' in goog.global) {
       try {
         var canvas = /** @type {HTMLCanvasElement} */
             (goog.dom.createElement(goog.dom.TagName.CANVAS));
-        return !goog.isNull(ol.webgl.getContext(canvas, {
+        var gl = ol.webgl.getContext(canvas, {
           failIfMajorPerformanceCaveat: true
-        }));
-      } catch (e) {
-        return false;
-      }
-    })();
+        });
+        if (!goog.isNull(gl)) {
+          hasWebGL = true;
+          textureSize = /** @type {number} */
+              (gl.getParameter(gl.MAX_TEXTURE_SIZE));
+          extensions = gl.getSupportedExtensions();
+        }
+      } catch (e) {}
+    }
+    ol.has.WEBGL = hasWebGL;
+    ol.WEBGL_EXTENSIONS = extensions;
+    ol.WEBGL_MAX_TEXTURE_SIZE = textureSize;
+  }
+})();
 
 // FIXME Test on Internet Explorer with VBArray
 
@@ -27471,7 +28647,7 @@ goog.require('goog.math');
  * RGB color representation. An array containing three elements [r, g, b],
  * each an integer in [0, 255], representing the red, green, and blue components
  * of the color respectively.
- * @typedef {Array.<number>}
+ * @typedef {Array<number>}
  */
 goog.color.Rgb;
 
@@ -27481,7 +28657,7 @@ goog.color.Rgb;
  * h (hue) must be an integer in [0, 360], cyclic.
  * s (saturation) must be a number in [0, 1].
  * v (value/brightness) must be an integer in [0, 255].
- * @typedef {Array.<number>}
+ * @typedef {Array<number>}
  */
 goog.color.Hsv;
 
@@ -27491,7 +28667,7 @@ goog.color.Hsv;
  * h (hue) must be an integer in [0, 360], cyclic.
  * s (saturation) must be a number in [0, 1].
  * l (lightness) must be a number in [0, 1].
- * @typedef {Array.<number>}
+ * @typedef {Array<number>}
  */
 goog.color.Hsl;
 
@@ -28157,7 +29333,7 @@ goog.color.lighten = function(rgb, factor) {
  * color. Uses W3C formula for judging readability and visual accessibility:
  * http://www.w3.org/TR/AERT#color-contrast
  * @param {goog.color.Rgb} prime Color represented as a rgb array.
- * @param {Array.<goog.color.Rgb>} suggestions Array of colors,
+ * @param {Array<goog.color.Rgb>} suggestions Array of colors,
  *     each representing a rgb array.
  * @return {!goog.color.Rgb} Highest-contrast color represented by an array..
  */
@@ -28313,6 +29489,8 @@ ol.color.blend = function(dst, src, opt_color) {
 
 
 /**
+ * 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
@@ -28328,6 +29506,7 @@ ol.color.asArray = function(color) {
 
 
 /**
+ * Return the color as an rgba string.
  * @param {ol.Color|string} color Color.
  * @return {string} Rgba string.
  * @api
@@ -28356,12 +29535,11 @@ ol.color.equals = function(color1, color2) {
 
 /**
  * @param {string} s String.
- * @param {ol.Color=} opt_color Color.
  * @return {ol.Color} Color.
  */
 ol.color.fromString = (
     /**
-     * @return {function(string, ol.Color=): ol.Color}
+     * @return {function(string): ol.Color}
      */
     function() {
 
@@ -28388,10 +29566,9 @@ ol.color.fromString = (
       return (
           /**
            * @param {string} s String.
-           * @param {ol.Color=} opt_color Color.
            * @return {ol.Color} Color.
            */
-          function(s, opt_color) {
+          function(s) {
             var color;
             if (cache.hasOwnProperty(s)) {
               color = cache[s];
@@ -28410,7 +29587,7 @@ ol.color.fromString = (
               cache[s] = color;
               ++cacheSize;
             }
-            return ol.color.returnOrUpdate(color, opt_color);
+            return color;
           });
 
     })();
@@ -28494,24 +29671,6 @@ ol.color.normalize = function(color, opt_color) {
 };
 
 
-/**
- * @param {ol.Color} color Color.
- * @param {ol.Color=} opt_color Color.
- * @return {ol.Color} Color.
- */
-ol.color.returnOrUpdate = function(color, opt_color) {
-  if (goog.isDef(opt_color)) {
-    opt_color[0] = color[0];
-    opt_color[1] = color[1];
-    opt_color[2] = color[2];
-    opt_color[3] = color[3];
-    return opt_color;
-  } else {
-    return color;
-  }
-};
-
-
 /**
  * @param {ol.Color} color Color.
  * @return {string} String.
@@ -28878,7 +30037,7 @@ goog.dom.classlist.add = function(element, className) {
 /**
  * Convenience method to add a number of class names at once.
  * @param {Element} element The element to which to add classes.
- * @param {goog.array.ArrayLike.<string>} classesToAdd An array-like object
+ * @param {goog.array.ArrayLike<string>} classesToAdd An array-like object
  * containing a collection of class names to add to the element.
  * This method may throw a DOM exception if classesToAdd contains invalid
  * or empty class names.
@@ -28942,7 +30101,7 @@ goog.dom.classlist.remove = function(element, className) {
  * repeatedly calling {@code goog.dom.classlist.remove} if you want to remove
  * a large set of class names at once.
  * @param {Element} element The element from which to remove classes.
- * @param {goog.array.ArrayLike.<string>} classesToRemove An array-like object
+ * @param {goog.array.ArrayLike<string>} classesToRemove An array-like object
  * containing a collection of class names to remove from the element.
  * This method may throw a DOM exception if classesToRemove contains invalid
  * or empty class names.
@@ -28988,7 +30147,7 @@ goog.dom.classlist.enable = function(element, className, enabled) {
  * method may throw a DOM exception for an invalid or empty class name if
  * DOMTokenList is used.
  * @param {!Element} element DOM node to add or remove the class on.
- * @param {goog.array.ArrayLike.<string>} classesToEnable An array-like object
+ * @param {goog.array.ArrayLike<string>} classesToEnable An array-like object
  *     containing a collection of class names to add or remove from the element.
  * @param {boolean} enabled Whether to add or remove the classes (true adds,
  *     false removes).
@@ -29051,3077 +30210,3405 @@ goog.dom.classlist.addRemove = function(element, classToRemove, classToAdd) {
   goog.dom.classlist.add(element, classToAdd);
 };
 
-goog.provide('ol.MapEvent');
-goog.provide('ol.MapEventType');
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-goog.require('goog.events.Event');
+/**
+ * @fileoverview Vendor prefix getters.
+ */
+
+goog.provide('goog.dom.vendor');
+
+goog.require('goog.string');
+goog.require('goog.userAgent');
 
 
 /**
- * @enum {string}
+ * Returns the JS vendor prefix used in CSS properties. Different vendors
+ * use different methods of changing the case of the property names.
+ *
+ * @return {?string} The JS vendor prefix or null if there is none.
  */
-ol.MapEventType = {
-  /**
-   * Triggered after a map frame is rendered.
-   * @event ol.MapEvent#postrender
-   * @api
-   */
-  POSTRENDER: 'postrender',
-  /**
-   * Triggered after the map is moved.
-   * @event ol.MapEvent#moveend
-   * @api
-   */
-  MOVEEND: 'moveend'
-};
+goog.dom.vendor.getVendorJsPrefix = function() {
+  if (goog.userAgent.WEBKIT) {
+    return 'Webkit';
+  } else if (goog.userAgent.GECKO) {
+    return 'Moz';
+  } else if (goog.userAgent.IE) {
+    return 'ms';
+  } else if (goog.userAgent.OPERA) {
+    return 'O';
+  }
 
+  return null;
+};
 
 
 /**
- * @classdesc
- * Events emitted as map events are instances of this type.
- * See {@link ol.Map} for which events trigger a map event.
+ * Returns the vendor prefix used in CSS properties.
  *
- * @constructor
- * @extends {goog.events.Event}
- * @implements {oli.MapEvent}
- * @param {string} type Event type.
- * @param {ol.Map} map Map.
- * @param {?olx.FrameState=} opt_frameState Frame state.
+ * @return {?string} The vendor prefix or null if there is none.
  */
-ol.MapEvent = function(type, map, opt_frameState) {
+goog.dom.vendor.getVendorPrefix = function() {
+  if (goog.userAgent.WEBKIT) {
+    return '-webkit';
+  } else if (goog.userAgent.GECKO) {
+    return '-moz';
+  } else if (goog.userAgent.IE) {
+    return '-ms';
+  } else if (goog.userAgent.OPERA) {
+    return '-o';
+  }
 
-  goog.base(this, type);
+  return null;
+};
 
-  /**
-   * 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 = goog.isDef(opt_frameState) ? opt_frameState : null;
+/**
+ * @param {string} propertyName A property name.
+ * @param {!Object=} opt_object If provided, we verify if the property exists in
+ *     the object.
+ * @return {?string} A vendor prefixed property name, or null if it does not
+ *     exist.
+ */
+goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) {
+  // We first check for a non-prefixed property, if available.
+  if (opt_object && propertyName in opt_object) {
+    return propertyName;
+  }
+  var prefix = goog.dom.vendor.getVendorJsPrefix();
+  if (prefix) {
+    prefix = prefix.toLowerCase();
+    var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName);
+    return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ?
+        prefixedPropertyName : null;
+  }
+  return null;
+};
 
+
+/**
+ * @param {string} eventType An event type.
+ * @return {string} A lower-cased vendor prefixed event type.
+ */
+goog.dom.vendor.getPrefixedEventType = function(eventType) {
+  var prefix = goog.dom.vendor.getVendorJsPrefix() || '';
+  return (prefix + eventType).toLowerCase();
 };
-goog.inherits(ol.MapEvent, goog.events.Event);
 
-goog.provide('ol.control.Control');
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A utility class for representing a numeric box.
+ */
 
-goog.require('goog.array');
-goog.require('goog.dom');
-goog.require('goog.events');
-goog.require('ol.MapEventType');
-goog.require('ol.Object');
+
+goog.provide('goog.math.Box');
+
+goog.require('goog.math.Coordinate');
 
 
 
 /**
- * @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.
+ * Class for representing a box. A box is specified as a top, right, bottom,
+ * and left. A box is useful for representing margins and padding.
  *
- * You can also extend this base for your own control class. See
- * examples/custom-controls for an example of how to do this.
+ * This class assumes 'screen coordinates': larger Y coordinates are further
+ * from the top of the screen.
  *
+ * @param {number} top Top.
+ * @param {number} right Right.
+ * @param {number} bottom Bottom.
+ * @param {number} left Left.
  * @constructor
- * @extends {ol.Object}
- * @implements {oli.control.Control}
- * @param {olx.control.ControlOptions} options Control options.
- * @api stable
  */
-ol.control.Control = function(options) {
-
-  goog.base(this);
-
+goog.math.Box = function(top, right, bottom, left) {
   /**
-   * @protected
-   * @type {Element}
+   * Top
+   * @type {number}
    */
-  this.element = goog.isDef(options.element) ? options.element : null;
+  this.top = top;
 
   /**
-   * @private
-   * @type {Element}
+   * Right
+   * @type {number}
    */
-  this.target_ = goog.isDef(options.target) ?
-      goog.dom.getElement(options.target) : null;
+  this.right = right;
 
   /**
-   * @private
-   * @type {ol.Map}
+   * Bottom
+   * @type {number}
    */
-  this.map_ = null;
+  this.bottom = bottom;
 
   /**
-   * @protected
-   * @type {!Array.<?number>}
+   * Left
+   * @type {number}
    */
-  this.listenerKeys = [];
-
+  this.left = left;
 };
-goog.inherits(ol.control.Control, ol.Object);
 
 
 /**
- * @inheritDoc
+ * Creates a Box by bounding a collection of goog.math.Coordinate objects
+ * @param {...goog.math.Coordinate} var_args Coordinates to be included inside
+ *     the box.
+ * @return {!goog.math.Box} A Box containing all the specified Coordinates.
  */
-ol.control.Control.prototype.disposeInternal = function() {
-  goog.dom.removeNode(this.element);
-  goog.base(this, 'disposeInternal');
+goog.math.Box.boundingBox = function(var_args) {
+  var box = new goog.math.Box(arguments[0].y, arguments[0].x,
+                              arguments[0].y, arguments[0].x);
+  for (var i = 1; i < arguments.length; i++) {
+    var coord = arguments[i];
+    box.top = Math.min(box.top, coord.y);
+    box.right = Math.max(box.right, coord.x);
+    box.bottom = Math.max(box.bottom, coord.y);
+    box.left = Math.min(box.left, coord.x);
+  }
+  return box;
 };
 
 
 /**
- * Get the map associated with this control.
- * @return {ol.Map} Map.
- * @api stable
+ * @return {number} width The width of this Box.
  */
-ol.control.Control.prototype.getMap = function() {
-  return this.map_;
+goog.math.Box.prototype.getWidth = function() {
+  return this.right - this.left;
 };
 
 
 /**
- * Function called on each map render. Executes in a requestAnimationFrame
- * callback. Can be implemented in sub-classes to re-render the control's
- * UI.
- * @param {ol.MapEvent} mapEvent Map event.
+ * @return {number} height The height of this Box.
  */
-ol.control.Control.prototype.handleMapPostrender = goog.nullFunction;
+goog.math.Box.prototype.getHeight = function() {
+  return this.bottom - this.top;
+};
 
 
 /**
- * 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
+ * Creates a copy of the box with the same dimensions.
+ * @return {!goog.math.Box} A clone of this Box.
  */
-ol.control.Control.prototype.setMap = function(map) {
-  if (!goog.isNull(this.map_)) {
-    goog.dom.removeNode(this.element);
-  }
-  if (!goog.array.isEmpty(this.listenerKeys)) {
-    goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey);
-    this.listenerKeys.length = 0;
-  }
-  this.map_ = map;
-  if (!goog.isNull(this.map_)) {
-    var target = !goog.isNull(this.target_) ?
-        this.target_ : map.getOverlayContainerStopEvent();
-    goog.dom.appendChild(target, this.element);
-    if (this.handleMapPostrender !== goog.nullFunction) {
-      this.listenerKeys.push(goog.events.listen(map,
-          ol.MapEventType.POSTRENDER, this.handleMapPostrender, false, this));
-    }
-    map.render();
-  }
+goog.math.Box.prototype.clone = function() {
+  return new goog.math.Box(this.top, this.right, this.bottom, this.left);
 };
 
-goog.provide('ol.css');
+
+if (goog.DEBUG) {
+  /**
+   * Returns a nice string representing the box.
+   * @return {string} In the form (50t, 73r, 24b, 13l).
+   * @override
+   */
+  goog.math.Box.prototype.toString = function() {
+    return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' +
+           this.left + 'l)';
+  };
+}
 
 
 /**
- * The CSS class that we'll give the DOM elements to have them unselectable.
+ * Returns whether the box contains a coordinate or another box.
  *
- * @const
- * @type {string}
+ * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
+ * @return {boolean} Whether the box contains the coordinate or other box.
  */
-ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
+goog.math.Box.prototype.contains = function(other) {
+  return goog.math.Box.contains(this, other);
+};
 
 
 /**
- * The CSS class for unsupported feature.
+ * Expands box with the given margins.
  *
- * @const
- * @type {string}
+ * @param {number|goog.math.Box} top Top margin or box with all margins.
+ * @param {number=} opt_right Right margin.
+ * @param {number=} opt_bottom Bottom margin.
+ * @param {number=} opt_left Left margin.
+ * @return {!goog.math.Box} A reference to this Box.
  */
-ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
+goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom,
+    opt_left) {
+  if (goog.isObject(top)) {
+    this.top -= top.top;
+    this.right += top.right;
+    this.bottom += top.bottom;
+    this.left -= top.left;
+  } else {
+    this.top -= top;
+    this.right += opt_right;
+    this.bottom += opt_bottom;
+    this.left -= opt_left;
+  }
+
+  return this;
+};
 
 
 /**
- * The CSS class for controls.
- *
- * @const
- * @type {string}
+ * Expand this box to include another box.
+ * NOTE(user): This is used in code that needs to be very fast, please don't
+ * add functionality to this function at the expense of speed (variable
+ * arguments, accepting multiple argument types, etc).
+ * @param {goog.math.Box} box The box to include in this one.
  */
-ol.css.CLASS_CONTROL = 'ol-control';
-
-goog.provide('ol.pointer.EventSource');
+goog.math.Box.prototype.expandToInclude = function(box) {
+  this.left = Math.min(this.left, box.left);
+  this.top = Math.min(this.top, box.top);
+  this.right = Math.max(this.right, box.right);
+  this.bottom = Math.max(this.bottom, box.bottom);
+};
 
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.object');
 
+/**
+ * Compares boxes for equality.
+ * @param {goog.math.Box} a A Box.
+ * @param {goog.math.Box} b A Box.
+ * @return {boolean} True iff the boxes are equal, or if both are null.
+ */
+goog.math.Box.equals = function(a, b) {
+  if (a == b) {
+    return true;
+  }
+  if (!a || !b) {
+    return false;
+  }
+  return a.top == b.top && a.right == b.right &&
+         a.bottom == b.bottom && a.left == b.left;
+};
 
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @param {Object.<string, function(goog.events.BrowserEvent)>} mapping
- * @constructor
+ * Returns whether a box contains a coordinate or another box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
+ * @return {boolean} Whether the box contains the coordinate or other box.
  */
-ol.pointer.EventSource = function(dispatcher, mapping) {
-  /**
-   * @type {ol.pointer.PointerEventHandler}
-   */
-  this.dispatcher = dispatcher;
+goog.math.Box.contains = function(box, other) {
+  if (!box || !other) {
+    return false;
+  }
 
-  /**
-   * @private
-   * @const
-   * @type {Object.<string, function(goog.events.BrowserEvent)>}
-   */
-  this.mapping_ = mapping;
+  if (other instanceof goog.math.Box) {
+    return other.left >= box.left && other.right <= box.right &&
+        other.top >= box.top && other.bottom <= box.bottom;
+  }
+
+  // other is a Coordinate.
+  return other.x >= box.left && other.x <= box.right &&
+         other.y >= box.top && other.y <= box.bottom;
 };
 
 
 /**
- * List of events supported by this source.
- * @return {Array.<string>} Event names
+ * Returns the relative x position of a coordinate compared to a box.  Returns
+ * zero if the coordinate is inside the box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate} coord A Coordinate.
+ * @return {number} The x position of {@code coord} relative to the nearest
+ *     side of {@code box}, or zero if {@code coord} is inside {@code box}.
  */
-ol.pointer.EventSource.prototype.getEvents = function() {
-  return goog.object.getKeys(this.mapping_);
+goog.math.Box.relativePositionX = function(box, coord) {
+  if (coord.x < box.left) {
+    return coord.x - box.left;
+  } else if (coord.x > box.right) {
+    return coord.x - box.right;
+  }
+  return 0;
 };
 
 
 /**
- * Returns a mapping between the supported event types and
- * the handlers that should handle an event.
- * @return {Object.<string, function(goog.events.BrowserEvent)>}
- *         Event/Handler mapping
+ * Returns the relative y position of a coordinate compared to a box.  Returns
+ * zero if the coordinate is inside the box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate} coord A Coordinate.
+ * @return {number} The y position of {@code coord} relative to the nearest
+ *     side of {@code box}, or zero if {@code coord} is inside {@code box}.
  */
-ol.pointer.EventSource.prototype.getMapping = function() {
-  return this.mapping_;
+goog.math.Box.relativePositionY = function(box, coord) {
+  if (coord.y < box.top) {
+    return coord.y - box.top;
+  } else if (coord.y > box.bottom) {
+    return coord.y - box.bottom;
+  }
+  return 0;
 };
 
 
 /**
- * Returns the handler that should handle a given event type.
- * @param {string} eventType
- * @return {function(goog.events.BrowserEvent)} Handler
+ * Returns the distance between a coordinate and the nearest corner/side of a
+ * box. Returns zero if the coordinate is inside the box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate} coord A Coordinate.
+ * @return {number} The distance between {@code coord} and the nearest
+ *     corner/side of {@code box}, or zero if {@code coord} is inside
+ *     {@code box}.
  */
-ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
-  return this.mapping_[eventType];
+goog.math.Box.distance = function(box, coord) {
+  var x = goog.math.Box.relativePositionX(box, coord);
+  var y = goog.math.Box.relativePositionY(box, coord);
+  return Math.sqrt(x * x + y * y);
 };
 
-// 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');
+/**
+ * Returns whether two boxes intersect.
+ *
+ * @param {goog.math.Box} a A Box.
+ * @param {goog.math.Box} b A second Box.
+ * @return {boolean} Whether the boxes intersect.
+ */
+goog.math.Box.intersects = function(a, b) {
+  return (a.left <= b.right && b.left <= a.right &&
+          a.top <= b.bottom && b.top <= a.bottom);
+};
 
-goog.require('goog.object');
-goog.require('ol.pointer.EventSource');
 
+/**
+ * Returns whether two boxes would intersect with additional padding.
+ *
+ * @param {goog.math.Box} a A Box.
+ * @param {goog.math.Box} b A second Box.
+ * @param {number} padding The additional padding.
+ * @return {boolean} Whether the boxes intersect.
+ */
+goog.math.Box.intersectsWithPadding = function(a, b, padding) {
+  return (a.left <= b.right + padding && b.left <= a.right + padding &&
+          a.top <= b.bottom + padding && b.top <= a.bottom + padding);
+};
 
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @constructor
- * @extends {ol.pointer.EventSource}
- */
-ol.pointer.MouseSource = function(dispatcher) {
-  var mapping = {
-    'mousedown': this.mousedown,
-    'mousemove': this.mousemove,
-    'mouseup': this.mouseup,
-    'mouseover': this.mouseover,
-    'mouseout': this.mouseout
-  };
-  goog.base(this, dispatcher, mapping);
-
-  /**
-   * @const
-   * @type {Object.<string, goog.events.BrowserEvent|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
-
-  /**
-   * @const
-   * @type {Array.<ol.Pixel>}
-   */
-  this.lastTouches = [];
-};
-goog.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
-
-
-/**
- * @const
- * @type {number}
- */
-ol.pointer.MouseSource.POINTER_ID = 1;
-
-
-/**
- * @const
- * @type {string}
- */
-ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
-
-
-/**
- * Radius around touchend that swallows mouse events.
+ * Rounds the fields to the next larger integer values.
  *
- * @const
- * @type {number}
+ * @return {!goog.math.Box} This box with ceil'd fields.
  */
-ol.pointer.MouseSource.DEDUP_DIST = 25;
+goog.math.Box.prototype.ceil = function() {
+  this.top = Math.ceil(this.top);
+  this.right = Math.ceil(this.right);
+  this.bottom = Math.ceil(this.bottom);
+  this.left = Math.ceil(this.left);
+  return this;
+};
 
 
 /**
- * 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.
+ * Rounds the fields to the next smaller integer values.
  *
- * @private
- * @param {goog.events.BrowserEvent} inEvent
- * @return {boolean} True, if the event was generated by a touch.
+ * @return {!goog.math.Box} This box with floored fields.
  */
-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;
+goog.math.Box.prototype.floor = function() {
+  this.top = Math.floor(this.top);
+  this.right = Math.floor(this.right);
+  this.bottom = Math.floor(this.bottom);
+  this.left = Math.floor(this.left);
+  return this;
 };
 
 
 /**
- * Creates a copy of the original event that will be used
- * for the fake pointer event.
+ * Rounds the fields to nearest integer values.
  *
- * @param {goog.events.BrowserEvent} inEvent
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @return {Object}
+ * @return {!goog.math.Box} This box with rounded fields.
  */
-ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
-  var e = dispatcher.cloneEvent(inEvent, inEvent.getBrowserEvent());
-
-  // forward mouse preventDefault
-  var pd = e.preventDefault;
-  e.preventDefault = function() {
-    inEvent.preventDefault();
-    pd();
-  };
-
-  e.pointerId = ol.pointer.MouseSource.POINTER_ID;
-  e.isPrimary = true;
-  e.pointerType = ol.pointer.MouseSource.POINTER_TYPE;
-
-  return e;
+goog.math.Box.prototype.round = function() {
+  this.top = Math.round(this.top);
+  this.right = Math.round(this.right);
+  this.bottom = Math.round(this.bottom);
+  this.left = Math.round(this.left);
+  return this;
 };
 
 
 /**
- * Handler for `mousedown`.
+ * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
+ * is given, then the left and right values are translated by the coordinate's
+ * x value and the top and bottom values are translated by the coordinate's y
+ * value.  Otherwise, {@code tx} and {@code opt_ty} are used to translate the x
+ * and y dimension values.
  *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {number|goog.math.Coordinate} tx The value to translate the x
+ *     dimension values by or the the coordinate to translate this box by.
+ * @param {number=} opt_ty The value to translate y dimension values by.
+ * @return {!goog.math.Box} This box after translating.
  */
-ol.pointer.MouseSource.prototype.mousedown = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var p = goog.object.containsKey(this.pointerMap,
-        ol.pointer.MouseSource.POINTER_ID.toString());
-    // TODO(dfreedman) workaround for some elements not sending mouseup
-    // http://crbug/149091
-    if (p) {
-      this.cancel(inEvent);
+goog.math.Box.prototype.translate = function(tx, opt_ty) {
+  if (tx instanceof goog.math.Coordinate) {
+    this.left += tx.x;
+    this.right += tx.x;
+    this.top += tx.y;
+    this.bottom += tx.y;
+  } else {
+    this.left += tx;
+    this.right += tx;
+    if (goog.isNumber(opt_ty)) {
+      this.top += opt_ty;
+      this.bottom += opt_ty;
     }
-    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-    goog.object.set(this.pointerMap,
-        ol.pointer.MouseSource.POINTER_ID.toString(), inEvent);
-    this.dispatcher.down(e, inEvent);
   }
+  return this;
 };
 
 
 /**
- * Handler for `mousemove`.
+ * Scales this coordinate by the given scale factors. The x and y dimension
+ * values are scaled by {@code sx} and {@code opt_sy} respectively.
+ * If {@code opt_sy} is not given, then {@code sx} is used for both x and y.
  *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {number} sx The scale factor to use for the x dimension.
+ * @param {number=} opt_sy The scale factor to use for the y dimension.
+ * @return {!goog.math.Box} This box after scaling.
  */
-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);
-  }
+goog.math.Box.prototype.scale = function(sx, opt_sy) {
+  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+  this.left *= sx;
+  this.right *= sx;
+  this.top *= sy;
+  this.bottom *= sy;
+  return this;
 };
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Handler for `mouseup`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @fileoverview A utility class for representing rectangles.
  */
-ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var p = goog.object.get(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();
-    }
-  }
-};
+goog.provide('goog.math.Rect');
 
+goog.require('goog.math.Box');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Size');
 
-/**
- * Handler for `mouseover`.
- *
- * @param {goog.events.BrowserEvent} inEvent
- */
-ol.pointer.MouseSource.prototype.mouseover = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-    this.dispatcher.enterOver(e, inEvent);
-  }
-};
 
 
 /**
- * Handler for `mouseout`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * Class for representing rectangular regions.
+ * @param {number} x Left.
+ * @param {number} y Top.
+ * @param {number} w Width.
+ * @param {number} h Height.
+ * @constructor
  */
-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);
-  }
+goog.math.Rect = function(x, y, w, h) {
+  /** @type {number} */
+  this.left = x;
+
+  /** @type {number} */
+  this.top = y;
+
+  /** @type {number} */
+  this.width = w;
+
+  /** @type {number} */
+  this.height = h;
 };
 
 
 /**
- * Dispatches a `pointercancel` event.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @return {!goog.math.Rect} A new copy of this Rectangle.
  */
-ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
-  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanupMouse();
+goog.math.Rect.prototype.clone = function() {
+  return new goog.math.Rect(this.left, this.top, this.width, this.height);
 };
 
 
 /**
- * Remove the mouse from the list of active pointers.
+ * Returns a new Box object with the same position and dimensions as this
+ * rectangle.
+ * @return {!goog.math.Box} A new Box representation of this Rectangle.
  */
-ol.pointer.MouseSource.prototype.cleanupMouse = function() {
-  goog.object.remove(this.pointerMap,
-      ol.pointer.MouseSource.POINTER_ID.toString());
+goog.math.Rect.prototype.toBox = function() {
+  var right = this.left + this.width;
+  var bottom = this.top + this.height;
+  return new goog.math.Box(this.top,
+                           right,
+                           bottom,
+                           this.left);
 };
 
-// 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('goog.object');
-goog.require('ol.pointer.EventSource');
-
-
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @constructor
- * @extends {ol.pointer.EventSource}
+ * Creates a new Rect object with the same position and dimensions as a given
+ * Box.  Note that this is only the inverse of toBox if left/top are defined.
+ * @param {goog.math.Box} box A box.
+ * @return {!goog.math.Rect} A new Rect initialized with the box's position
+ *     and size.
  */
-ol.pointer.MsSource = function(dispatcher) {
-  var mapping = {
-    'MSPointerDown': this.msPointerDown,
-    'MSPointerMove': this.msPointerMove,
-    'MSPointerUp': this.msPointerUp,
-    'MSPointerOut': this.msPointerOut,
-    'MSPointerOver': this.msPointerOver,
-    'MSPointerCancel': this.msPointerCancel,
-    'MSGotPointerCapture': this.msGotPointerCapture,
-    'MSLostPointerCapture': this.msLostPointerCapture
-  };
-  goog.base(this, dispatcher, mapping);
+goog.math.Rect.createFromBox = function(box) {
+  return new goog.math.Rect(box.left, box.top,
+      box.right - box.left, box.bottom - box.top);
+};
 
-  /**
-   * @const
-   * @type {Object.<string, goog.events.BrowserEvent|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
 
+if (goog.DEBUG) {
   /**
-   * @const
-   * @type {Array.<string>}
+   * Returns a nice string representing size and dimensions of rectangle.
+   * @return {string} In the form (50, 73 - 75w x 25h).
+   * @override
    */
-  this.POINTER_TYPES = [
-    '',
-    'unavailable',
-    'touch',
-    'pen',
-    'mouse'
-  ];
-};
-goog.inherits(ol.pointer.MsSource, ol.pointer.EventSource);
+  goog.math.Rect.prototype.toString = function() {
+    return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +
+           this.height + 'h)';
+  };
+}
 
 
 /**
- * Creates a copy of the original event that will be used
- * for the fake pointer event.
- *
- * @private
- * @param {goog.events.BrowserEvent} inEvent
- * @return {Object}
+ * Compares rectangles for equality.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {boolean} True iff the rectangles have the same left, top, width,
+ *     and height, or if both are null.
  */
-ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) {
-  var e = inEvent;
-  if (goog.isNumber(inEvent.getBrowserEvent().pointerType)) {
-    e = this.dispatcher.cloneEvent(inEvent, inEvent.getBrowserEvent());
-    e.pointerType = this.POINTER_TYPES[inEvent.getBrowserEvent().pointerType];
+goog.math.Rect.equals = function(a, b) {
+  if (a == b) {
+    return true;
   }
-
-  return e;
+  if (!a || !b) {
+    return false;
+  }
+  return a.left == b.left && a.width == b.width &&
+         a.top == b.top && a.height == b.height;
 };
 
 
 /**
- * Remove this pointer from the list of active pointers.
- * @param {number} pointerId
+ * Computes the intersection of this rectangle and the rectangle parameter.  If
+ * there is no intersection, returns false and leaves this rectangle as is.
+ * @param {goog.math.Rect} rect A Rectangle.
+ * @return {boolean} True iff this rectangle intersects with the parameter.
  */
-ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
-  goog.object.remove(this.pointerMap, pointerId);
-};
+goog.math.Rect.prototype.intersection = function(rect) {
+  var x0 = Math.max(this.left, rect.left);
+  var x1 = Math.min(this.left + this.width, rect.left + rect.width);
 
+  if (x0 <= x1) {
+    var y0 = Math.max(this.top, rect.top);
+    var y1 = Math.min(this.top + this.height, rect.top + rect.height);
 
-/**
- * Handler for `msPointerDown`.
- *
- * @param {goog.events.BrowserEvent} inEvent
- */
-ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
-  goog.object.set(this.pointerMap,
-      inEvent.getBrowserEvent().pointerId, inEvent);
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.down(e, inEvent);
+    if (y0 <= y1) {
+      this.left = x0;
+      this.top = y0;
+      this.width = x1 - x0;
+      this.height = y1 - y0;
+
+      return true;
+    }
+  }
+  return false;
 };
 
 
 /**
- * Handler for `msPointerMove`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * Returns the intersection of two rectangles. Two rectangles intersect if they
+ * touch at all, for example, two zero width and height rectangles would
+ * intersect if they had the same top and left.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {goog.math.Rect} A new intersection rect (even if width and height
+ *     are 0), or null if there is no intersection.
  */
-ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.move(e, inEvent);
-};
+goog.math.Rect.intersection = function(a, b) {
+  // There is no nice way to do intersection via a clone, because any such
+  // clone might be unnecessary if this function returns null.  So, we duplicate
+  // code from above.
 
+  var x0 = Math.max(a.left, b.left);
+  var x1 = Math.min(a.left + a.width, b.left + b.width);
 
-/**
- * Handler for `msPointerUp`.
- *
- * @param {goog.events.BrowserEvent} inEvent
- */
-ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.up(e, inEvent);
-  this.cleanup(inEvent.getBrowserEvent().pointerId);
+  if (x0 <= x1) {
+    var y0 = Math.max(a.top, b.top);
+    var y1 = Math.min(a.top + a.height, b.top + b.height);
+
+    if (y0 <= y1) {
+      return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
+    }
+  }
+  return null;
 };
 
 
 /**
- * Handler for `msPointerOut`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * Returns whether two rectangles intersect. Two rectangles intersect if they
+ * touch at all, for example, two zero width and height rectangles would
+ * intersect if they had the same top and left.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {boolean} Whether a and b intersect.
  */
-ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.leaveOut(e, inEvent);
+goog.math.Rect.intersects = function(a, b) {
+  return (a.left <= b.left + b.width && b.left <= a.left + a.width &&
+      a.top <= b.top + b.height && b.top <= a.top + a.height);
 };
 
 
 /**
- * Handler for `msPointerOver`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * Returns whether a rectangle intersects this rectangle.
+ * @param {goog.math.Rect} rect A rectangle.
+ * @return {boolean} Whether rect intersects this rectangle.
  */
-ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.enterOver(e, inEvent);
+goog.math.Rect.prototype.intersects = function(rect) {
+  return goog.math.Rect.intersects(this, rect);
 };
 
 
 /**
- * Handler for `msPointerCancel`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * Computes the difference regions between two rectangles. The return value is
+ * an array of 0 to 4 rectangles defining the remaining regions of the first
+ * rectangle after the second has been subtracted.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
+ *     together define the difference area of rectangle a minus rectangle b.
  */
-ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanup(inEvent.getBrowserEvent().pointerId);
-};
+goog.math.Rect.difference = function(a, b) {
+  var intersection = goog.math.Rect.intersection(a, b);
+  if (!intersection || !intersection.height || !intersection.width) {
+    return [a.clone()];
+  }
 
+  var result = [];
 
-/**
- * Handler for `msLostPointerCapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
- */
-ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('lostpointercapture',
-      inEvent.getBrowserEvent(), inEvent);
-  this.dispatcher.dispatchEvent(e);
+  var top = a.top;
+  var height = a.height;
+
+  var ar = a.left + a.width;
+  var ab = a.top + a.height;
+
+  var br = b.left + b.width;
+  var bb = b.top + b.height;
+
+  // Subtract off any area on top where A extends past B
+  if (b.top > a.top) {
+    result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));
+    top = b.top;
+    // If we're moving the top down, we also need to subtract the height diff.
+    height -= b.top - a.top;
+  }
+  // Subtract off any area on bottom where A extends past B
+  if (bb < ab) {
+    result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));
+    height = bb - top;
+  }
+  // Subtract any area on left where A extends past B
+  if (b.left > a.left) {
+    result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));
+  }
+  // Subtract any area on right where A extends past B
+  if (br < ar) {
+    result.push(new goog.math.Rect(br, top, ar - br, height));
+  }
+
+  return result;
 };
 
 
 /**
- * Handler for `msGotPointerCapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * Computes the difference regions between this rectangle and {@code rect}. The
+ * return value is an array of 0 to 4 rectangles defining the remaining regions
+ * of this rectangle after the other has been subtracted.
+ * @param {goog.math.Rect} rect A Rectangle.
+ * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
+ *     together define the difference area of rectangle a minus rectangle b.
  */
-ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('gotpointercapture',
-      inEvent.getBrowserEvent(), inEvent);
-  this.dispatcher.dispatchEvent(e);
+goog.math.Rect.prototype.difference = function(rect) {
+  return goog.math.Rect.difference(this, rect);
 };
 
-// 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');
+/**
+ * Expand this rectangle to also include the area of the given rectangle.
+ * @param {goog.math.Rect} rect The other rectangle.
+ */
+goog.math.Rect.prototype.boundingRect = function(rect) {
+  // We compute right and bottom before we change left and top below.
+  var right = Math.max(this.left + this.width, rect.left + rect.width);
+  var bottom = Math.max(this.top + this.height, rect.top + rect.height);
 
-goog.require('goog.object');
-goog.require('ol.pointer.EventSource');
+  this.left = Math.min(this.left, rect.left);
+  this.top = Math.min(this.top, rect.top);
 
+  this.width = right - this.left;
+  this.height = bottom - this.top;
+};
 
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @constructor
- * @extends {ol.pointer.EventSource}
+ * Returns a new rectangle which completely contains both input rectangles.
+ * @param {goog.math.Rect} a A rectangle.
+ * @param {goog.math.Rect} b A rectangle.
+ * @return {goog.math.Rect} A new bounding rect, or null if either rect is
+ *     null.
  */
-ol.pointer.NativeSource = function(dispatcher) {
-  var mapping = {
-    'pointerdown': this.pointerDown,
-    'pointermove': this.pointerMove,
-    'pointerup': this.pointerUp,
-    'pointerout': this.pointerOut,
-    'pointerover': this.pointerOver,
-    'pointercancel': this.pointerCancel,
-    'gotpointercapture': this.gotPointerCapture,
-    'lostpointercapture': this.lostPointerCapture
-  };
-  goog.base(this, dispatcher, mapping);
+goog.math.Rect.boundingRect = function(a, b) {
+  if (!a || !b) {
+    return null;
+  }
+
+  var clone = a.clone();
+  clone.boundingRect(b);
+
+  return clone;
 };
-goog.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
 
 
 /**
- * Handler for `pointerdown`.
+ * Tests whether this rectangle entirely contains another rectangle or
+ * coordinate.
  *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or
+ *     coordinate to test for containment.
+ * @return {boolean} Whether this rectangle contains given rectangle or
+ *     coordinate.
  */
-ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.contains = function(another) {
+  if (another instanceof goog.math.Rect) {
+    return this.left <= another.left &&
+           this.left + this.width >= another.left + another.width &&
+           this.top <= another.top &&
+           this.top + this.height >= another.top + another.height;
+  } else { // (another instanceof goog.math.Coordinate)
+    return another.x >= this.left &&
+           another.x <= this.left + this.width &&
+           another.y >= this.top &&
+           another.y <= this.top + this.height;
+  }
 };
 
 
 /**
- * Handler for `pointermove`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {!goog.math.Coordinate} point A coordinate.
+ * @return {number} The squared distance between the point and the closest
+ *     point inside the rectangle. Returns 0 if the point is inside the
+ *     rectangle.
  */
-ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.squaredDistance = function(point) {
+  var dx = point.x < this.left ?
+      this.left - point.x : Math.max(point.x - (this.left + this.width), 0);
+  var dy = point.y < this.top ?
+      this.top - point.y : Math.max(point.y - (this.top + this.height), 0);
+  return dx * dx + dy * dy;
 };
 
 
 /**
- * Handler for `pointerup`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {!goog.math.Coordinate} point A coordinate.
+ * @return {number} The distance between the point and the closest point
+ *     inside the rectangle. Returns 0 if the point is inside the rectangle.
  */
-ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.distance = function(point) {
+  return Math.sqrt(this.squaredDistance(point));
 };
 
 
 /**
- * Handler for `pointerout`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @return {!goog.math.Size} The size of this rectangle.
  */
-ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.getSize = function() {
+  return new goog.math.Size(this.width, this.height);
 };
 
 
 /**
- * Handler for `pointerover`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
+ *     the rectangle.
  */
-ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.getTopLeft = function() {
+  return new goog.math.Coordinate(this.left, this.top);
 };
 
 
 /**
- * Handler for `pointercancel`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @return {!goog.math.Coordinate} A new coordinate for the center of the
+ *     rectangle.
  */
-ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.getCenter = function() {
+  return new goog.math.Coordinate(
+      this.left + this.width / 2, this.top + this.height / 2);
 };
 
 
 /**
- * Handler for `lostpointercapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner
+ *     of the rectangle.
  */
-ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.getBottomRight = function() {
+  return new goog.math.Coordinate(
+      this.left + this.width, this.top + this.height);
 };
 
 
 /**
- * Handler for `gotpointercapture`.
- *
- * @param {goog.events.BrowserEvent} inEvent
+ * Rounds the fields to the next larger integer values.
+ * @return {!goog.math.Rect} This rectangle with ceil'd fields.
  */
-ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+goog.math.Rect.prototype.ceil = function() {
+  this.left = Math.ceil(this.left);
+  this.top = Math.ceil(this.top);
+  this.width = Math.ceil(this.width);
+  this.height = Math.ceil(this.height);
+  return this;
 };
 
-// Based on https://github.com/Polymer/PointerEvents
 
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+/**
+ * Rounds the fields to the next smaller integer values.
+ * @return {!goog.math.Rect} This rectangle with floored fields.
+ */
+goog.math.Rect.prototype.floor = function() {
+  this.left = Math.floor(this.left);
+  this.top = Math.floor(this.top);
+  this.width = Math.floor(this.width);
+  this.height = Math.floor(this.height);
+  return this;
+};
+
+
+/**
+ * Rounds the fields to nearest integer values.
+ * @return {!goog.math.Rect} This rectangle with rounded fields.
+ */
+goog.math.Rect.prototype.round = function() {
+  this.left = Math.round(this.left);
+  this.top = Math.round(this.top);
+  this.width = Math.round(this.width);
+  this.height = Math.round(this.height);
+  return this;
+};
+
+
+/**
+ * Translates this rectangle by the given offsets. If a
+ * {@code goog.math.Coordinate} is given, then the left and top values are
+ * translated by the coordinate's x and y values. Otherwise, top and left are
+ * translated by {@code tx} and {@code opt_ty} respectively.
+ * @param {number|goog.math.Coordinate} tx The value to translate left by or the
+ *     the coordinate to translate this rect by.
+ * @param {number=} opt_ty The value to translate top by.
+ * @return {!goog.math.Rect} This rectangle after translating.
+ */
+goog.math.Rect.prototype.translate = function(tx, opt_ty) {
+  if (tx instanceof goog.math.Coordinate) {
+    this.left += tx.x;
+    this.top += tx.y;
+  } else {
+    this.left += tx;
+    if (goog.isNumber(opt_ty)) {
+      this.top += opt_ty;
+    }
+  }
+  return this;
+};
+
+
+/**
+ * Scales this rectangle by the given scale factors. The left and width values
+ * are scaled by {@code sx} and the top and height values are scaled by
+ * {@code opt_sy}.  If {@code opt_sy} is not given, then all fields are scaled
+ * by {@code sx}.
+ * @param {number} sx The scale factor to use for the x dimension.
+ * @param {number=} opt_sy The scale factor to use for the y dimension.
+ * @return {!goog.math.Rect} This rectangle after scaling.
+ */
+goog.math.Rect.prototype.scale = function(sx, opt_sy) {
+  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+  this.left *= sx;
+  this.width *= sx;
+  this.top *= sy;
+  this.height *= sy;
+  return this;
+};
+
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
+// 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
 //
-// * 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.
+//      http://www.apache.org/licenses/LICENSE-2.0
 //
-// 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.
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-goog.provide('ol.pointer.PointerEvent');
+/**
+ * @fileoverview Utilities for element styles.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author eae@google.com (Emil A Eklund)
+ * @see ../demos/inline_block_quirks.html
+ * @see ../demos/inline_block_standards.html
+ * @see ../demos/style_viewport.html
+ */
 
+goog.provide('goog.style');
 
-goog.require('goog.events');
-goog.require('goog.events.Event');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.vendor');
+goog.require('goog.math.Box');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Rect');
+goog.require('goog.math.Size');
 goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.userAgent');
 
 
+/**
+ * @define {boolean} Whether we know at compile time that
+ *     getBoundingClientRect() is present and bug-free on the browser.
+ */
+goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false);
+
 
 /**
- * A class for pointer events.
+ * Sets a style value on an element.
  *
- * This class is used as an abstraction for mouse events,
- * touch events and even native pointer events.
+ * This function is not indended to patch issues in the browser's style
+ * handling, but to allow easy programmatic access to setting dash-separated
+ * style properties.  An example is setting a batch of properties from a data
+ * object without overwriting old styles.  When possible, use native APIs:
+ * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
+ * elem.style.cssText = 'property1: value1; property2: value2'.
  *
- * @constructor
- * @extends {goog.events.Event}
- * @param {string} type The type of the event to create.
- * @param {goog.events.BrowserEvent} browserEvent
- * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of
- *    initial event properties.
+ * @param {Element} element The element to change.
+ * @param {string|Object} style If a string, a style name. If an object, a hash
+ *     of style names to style values.
+ * @param {string|number|boolean=} opt_value If style was a string, then this
+ *     should be the value.
  */
-ol.pointer.PointerEvent = function(type, browserEvent, opt_eventDict) {
-  goog.base(this, type);
-
-  /**
-   * @const
-   * @type {goog.events.BrowserEvent}
-   */
-  this.browserEvent = browserEvent;
-
-  var eventDict = goog.isDef(opt_eventDict) ? opt_eventDict : {};
-
-  /**
-   * @type {number}
-   */
-  this.buttons = this.getButtons_(eventDict);
+goog.style.setStyle = function(element, style, opt_value) {
+  if (goog.isString(style)) {
+    goog.style.setStyle_(element, opt_value, style);
+  } else {
+    for (var key in style) {
+      goog.style.setStyle_(element, style[key], key);
+    }
+  }
+};
 
-  /**
-   * @type {number}
-   */
-  this.pressure = this.getPressure_(eventDict, this.buttons);
 
-  // MouseEvent related properties
+/**
+ * Sets a style value on an element, with parameters swapped to work with
+ * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
+ * necessary.
+ * @param {Element} element The element to change.
+ * @param {string|number|boolean|undefined} value Style value.
+ * @param {string} style Style name.
+ * @private
+ */
+goog.style.setStyle_ = function(element, value, style) {
+  var propertyName = goog.style.getVendorJsStyleName_(element, style);
 
-  /**
-   * @type {boolean}
-   */
-  this.bubbles = goog.object.get(eventDict, 'bubbles', false);
+  if (propertyName) {
+    element.style[propertyName] = value;
+  }
+};
 
-  /**
-   * @type {boolean}
-   */
-  this.cancelable = goog.object.get(eventDict, 'cancelable', false);
 
-  /**
-   * @type {Object}
-   */
-  this.view = goog.object.get(eventDict, 'view', null);
+/**
+ * Style name cache that stores previous property name lookups.
+ *
+ * This is used by setStyle to speed up property lookups, entries look like:
+ *   { StyleName: ActualPropertyName }
+ *
+ * @private {!Object<string, string>}
+ */
+goog.style.styleNameCache_ = {};
 
-  /**
-   * @type {number}
-   */
-  this.detail = goog.object.get(eventDict, 'detail', null);
 
-  /**
-   * @type {number}
-   */
-  this.screenX = goog.object.get(eventDict, 'screenX', 0);
+/**
+ * Returns the style property name in camel-case. If it does not exist and a
+ * vendor-specific version of the property does exist, then return the vendor-
+ * specific property name instead.
+ * @param {Element} element The element to change.
+ * @param {string} style Style name.
+ * @return {string} Vendor-specific style.
+ * @private
+ */
+goog.style.getVendorJsStyleName_ = function(element, style) {
+  var propertyName = goog.style.styleNameCache_[style];
+  if (!propertyName) {
+    var camelStyle = goog.string.toCamelCase(style);
+    propertyName = camelStyle;
 
-  /**
-   * @type {number}
-   */
-  this.screenY = goog.object.get(eventDict, 'screenY', 0);
+    if (element.style[camelStyle] === undefined) {
+      var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
+          goog.string.toTitleCase(camelStyle);
 
-  /**
-   * @type {number}
-   */
-  this.clientX = goog.object.get(eventDict, 'clientX', 0);
+      if (element.style[prefixedStyle] !== undefined) {
+        propertyName = prefixedStyle;
+      }
+    }
+    goog.style.styleNameCache_[style] = propertyName;
+  }
 
-  /**
-   * @type {number}
-   */
-  this.clientY = goog.object.get(eventDict, 'clientY', 0);
+  return propertyName;
+};
 
-  /**
-   * @type {boolean}
-   */
-  this.ctrlKey = goog.object.get(eventDict, 'ctrlKey', false);
 
-  /**
-   * @type {boolean}
-   */
-  this.altKey = goog.object.get(eventDict, 'altKey', false);
+/**
+ * Returns the style property name in CSS notation. If it does not exist and a
+ * vendor-specific version of the property does exist, then return the vendor-
+ * specific property name instead.
+ * @param {Element} element The element to change.
+ * @param {string} style Style name.
+ * @return {string} Vendor-specific style.
+ * @private
+ */
+goog.style.getVendorStyleName_ = function(element, style) {
+  var camelStyle = goog.string.toCamelCase(style);
 
-  /**
-   * @type {boolean}
-   */
-  this.shiftKey = goog.object.get(eventDict, 'shiftKey', false);
+  if (element.style[camelStyle] === undefined) {
+    var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
+        goog.string.toTitleCase(camelStyle);
 
-  /**
-   * @type {boolean}
-   */
-  this.metaKey = goog.object.get(eventDict, 'metaKey', false);
+    if (element.style[prefixedStyle] !== undefined) {
+      return goog.dom.vendor.getVendorPrefix() + '-' + style;
+    }
+  }
 
-  /**
-   * @type {number}
-   */
-  this.button = goog.object.get(eventDict, 'button', 0);
+  return style;
+};
 
-  /**
-   * @type {Node}
-   */
-  this.relatedTarget = goog.object.get(eventDict, 'relatedTarget', null);
 
-  // PointerEvent related properties
-
-  /**
-   * @const
-   * @type {number}
-   */
-  this.pointerId = goog.object.get(eventDict, 'pointerId', 0);
-
-  /**
-   * @type {number}
-   */
-  this.width = goog.object.get(eventDict, 'width', 0);
-
-  /**
-   * @type {number}
-   */
-  this.height = goog.object.get(eventDict, 'height', 0);
-
-  /**
-   * @type {number}
-   */
-  this.tiltX = goog.object.get(eventDict, 'tiltX', 0);
-
-  /**
-   * @type {number}
-   */
-  this.tiltY = goog.object.get(eventDict, 'tiltY', 0);
-
-  /**
-   * @type {string}
-   */
-  this.pointerType = goog.object.get(eventDict, 'pointerType', '');
-
-  /**
-   * @type {number}
-   */
-  this.hwTimestamp = goog.object.get(eventDict, 'hwTimestamp', 0);
-
-  /**
-   * @type {boolean}
-   */
-  this.isPrimary = goog.object.get(eventDict, 'isPrimary', false);
+/**
+ * Retrieves an explicitly-set style value of a node. This returns '' if there
+ * isn't a style attribute on the element or if this style property has not been
+ * explicitly set in script.
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} property Property to get, css-style (if you have a camel-case
+ * property, use element.style[style]).
+ * @return {string} Style value.
+ */
+goog.style.getStyle = function(element, property) {
+  // element.style is '' for well-known properties which are unset.
+  // For for browser specific styles as 'filter' is undefined
+  // so we need to return '' explicitly to make it consistent across
+  // browsers.
+  var styleValue = element.style[goog.string.toCamelCase(property)];
 
-  // keep the semantics of preventDefault
-  if (browserEvent.preventDefault) {
-    this.preventDefault = function() {
-      browserEvent.preventDefault();
-    };
+  // Using typeof here because of a bug in Safari 5.1, where this value
+  // was undefined, but === undefined returned false.
+  if (typeof(styleValue) !== 'undefined') {
+    return styleValue;
   }
+
+  return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
+      '';
 };
-goog.inherits(ol.pointer.PointerEvent, goog.events.Event);
 
 
 /**
- * @private
- * @param {Object.<string, ?>} eventDict
- * @return {number}
+ * Retrieves a computed style value of a node. It returns empty string if the
+ * value cannot be computed (which will be the case in Internet Explorer) or
+ * "none" if the property requested is an SVG one and it has not been
+ * explicitly set (firefox and webkit).
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} property Property to get (camel-case).
+ * @return {string} Style value.
  */
-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;
+goog.style.getComputedStyle = function(element, property) {
+  var doc = goog.dom.getOwnerDocument(element);
+  if (doc.defaultView && doc.defaultView.getComputedStyle) {
+    var styles = doc.defaultView.getComputedStyle(element, null);
+    if (styles) {
+      // element.style[..] is undefined for browser specific styles
+      // as 'filter'.
+      return styles[property] || styles.getPropertyValue(property) || '';
     }
   }
-  return buttons;
-};
 
-
-/**
- * @private
- * @param {Object.<string, ?>} eventDict
- * @param {number} buttons
- * @return {number}
- */
-ol.pointer.PointerEvent.prototype.getPressure_ = function(eventDict, buttons) {
-  // Spec requires that pointers without pressure specified use 0.5 for down
-  // state and 0 for up state.
-  var pressure = 0;
-  if (eventDict.pressure) {
-    pressure = eventDict.pressure;
-  } else {
-    pressure = buttons ? 0.5 : 0;
-  }
-  return pressure;
+  return '';
 };
 
 
 /**
- * Is the `buttons` property supported?
- * @type {boolean}
+ * Gets the cascaded style value of a node, or null if the value cannot be
+ * computed (only Internet Explorer can do this).
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} style Property to get (camel-case).
+ * @return {string} Style value.
  */
-ol.pointer.PointerEvent.HAS_BUTTONS = false;
+goog.style.getCascadedStyle = function(element, style) {
+  // TODO(nicksantos): This should be documented to return null. #fixTypes
+  return element.currentStyle ? element.currentStyle[style] : null;
+};
 
 
 /**
- * Checks if the `buttons` property is supported.
+ * Cross-browser pseudo get computed style. It returns the computed style where
+ * available. If not available it tries the cascaded style value (IE
+ * currentStyle) and in worst case the inline style value.  It shouldn't be
+ * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
+ * discussion.
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} style Property to get (must be camelCase, not css-style.).
+ * @return {string} Style value.
+ * @private
  */
-(function() {
-  try {
-    var ev = new MouseEvent('click', {buttons: 1});
-    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
-  } catch (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.TouchSource');
-
-goog.require('goog.array');
-goog.require('goog.object');
-goog.require('ol.pointer.EventSource');
-goog.require('ol.pointer.MouseSource');
-
+goog.style.getStyle_ = function(element, style) {
+  return goog.style.getComputedStyle(element, style) ||
+         goog.style.getCascadedStyle(element, style) ||
+         (element.style && element.style[style]);
+};
 
 
 /**
- * @constructor
- * @param {ol.pointer.PointerEventHandler} dispatcher
- * @param {ol.pointer.MouseSource} mouseSource
- * @extends {ol.pointer.EventSource}
+ * Retrieves the computed value of the box-sizing CSS attribute.
+ * Browser support: http://caniuse.com/css3-boxsizing.
+ * @param {!Element} element The element whose box-sizing to get.
+ * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
+ *     box-sizing is not supported (IE7 and below).
  */
-ol.pointer.TouchSource = function(dispatcher, mouseSource) {
-  var mapping = {
-    'touchstart': this.touchstart,
-    'touchmove': this.touchmove,
-    'touchend': this.touchend,
-    'touchcancel': this.touchcancel
-  };
-  goog.base(this, dispatcher, mapping);
-
-  /**
-   * @const
-   * @type {Object.<string, goog.events.BrowserEvent|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
-
-  /**
-   * @const
-   * @type {ol.pointer.MouseSource}
-   */
-  this.mouseSource = mouseSource;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.firstTouchId_ = undefined;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.clickCount_ = 0;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.resetId_ = undefined;
+goog.style.getComputedBoxSizing = function(element) {
+  return goog.style.getStyle_(element, 'boxSizing') ||
+      goog.style.getStyle_(element, 'MozBoxSizing') ||
+      goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
 };
-goog.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);
 
 
 /**
- * Mouse event timeout: This should be long enough to
- * ignore compat mouse events made by touch.
- * @const
- * @type {number}
+ * Retrieves the computed value of the position CSS attribute.
+ * @param {Element} element The element to get the position of.
+ * @return {string} Position value.
  */
-ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
+goog.style.getComputedPosition = function(element) {
+  return goog.style.getStyle_(element, 'position');
+};
 
 
 /**
- * @const
- * @type {number}
+ * Retrieves the computed background color string for a given element. The
+ * string returned is suitable for assigning to another element's
+ * background-color, but is not guaranteed to be in any particular string
+ * format. Accessing the color in a numeric form may not be possible in all
+ * browsers or with all input.
+ *
+ * If the background color for the element is defined as a hexadecimal value,
+ * the resulting string can be parsed by goog.color.parse in all supported
+ * browsers.
+ *
+ * Whether named colors like "red" or "lightblue" get translated into a
+ * format which can be parsed is browser dependent. Calling this function on
+ * transparent elements will return "transparent" in most browsers or
+ * "rgba(0, 0, 0, 0)" in WebKit.
+ * @param {Element} element The element to get the background color of.
+ * @return {string} The computed string value of the background color.
  */
-ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
+goog.style.getBackgroundColor = function(element) {
+  return goog.style.getStyle_(element, 'backgroundColor');
+};
 
 
 /**
- * @const
- * @type {string}
+ * Retrieves the computed value of the overflow-x CSS attribute.
+ * @param {Element} element The element to get the overflow-x of.
+ * @return {string} The computed string value of the overflow-x attribute.
  */
-ol.pointer.TouchSource.POINTER_TYPE = 'touch';
+goog.style.getComputedOverflowX = function(element) {
+  return goog.style.getStyle_(element, 'overflowX');
+};
 
 
 /**
- * @private
- * @param {Touch} inTouch
- * @return {boolean} True, if this is the primary touch.
+ * Retrieves the computed value of the overflow-y CSS attribute.
+ * @param {Element} element The element to get the overflow-y of.
+ * @return {string} The computed string value of the overflow-y attribute.
  */
-ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
-  return this.firstTouchId_ === inTouch.identifier;
+goog.style.getComputedOverflowY = function(element) {
+  return goog.style.getStyle_(element, 'overflowY');
 };
 
 
 /**
- * Set primary touch if there are no pointers, or the only pointer is the mouse.
- * @param {Touch} inTouch
- * @private
+ * Retrieves the computed value of the z-index CSS attribute.
+ * @param {Element} element The element to get the z-index of.
+ * @return {string|number} The computed value of the z-index attribute.
  */
-ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) {
-  var count = goog.object.getCount(this.pointerMap);
-  if (count === 0 || (count === 1 && goog.object.containsKey(this.pointerMap,
-      ol.pointer.MouseSource.POINTER_ID.toString()))) {
-    this.firstTouchId_ = inTouch.identifier;
-    this.cancelResetClickCount_();
-  }
+goog.style.getComputedZIndex = function(element) {
+  return goog.style.getStyle_(element, 'zIndex');
 };
 
 
 /**
- * @private
- * @param {Object} inPointer
+ * Retrieves the computed value of the text-align CSS attribute.
+ * @param {Element} element The element to get the text-align of.
+ * @return {string} The computed string value of the text-align attribute.
  */
-ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
-  if (inPointer.isPrimary) {
-    this.firstTouchId_ = undefined;
-    this.resetClickCount_();
-  }
+goog.style.getComputedTextAlign = function(element) {
+  return goog.style.getStyle_(element, 'textAlign');
 };
 
 
 /**
- * @private
+ * Retrieves the computed value of the cursor CSS attribute.
+ * @param {Element} element The element to get the cursor of.
+ * @return {string} The computed string value of the cursor attribute.
  */
-ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
-  this.resetId_ = goog.global.setTimeout(
-      goog.bind(this.resetClickCountHandler_, this),
-      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
+goog.style.getComputedCursor = function(element) {
+  return goog.style.getStyle_(element, 'cursor');
 };
 
 
 /**
- * @private
+ * Retrieves the computed value of the CSS transform attribute.
+ * @param {Element} element The element to get the transform of.
+ * @return {string} The computed string representation of the transform matrix.
  */
-ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
-  this.clickCount_ = 0;
-  this.resetId_ = undefined;
+goog.style.getComputedTransform = function(element) {
+  var property = goog.style.getVendorStyleName_(element, 'transform');
+  return goog.style.getStyle_(element, property) ||
+      goog.style.getStyle_(element, 'transform');
 };
 
 
 /**
- * @private
+ * Sets the top/left values of an element.  If no unit is specified in the
+ * argument then it will add px. The second argument is required if the first
+ * argument is a string or number and is ignored if the first argument
+ * is a coordinate.
+ * @param {Element} el Element to move.
+ * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
+ * @param {string|number=} opt_arg2 Top position.
  */
-ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
-  if (goog.isDef(this.resetId_)) {
-    goog.global.clearTimeout(this.resetId_);
+goog.style.setPosition = function(el, arg1, opt_arg2) {
+  var x, y;
+  var buggyGeckoSubPixelPos = goog.userAgent.GECKO &&
+      (goog.userAgent.MAC || goog.userAgent.X11) &&
+      goog.userAgent.isVersionOrHigher('1.9');
+
+  if (arg1 instanceof goog.math.Coordinate) {
+    x = arg1.x;
+    y = arg1.y;
+  } else {
+    x = arg1;
+    y = opt_arg2;
   }
+
+  // Round to the nearest pixel for buggy sub-pixel support.
+  el.style.left = goog.style.getPixelStyleValue_(
+      /** @type {number|string} */ (x), buggyGeckoSubPixelPos);
+  el.style.top = goog.style.getPixelStyleValue_(
+      /** @type {number|string} */ (y), buggyGeckoSubPixelPos);
 };
 
 
 /**
- * @private
- * @param {goog.events.BrowserEvent} browserEvent Browser event
- * @param {Touch} inTouch Touch event
- * @return {Object}
+ * Gets the offsetLeft and offsetTop properties of an element and returns them
+ * in a Coordinate object
+ * @param {Element} element Element.
+ * @return {!goog.math.Coordinate} The position.
  */
-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 neccessary?
-  //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;
+goog.style.getPosition = function(element) {
+  return new goog.math.Coordinate(element.offsetLeft, element.offsetTop);
 };
 
 
 /**
- * @private
- * @param {goog.events.BrowserEvent} inEvent Touch event
- * @param {function(goog.events.BrowserEvent, Object)} inFunction
+ * Returns the viewport element for a particular document
+ * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
+ *     of.
+ * @return {Element} document.documentElement or document.body.
  */
-ol.pointer.TouchSource.prototype.processTouches_ =
-    function(inEvent, inFunction) {
-  var touches = Array.prototype.slice.call(
-      inEvent.getBrowserEvent().changedTouches);
-  var count = touches.length;
-  function preventDefault() {
-    inEvent.preventDefault();
+goog.style.getClientViewportElement = function(opt_node) {
+  var doc;
+  if (opt_node) {
+    doc = goog.dom.getOwnerDocument(opt_node);
+  } else {
+    doc = goog.dom.getDocument();
   }
-  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);
+
+  // In old IE versions the document.body represented the viewport
+  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
+      !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
+    return doc.body;
   }
+  return doc.documentElement;
 };
 
 
 /**
- * @private
- * @param {TouchList} touchList
- * @param {number} searchId
- * @return {boolean} True, if the `Touch` with the given id is in the list.
+ * Calculates the viewport coordinates relative to the page/document
+ * containing the node. The viewport may be the browser viewport for
+ * non-iframe document, or the iframe container for iframe'd document.
+ * @param {!Document} doc The document to use as the reference point.
+ * @return {!goog.math.Coordinate} The page offset of the viewport.
  */
-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;
+goog.style.getViewportPageOffset = function(doc) {
+  var body = doc.body;
+  var documentElement = doc.documentElement;
+  var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
+  var scrollTop = body.scrollTop || documentElement.scrollTop;
+  return new goog.math.Coordinate(scrollLeft, scrollTop);
 };
 
 
 /**
- * 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
+ * Gets the client rectangle of the DOM element.
+ *
+ * getBoundingClientRect is part of a new CSS object model draft (with a
+ * long-time presence in IE), replacing the error-prone parent offset
+ * computation and the now-deprecated Gecko getBoxObjectFor.
+ *
+ * This utility patches common browser bugs in getBoundingClientRect. It
+ * will fail if getBoundingClientRect is unsupported.
  *
+ * If the element is not in the DOM, the result is undefined, and an error may
+ * be thrown depending on user agent.
+ *
+ * @param {!Element} el The element whose bounding rectangle is being queried.
+ * @return {Object} A native bounding rectangle with numerical left, top,
+ *     right, and bottom.  Reported by Firefox to be of object type ClientRect.
  * @private
- * @param {goog.events.BrowserEvent} inEvent
  */
-ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) {
-  var touchList = inEvent.getBrowserEvent().touches;
-  // pointerMap.getCount() should be < touchList.length here,
-  // as the touchstart has not been processed yet.
-  var keys = goog.object.getKeys(this.pointerMap);
-  var count = keys.length;
-  if (count >= touchList.length) {
-    var d = [];
-    var i, key, value;
-    for (i = 0; i < count; ++i) {
-      key = keys[i];
-      value = this.pointerMap[key];
-      // Never remove pointerId == 1, which is mouse.
-      // Touch identifiers are 2 smaller than their pointerId, which is the
-      // index in pointermap.
-      if (key != ol.pointer.MouseSource.POINTER_ID &&
-          !this.findTouch_(touchList, key - 2)) {
-        d.push(value.out);
-      }
-    }
-    for (i = 0; i < d.length; ++i) {
-      this.cancelOut_(inEvent, d[i]);
-    }
+goog.style.getBoundingClientRect_ = function(el) {
+  var rect;
+  try {
+    rect = el.getBoundingClientRect();
+  } catch (e) {
+    // In IE < 9, calling getBoundingClientRect on an orphan element raises an
+    // "Unspecified Error". All other browsers return zeros.
+    return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
   }
-};
-
-
-/**
- * Handler for `touchstart`, triggers `pointerover`,
- * `pointerenter` and `pointerdown` events.
- *
- * @param {goog.events.BrowserEvent} inEvent
- */
-ol.pointer.TouchSource.prototype.touchstart = function(inEvent) {
-  this.vacuumTouches_(inEvent);
-  this.setPrimaryTouch_(inEvent.getBrowserEvent().changedTouches[0]);
-  this.dedupSynthMouse_(inEvent);
-  this.clickCount_++;
-  this.processTouches_(inEvent, this.overDown_);
-};
 
+  // Patch the result in IE only, so that this function can be inlined if
+  // compiled for non-IE.
+  if (goog.userAgent.IE && el.ownerDocument.body) {
 
-/**
- * @private
- * @param {goog.events.BrowserEvent} browserEvent
- * @param {Object} inPointer
- */
-ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) {
-  goog.object.set(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);
-};
+    // In IE, most of the time, 2 extra pixels are added to the top and left
+    // due to the implicit 2-pixel inset border.  In IE6/7 quirks mode and
+    // IE6 standards mode, this border can be overridden by setting the
+    // document element's border to zero -- thus, we cannot rely on the
+    // offset always being 2 pixels.
 
+    // In quirks mode, the offset can be determined by querying the body's
+    // clientLeft/clientTop, but in standards mode, it is found by querying
+    // the document element's clientLeft/clientTop.  Since we already called
+    // getBoundingClientRect we have already forced a reflow, so it is not
+    // too expensive just to query them all.
 
-/**
- * Handler for `touchmove`.
- *
- * @param {goog.events.BrowserEvent} inEvent
- */
-ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
-  inEvent.preventDefault();
-  this.processTouches_(inEvent, this.moveOverOut_);
+    // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
+    var doc = el.ownerDocument;
+    rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
+    rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
+  }
+  return /** @type {Object} */ (rect);
 };
 
 
 /**
- * @private
- * @param {goog.events.BrowserEvent} browserEvent
- * @param {Object} inPointer
+ * Returns the first parent that could affect the position of a given element.
+ * @param {Element} element The element to get the offset parent for.
+ * @return {Element} The first offset parent or null if one cannot be found.
  */
-ol.pointer.TouchSource.prototype.moveOverOut_ =
-    function(browserEvent, inPointer) {
-  var event = inPointer;
-  var pointer = goog.object.get(this.pointerMap, event.pointerId);
-  // a finger drifted off the screen, ignore it
-  if (!pointer) {
-    return;
+goog.style.getOffsetParent = function(element) {
+  // element.offsetParent does the right thing in IE7 and below.  In other
+  // browsers it only includes elements with position absolute, relative or
+  // fixed, not elements with overflow set to auto or scroll.
+  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
+    return element.offsetParent;
   }
-  var 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);
+
+  var doc = goog.dom.getOwnerDocument(element);
+  var positionStyle = goog.style.getStyle_(element, 'position');
+  var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
+  for (var parent = element.parentNode; parent && parent != doc;
+       parent = parent.parentNode) {
+    positionStyle =
+        goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
+    skipStatic = skipStatic && positionStyle == 'static' &&
+                 parent != doc.documentElement && parent != doc.body;
+    if (!skipStatic && (parent.scrollWidth > parent.clientWidth ||
+                        parent.scrollHeight > parent.clientHeight ||
+                        positionStyle == 'fixed' ||
+                        positionStyle == 'absolute' ||
+                        positionStyle == 'relative')) {
+      return /** @type {!Element} */ (parent);
     }
   }
-  pointer.out = event;
-  pointer.outTarget = event.target;
+  return null;
 };
 
 
 /**
- * Handler for `touchend`, triggers `pointerup`,
- * `pointerout` and `pointerleave` events.
+ * Calculates and returns the visible rectangle for a given element. Returns a
+ * box describing the visible portion of the nearest scrollable offset ancestor.
+ * Coordinates are given relative to the document.
  *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Element} element Element to get the visible rect for.
+ * @return {goog.math.Box} Bounding elementBox describing the visible rect or
+ *     null if scrollable ancestor isn't inside the visible viewport.
  */
-ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
-  this.dedupSynthMouse_(inEvent);
-  this.processTouches_(inEvent, this.upOut_);
-};
+goog.style.getVisibleRectForElement = function(element) {
+  var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
+  var dom = goog.dom.getDomHelper(element);
+  var body = dom.getDocument().body;
+  var documentElement = dom.getDocument().documentElement;
+  var scrollEl = dom.getDocumentScrollElement();
 
+  // Determine the size of the visible rect by climbing the dom accounting for
+  // all scrollable containers.
+  for (var el = element; el = goog.style.getOffsetParent(el); ) {
+    // clientWidth is zero for inline block elements in IE.
+    // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
+    if ((!goog.userAgent.IE || el.clientWidth != 0) &&
+        (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
+        // body may have overflow set on it, yet we still get the entire
+        // viewport. In some browsers, el.offsetParent may be
+        // document.documentElement, so check for that too.
+        (el != body && el != documentElement &&
+            goog.style.getStyle_(el, 'overflow') != 'visible')) {
+      var pos = goog.style.getPageOffset(el);
+      var client = goog.style.getClientLeftTop(el);
+      pos.x += client.x;
+      pos.y += client.y;
 
-/**
- * @private
- * @param {goog.events.BrowserEvent} browserEvent
- * @param {Object} inPointer
- */
-ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) {
-  this.dispatcher.up(inPointer, browserEvent);
-  this.dispatcher.out(inPointer, browserEvent);
-  this.dispatcher.leave(inPointer, browserEvent);
-  this.cleanUpPointer_(inPointer);
+      visibleRect.top = Math.max(visibleRect.top, pos.y);
+      visibleRect.right = Math.min(visibleRect.right,
+                                   pos.x + el.clientWidth);
+      visibleRect.bottom = Math.min(visibleRect.bottom,
+                                    pos.y + el.clientHeight);
+      visibleRect.left = Math.max(visibleRect.left, pos.x);
+    }
+  }
+
+  // Clip by window's viewport.
+  var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
+  visibleRect.left = Math.max(visibleRect.left, scrollX);
+  visibleRect.top = Math.max(visibleRect.top, scrollY);
+  var winSize = dom.getViewportSize();
+  visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
+  visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
+  return visibleRect.top >= 0 && visibleRect.left >= 0 &&
+         visibleRect.bottom > visibleRect.top &&
+         visibleRect.right > visibleRect.left ?
+         visibleRect : null;
 };
 
 
 /**
- * Handler for `touchcancel`, triggers `pointercancel`,
- * `pointerout` and `pointerleave` events.
+ * Calculate the scroll position of {@code container} with the minimum amount so
+ * that the content and the borders of the given {@code element} become visible.
+ * If the element is bigger than the container, its top left corner will be
+ * aligned as close to the container's top left corner as possible.
  *
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Element} element The element to make visible.
+ * @param {Element} container The container to scroll.
+ * @param {boolean=} opt_center Whether to center the element in the container.
+ *     Defaults to false.
+ * @return {!goog.math.Coordinate} The new scroll position of the container,
+ *     in form of goog.math.Coordinate(scrollLeft, scrollTop).
  */
-ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
-  this.processTouches_(inEvent, this.cancelOut_);
+goog.style.getContainerOffsetToScrollInto =
+    function(element, container, opt_center) {
+  // Absolute position of the element's border's top left corner.
+  var elementPos = goog.style.getPageOffset(element);
+  // Absolute position of the container's border's top left corner.
+  var containerPos = goog.style.getPageOffset(container);
+  var containerBorder = goog.style.getBorderBox(container);
+  // Relative pos. of the element's border box to the container's content box.
+  var relX = elementPos.x - containerPos.x - containerBorder.left;
+  var relY = elementPos.y - containerPos.y - containerBorder.top;
+  // How much the element can move in the container, i.e. the difference between
+  // the element's bottom-right-most and top-left-most position where it's
+  // fully visible.
+  var spaceX = container.clientWidth - element.offsetWidth;
+  var spaceY = container.clientHeight - element.offsetHeight;
+
+  var scrollLeft = container.scrollLeft;
+  var scrollTop = container.scrollTop;
+  if (opt_center) {
+    // All browsers round non-integer scroll positions down.
+    scrollLeft += relX - spaceX / 2;
+    scrollTop += relY - spaceY / 2;
+  } else {
+    // This formula was designed to give the correct scroll values in the
+    // following cases:
+    // - element is higher than container (spaceY < 0) => scroll down by relY
+    // - element is not higher that container (spaceY >= 0):
+    //   - it is above container (relY < 0) => scroll up by abs(relY)
+    //   - it is below container (relY > spaceY) => scroll down by relY - spaceY
+    //   - it is in the container => don't scroll
+    scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
+    scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
+  }
+  return new goog.math.Coordinate(scrollLeft, scrollTop);
 };
 
 
 /**
- * @private
- * @param {goog.events.BrowserEvent} browserEvent
- * @param {Object} inPointer
+ * Changes the scroll position of {@code container} with the minimum amount so
+ * that the content and the borders of the given {@code element} become visible.
+ * If the element is bigger than the container, its top left corner will be
+ * aligned as close to the container's top left corner as possible.
+ *
+ * @param {Element} element The element to make visible.
+ * @param {Element} container The container to scroll.
+ * @param {boolean=} opt_center Whether to center the element in the container.
+ *     Defaults to false.
  */
-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);
+goog.style.scrollIntoContainerView = function(element, container, opt_center) {
+  var offset =
+      goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
+  container.scrollLeft = offset.x;
+  container.scrollTop = offset.y;
 };
 
 
 /**
- * @private
- * @param {Object} inPointer
+ * Returns clientLeft (width of the left border and, if the directionality is
+ * right to left, the vertical scrollbar) and clientTop as a coordinate object.
+ *
+ * @param {Element} el Element to get clientLeft for.
+ * @return {!goog.math.Coordinate} Client left and top.
  */
-ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
-  goog.object.remove(this.pointerMap, inPointer.pointerId);
-  this.removePrimaryPointer_(inPointer);
+goog.style.getClientLeftTop = function(el) {
+  // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=111207
+  if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) {
+    var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth'));
+    if (goog.style.isRightToLeft(el)) {
+      var scrollbarWidth = el.offsetWidth - el.clientWidth - left -
+          parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth'));
+      left += scrollbarWidth;
+    }
+    return new goog.math.Coordinate(left,
+        parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth')));
+  }
+
+  return new goog.math.Coordinate(el.clientLeft, el.clientTop);
 };
 
 
 /**
- * Prevent synth mouse events from creating pointer events.
+ * Returns a Coordinate object relative to the top-left of the HTML document.
+ * Implemented as a single function to save having to do two recursive loops in
+ * opera and safari just to get both coordinates.  If you just want one value do
+ * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
+ * note if you call both those methods the tree will be analysed twice.
  *
- * @private
- * @param {goog.events.BrowserEvent} inEvent
+ * @param {Element} el Element to get the page offset for.
+ * @return {!goog.math.Coordinate} The page offset.
  */
-ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) {
-  var lts = this.mouseSource.lastTouches;
-  var t = inEvent.getBrowserEvent().changedTouches[0];
-  // only the primary finger will synth mouse events
-  if (this.isPrimaryTouch_(t)) {
-    // remember x/y of last touch
-    var lt = /** @type {ol.Pixel} */ ([t.clientX, t.clientY]);
-    lts.push(lt);
-
-    goog.global.setTimeout(function() {
-      // remove touch after timeout
-      goog.array.remove(lts, lt);
-    }, ol.pointer.TouchSource.DEDUP_TIMEOUT);
-  }
-};
+goog.style.getPageOffset = function(el) {
+  var box, doc = goog.dom.getOwnerDocument(el);
+  var positionStyle = goog.style.getStyle_(el, 'position');
+  // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
+  goog.asserts.assertObject(el, 'Parameter is required');
 
-// Based on https://github.com/Polymer/PointerEvents
+  // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the
+  // position. When invoked for an element with position absolute and a negative
+  // position though it can be off by one. Therefor the recursive implementation
+  // is used in those (relatively rare) cases.
+  var BUGGY_GECKO_BOX_OBJECT =
+      !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS &&
+      goog.userAgent.GECKO && doc.getBoxObjectFor &&
+      !el.getBoundingClientRect && positionStyle == 'absolute' &&
+      (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0);
 
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  // NOTE(arv): If element is hidden (display none or disconnected or any the
+  // ancestors are hidden) we get (0,0) by default but we still do the
+  // accumulation of scroll position.
 
-goog.provide('ol.pointer.PointerEventHandler');
+  // TODO(arv): Should we check if the node is disconnected and in that case
+  //            return (0,0)?
 
-goog.require('goog.array');
-goog.require('goog.events');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventTarget');
+  var pos = new goog.math.Coordinate(0, 0);
+  var viewportElement = goog.style.getClientViewportElement(doc);
+  if (el == viewportElement) {
+    // viewport is always at 0,0 as that defined the coordinate system for this
+    // function - this avoids special case checks in the code below
+    return pos;
+  }
 
-goog.require('ol.has');
-goog.require('ol.pointer.MouseSource');
-goog.require('ol.pointer.MsSource');
-goog.require('ol.pointer.NativeSource');
-goog.require('ol.pointer.PointerEvent');
-goog.require('ol.pointer.TouchSource');
+  // IE, Gecko 1.9+, and most modern WebKit.
+  if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
+      el.getBoundingClientRect) {
+    box = goog.style.getBoundingClientRect_(el);
+    // Must add the scroll coordinates in to get the absolute page offset
+    // of element since getBoundingClientRect returns relative coordinates to
+    // the viewport.
+    var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
+    pos.x = box.left + scrollCoord.x;
+    pos.y = box.top + scrollCoord.y;
 
+  // Gecko prior to 1.9.
+  } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) {
+    // Gecko ignores the scroll values for ancestors, up to 1.9.  See:
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
 
+    box = doc.getBoxObjectFor(el);
+    // TODO(user): Fix the off-by-one error when window is scrolled down
+    // or right more than 1 pixel. The viewport offset does not move in lock
+    // step with the window scroll; it moves in increments of 2px and at
+    // somewhat random intervals.
+    var vpBox = doc.getBoxObjectFor(viewportElement);
+    pos.x = box.screenX - vpBox.screenX;
+    pos.y = box.screenY - vpBox.screenY;
 
-/**
- * @constructor
- * @extends {goog.events.EventTarget}
- * @param {Element|HTMLDocument} element Viewport element.
- */
-ol.pointer.PointerEventHandler = function(element) {
-  goog.base(this);
+  // Safari, Opera and Camino up to 1.0.4.
+  } else {
+    var parent = el;
+    do {
+      pos.x += parent.offsetLeft;
+      pos.y += parent.offsetTop;
+      // For safari/chrome, we need to add parent's clientLeft/Top as well.
+      if (parent != el) {
+        pos.x += parent.clientLeft || 0;
+        pos.y += parent.clientTop || 0;
+      }
+      // In Safari when hit a position fixed element the rest of the offsets
+      // are not correct.
+      if (goog.userAgent.WEBKIT &&
+          goog.style.getComputedPosition(parent) == 'fixed') {
+        pos.x += doc.body.scrollLeft;
+        pos.y += doc.body.scrollTop;
+        break;
+      }
+      parent = parent.offsetParent;
+    } while (parent && parent != el);
 
-  /**
-   * @const
-   * @private
-   * @type {Element|HTMLDocument}
-   */
-  this.element_ = element;
+    // Opera & (safari absolute) incorrectly account for body offsetTop.
+    if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT &&
+        positionStyle == 'absolute')) {
+      pos.y -= doc.body.offsetTop;
+    }
 
-  /**
-   * @const
-   * @type {Object.<string, goog.events.BrowserEvent|Object>}
-   */
-  this.pointerMap = {};
+    for (parent = el; (parent = goog.style.getOffsetParent(parent)) &&
+        parent != doc.body && parent != viewportElement; ) {
+      pos.x -= parent.scrollLeft;
+      // Workaround for a bug in Opera 9.2 (and earlier) where table rows may
+      // report an invalid scroll top value. The bug was fixed in Opera 9.5
+      // however as that version supports getBoundingClientRect it won't
+      // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965
+      if (!goog.userAgent.OPERA || parent.tagName != 'TR') {
+        pos.y -= parent.scrollTop;
+      }
+    }
+  }
 
-  /**
-   * @type {Object.<string, function(goog.events.BrowserEvent)>}
-   * @private
-   */
-  this.eventMap_ = {};
+  return pos;
+};
 
-  /**
-   * @type {Array.<ol.pointer.EventSource>}
-   * @private
-   */
-  this.eventSourceList_ = [];
 
-  this.registerSources();
+/**
+ * Returns the left coordinate of an element relative to the HTML document
+ * @param {Element} el Elements.
+ * @return {number} The left coordinate.
+ */
+goog.style.getPageOffsetLeft = function(el) {
+  return goog.style.getPageOffset(el).x;
 };
-goog.inherits(ol.pointer.PointerEventHandler, goog.events.EventTarget);
 
 
 /**
- * Set up the event sources (mouse, touch and native pointers)
- * that generate pointer events.
+ * Returns the top coordinate of an element relative to the HTML document
+ * @param {Element} el Elements.
+ * @return {number} The top coordinate.
  */
-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_();
-};
+goog.style.getPageOffsetTop = function(el) {
+  return goog.style.getPageOffset(el).y;
+};
 
 
 /**
- * Add a new event source that will generate pointer events.
+ * Returns a Coordinate object relative to the top-left of an HTML document
+ * in an ancestor frame of this element. Used for measuring the position of
+ * an element inside a frame relative to a containing frame.
  *
- * @param {string} name A name for the event source
- * @param {ol.pointer.EventSource} source
+ * @param {Element} el Element to get the page offset for.
+ * @param {Window} relativeWin The window to measure relative to. If relativeWin
+ *     is not in the ancestor frame chain of the element, we measure relative to
+ *     the top-most window.
+ * @return {!goog.math.Coordinate} The page offset.
  */
-ol.pointer.PointerEventHandler.prototype.registerSource =
-    function(name, source) {
-  var s = source;
-  var newEvents = s.getEvents();
+goog.style.getFramedPageOffset = function(el, relativeWin) {
+  var position = new goog.math.Coordinate(0, 0);
 
-  if (newEvents) {
-    goog.array.forEach(newEvents, function(e) {
-      var handler = s.getHandlerForEvent(e);
+  // Iterate up the ancestor frame chain, keeping track of the current window
+  // and the current element in that window.
+  var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
+  var currentEl = el;
+  do {
+    // if we're at the top window, we want to get the page offset.
+    // if we're at an inner frame, we only want to get the window position
+    // so that we can determine the actual page offset in the context of
+    // the outer window.
+    var offset = currentWin == relativeWin ?
+        goog.style.getPageOffset(currentEl) :
+        goog.style.getClientPositionForElement_(
+            goog.asserts.assert(currentEl));
 
-      if (handler) {
-        this.eventMap_[e] = goog.bind(handler, s);
-      }
-    }, this);
-    this.eventSourceList_.push(s);
-  }
+    position.x += offset.x;
+    position.y += offset.y;
+  } while (currentWin && currentWin != relativeWin &&
+      (currentEl = currentWin.frameElement) &&
+      (currentWin = currentWin.parent));
+
+  return position;
 };
 
 
 /**
- * Set up the events for all registered event sources.
- * @private
+ * Translates the specified rect relative to origBase page, for newBase page.
+ * If origBase and newBase are the same, this function does nothing.
+ *
+ * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
+ *     and it will have the translated result.
+ * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
+ * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
+ *     coordinate.  This must be a DOM for an ancestor frame of origBase
+ *     or the same as origBase.
  */
-ol.pointer.PointerEventHandler.prototype.register_ = function() {
-  var l = this.eventSourceList_.length;
-  var eventSource;
-  for (var i = 0; i < l; i++) {
-    eventSource = this.eventSourceList_[i];
-    this.addEvents_(eventSource.getEvents());
+goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
+  if (origBase.getDocument() != newBase.getDocument()) {
+    var body = origBase.getDocument().body;
+    var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
+
+    // Adjust Body's margin.
+    pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
+
+    if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
+        !origBase.isCss1CompatMode()) {
+      pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
+    }
+
+    rect.left += pos.x;
+    rect.top += pos.y;
   }
 };
 
 
 /**
- * Remove all registered events.
- * @private
+ * Returns the position of an element relative to another element in the
+ * document.  A relative to B
+ * @param {Element|Event|goog.events.Event} a Element or mouse event whose
+ *     position we're calculating.
+ * @param {Element|Event|goog.events.Event} b Element or mouse event position
+ *     is relative to.
+ * @return {!goog.math.Coordinate} The relative position.
  */
-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());
-  }
+goog.style.getRelativePosition = function(a, b) {
+  var ap = goog.style.getClientPosition(a);
+  var bp = goog.style.getClientPosition(b);
+  return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
 };
 
 
 /**
- * Calls the right handler for a new event.
+ * Returns the position of the event or the element's border box relative to
+ * the client viewport.
+ * @param {!Element} el Element whose position to get.
+ * @return {!goog.math.Coordinate} The position.
  * @private
- * @param {goog.events.BrowserEvent} inEvent Browser event.
  */
-ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
-  var type = inEvent.type;
-  var handler = this.eventMap_[type];
-  if (handler) {
-    handler(inEvent);
+goog.style.getClientPositionForElement_ = function(el) {
+  var pos;
+  if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
+      el.getBoundingClientRect) {
+    // IE, Gecko 1.9+, and most modern WebKit
+    var box = goog.style.getBoundingClientRect_(el);
+    pos = new goog.math.Coordinate(box.left, box.top);
+  } else {
+    var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll();
+    var pageCoord = goog.style.getPageOffset(el);
+    pos = new goog.math.Coordinate(
+        pageCoord.x - scrollCoord.x,
+        pageCoord.y - scrollCoord.y);
+  }
+
+  // Gecko below version 12 doesn't add CSS translation to the client position
+  // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do
+  // so manually.
+  if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) {
+    return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el));
+  } else {
+    return pos;
   }
 };
 
 
 /**
- * Setup listeners for the given events.
- * @private
- * @param {Array.<string>} events List of events.
+ * Returns the position of the event or the element's border box relative to
+ * the client viewport.
+ * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
+ * @return {!goog.math.Coordinate} The position.
  */
-ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
-  goog.array.forEach(events, function(eventName) {
-    goog.events.listen(this.element_, eventName,
-        this.eventHandler_, false, this);
-  }, this);
-};
+goog.style.getClientPosition = function(el) {
+  goog.asserts.assert(el);
+  if (el.nodeType == goog.dom.NodeType.ELEMENT) {
+    return goog.style.getClientPositionForElement_(
+        /** @type {!Element} */ (el));
+  } else {
+    var isAbstractedEvent = goog.isFunction(el.getBrowserEvent);
+    var be = /** @type {!goog.events.BrowserEvent} */ (el);
+    var targetEvent = el;
 
+    if (el.targetTouches && el.targetTouches.length) {
+      targetEvent = el.targetTouches[0];
+    } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches &&
+        be.getBrowserEvent().targetTouches.length) {
+      targetEvent = be.getBrowserEvent().targetTouches[0];
+    }
 
-/**
- * Unregister listeners for the given events.
- * @private
- * @param {Array.<string>} events List of events.
- */
-ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
-  goog.array.forEach(events, function(e) {
-    goog.events.unlisten(this.element_, e,
-        this.eventHandler_, false, this);
-  }, this);
+    return new goog.math.Coordinate(
+        targetEvent.clientX,
+        targetEvent.clientY);
+  }
 };
 
 
 /**
- * Returns a snapshot of inEvent, with writable properties.
- *
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @param {Event|Touch} inEvent An event that contains
- *    properties to copy.
- * @return {Object} An object containing shallow copies of
- *    `inEvent`'s properties.
+ * Moves an element to the given coordinates relative to the client viewport.
+ * @param {Element} el Absolutely positioned element to set page offset for.
+ *     It must be in the document.
+ * @param {number|goog.math.Coordinate} x Left position of the element's margin
+ *     box or a coordinate object.
+ * @param {number=} opt_y Top position of the element's margin box.
  */
-ol.pointer.PointerEventHandler.prototype.cloneEvent =
-    function(browserEvent, inEvent) {
-  var eventCopy = {}, p;
-  for (var i = 0, ii = ol.pointer.CLONE_PROPS.length; i < ii; i++) {
-    p = ol.pointer.CLONE_PROPS[i][0];
-    eventCopy[p] =
-        browserEvent[p] ||
-        inEvent[p] ||
-        ol.pointer.CLONE_PROPS[i][1];
+goog.style.setPageOffset = function(el, x, opt_y) {
+  // Get current pageoffset
+  var cur = goog.style.getPageOffset(el);
+
+  if (x instanceof goog.math.Coordinate) {
+    opt_y = x.y;
+    x = x.x;
   }
 
-  return eventCopy;
-};
+  // NOTE(arv): We cannot allow strings for x and y. We could but that would
+  // require us to manually transform between different units
 
+  // Work out deltas
+  var dx = x - cur.x;
+  var dy = opt_y - cur.y;
 
-// EVENTS
+  // Set position to current left/top + delta
+  goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy);
+};
 
 
 /**
- * Triggers a 'pointerdown' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Sets the width/height values of an element.  If an argument is numeric,
+ * or a goog.math.Size is passed, it is assumed to be pixels and will add
+ * 'px' after converting it to an integer in string form. (This just sets the
+ * CSS width and height properties so it might set content-box or border-box
+ * size depending on the box model the browser is using.)
+ *
+ * @param {Element} element Element to set the size of.
+ * @param {string|number|goog.math.Size} w Width of the element, or a
+ *     size object.
+ * @param {string|number=} opt_h Height of the element. Required if w is not a
+ *     size object.
  */
-ol.pointer.PointerEventHandler.prototype.down =
-    function(pointerEventData, browserEvent) {
-  this.fireEvent(ol.pointer.EventType.POINTERDOWN,
-      pointerEventData, browserEvent);
+goog.style.setSize = function(element, w, opt_h) {
+  var h;
+  if (w instanceof goog.math.Size) {
+    h = w.height;
+    w = w.width;
+  } else {
+    if (opt_h == undefined) {
+      throw Error('missing height argument');
+    }
+    h = opt_h;
+  }
+
+  goog.style.setWidth(element, /** @type {string|number} */ (w));
+  goog.style.setHeight(element, /** @type {string|number} */ (h));
 };
 
 
 /**
- * Triggers a 'pointermove' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Helper function to create a string to be set into a pixel-value style
+ * property of an element. Can round to the nearest integer value.
+ *
+ * @param {string|number} value The style value to be used. If a number,
+ *     'px' will be appended, otherwise the value will be applied directly.
+ * @param {boolean} round Whether to round the nearest integer (if property
+ *     is a number).
+ * @return {string} The string value for the property.
+ * @private
  */
-ol.pointer.PointerEventHandler.prototype.move =
-    function(pointerEventData, browserEvent) {
-  this.fireEvent(ol.pointer.EventType.POINTERMOVE,
-      pointerEventData, browserEvent);
+goog.style.getPixelStyleValue_ = function(value, round) {
+  if (typeof value == 'number') {
+    value = (round ? Math.round(value) : value) + 'px';
+  }
+
+  return value;
 };
 
 
 /**
- * Triggers a 'pointerup' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Set the height of an element.  Sets the element's style property.
+ * @param {Element} element Element to set the height of.
+ * @param {string|number} height The height value to set.  If a number, 'px'
+ *     will be appended, otherwise the value will be applied directly.
  */
-ol.pointer.PointerEventHandler.prototype.up =
-    function(pointerEventData, browserEvent) {
-  this.fireEvent(ol.pointer.EventType.POINTERUP,
-      pointerEventData, browserEvent);
+goog.style.setHeight = function(element, height) {
+  element.style.height = goog.style.getPixelStyleValue_(height, true);
 };
 
 
 /**
- * Triggers a 'pointerenter' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Set the width of an element.  Sets the element's style property.
+ * @param {Element} element Element to set the width of.
+ * @param {string|number} width The width value to set.  If a number, 'px'
+ *     will be appended, otherwise the value will be applied directly.
  */
-ol.pointer.PointerEventHandler.prototype.enter =
-    function(pointerEventData, browserEvent) {
-  pointerEventData.bubbles = false;
-  this.fireEvent(ol.pointer.EventType.POINTERENTER,
-      pointerEventData, browserEvent);
+goog.style.setWidth = function(element, width) {
+  element.style.width = goog.style.getPixelStyleValue_(width, true);
 };
 
 
 /**
- * Triggers a 'pointerleave' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Gets the height and width of an element, even if its display is none.
+ *
+ * Specifically, this returns the height and width of the border box,
+ * irrespective of the box model in effect.
+ *
+ * Note that this function does not take CSS transforms into account. Please see
+ * {@code goog.style.getTransformedSize}.
+ * @param {Element} element Element to get size of.
+ * @return {!goog.math.Size} Object with width/height properties.
  */
-ol.pointer.PointerEventHandler.prototype.leave =
-    function(pointerEventData, browserEvent) {
-  pointerEventData.bubbles = false;
-  this.fireEvent(ol.pointer.EventType.POINTERLEAVE,
-      pointerEventData, browserEvent);
+goog.style.getSize = function(element) {
+  return goog.style.evaluateWithTemporaryDisplay_(
+      goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
 };
 
 
 /**
- * Triggers a 'pointerover' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Call {@code fn} on {@code element} such that {@code element}'s dimensions are
+ * accurate when it's passed to {@code fn}.
+ * @param {function(!Element): T} fn Function to call with {@code element} as
+ *     an argument after temporarily changing {@code element}'s display such
+ *     that its dimensions are accurate.
+ * @param {!Element} element Element (which may have display none) to use as
+ *     argument to {@code fn}.
+ * @return {T} Value returned by calling {@code fn} with {@code element}.
+ * @template T
+ * @private
  */
-ol.pointer.PointerEventHandler.prototype.over =
-    function(pointerEventData, browserEvent) {
-  pointerEventData.bubbles = true;
-  this.fireEvent(ol.pointer.EventType.POINTEROVER,
-      pointerEventData, browserEvent);
-};
+goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
+  if (goog.style.getStyle_(element, 'display') != 'none') {
+    return fn(element);
+  }
 
+  var style = element.style;
+  var originalDisplay = style.display;
+  var originalVisibility = style.visibility;
+  var originalPosition = style.position;
 
-/**
- * Triggers a 'pointerout' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
- */
-ol.pointer.PointerEventHandler.prototype.out =
-    function(pointerEventData, browserEvent) {
-  pointerEventData.bubbles = true;
-  this.fireEvent(ol.pointer.EventType.POINTEROUT,
-      pointerEventData, browserEvent);
-};
+  style.visibility = 'hidden';
+  style.position = 'absolute';
+  style.display = 'inline';
 
+  var retVal = fn(element);
 
-/**
- * Triggers a 'pointercancel' event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
- */
-ol.pointer.PointerEventHandler.prototype.cancel =
-    function(pointerEventData, browserEvent) {
-  this.fireEvent(ol.pointer.EventType.POINTERCANCEL,
-      pointerEventData, browserEvent);
+  style.display = originalDisplay;
+  style.position = originalPosition;
+  style.visibility = originalVisibility;
+
+  return retVal;
 };
 
 
 /**
- * Triggers a combination of 'pointerout' and 'pointerleave' events.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Gets the height and width of an element when the display is not none.
+ * @param {Element} element Element to get size of.
+ * @return {!goog.math.Size} Object with width/height properties.
+ * @private
  */
-ol.pointer.PointerEventHandler.prototype.leaveOut =
-    function(pointerEventData, browserEvent) {
-  this.out(pointerEventData, browserEvent);
-  if (!this.contains_(
-      pointerEventData.target,
-      pointerEventData.relatedTarget)) {
-    this.leave(pointerEventData, browserEvent);
+goog.style.getSizeWithDisplay_ = function(element) {
+  var offsetWidth = element.offsetWidth;
+  var offsetHeight = element.offsetHeight;
+  var webkitOffsetsZero =
+      goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
+  if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
+      element.getBoundingClientRect) {
+    // Fall back to calling getBoundingClientRect when offsetWidth or
+    // offsetHeight are not defined, or when they are zero in WebKit browsers.
+    // This makes sure that we return for the correct size for SVG elements, but
+    // will still return 0 on Webkit prior to 534.8, see
+    // http://trac.webkit.org/changeset/67252.
+    var clientRect = goog.style.getBoundingClientRect_(element);
+    return new goog.math.Size(clientRect.right - clientRect.left,
+        clientRect.bottom - clientRect.top);
   }
+  return new goog.math.Size(offsetWidth, offsetHeight);
 };
 
 
 /**
- * Triggers a combination of 'pointerover' and 'pointerevents' events.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Gets the height and width of an element, post transform, even if its display
+ * is none.
+ *
+ * This is like {@code goog.style.getSize}, except:
+ * <ol>
+ * <li>Takes webkitTransforms such as rotate and scale into account.
+ * <li>Will return null if {@code element} doesn't respond to
+ *     {@code getBoundingClientRect}.
+ * <li>Currently doesn't make sense on non-WebKit browsers which don't support
+ *    webkitTransforms.
+ * </ol>
+ * @param {!Element} element Element to get size of.
+ * @return {goog.math.Size} Object with width/height properties.
  */
-ol.pointer.PointerEventHandler.prototype.enterOver =
-    function(pointerEventData, browserEvent) {
-  this.over(pointerEventData, browserEvent);
-  if (!this.contains_(
-      pointerEventData.target,
-      pointerEventData.relatedTarget)) {
-    this.enter(pointerEventData, browserEvent);
+goog.style.getTransformedSize = function(element) {
+  if (!element.getBoundingClientRect) {
+    return null;
   }
+
+  var clientRect = goog.style.evaluateWithTemporaryDisplay_(
+      goog.style.getBoundingClientRect_, element);
+  return new goog.math.Size(clientRect.right - clientRect.left,
+      clientRect.bottom - clientRect.top);
 };
 
 
 /**
- * @private
- * @param {Element} container
- * @param {Element} contained
- * @return {boolean} Returns true if the container element
- *   contains the other element.
+ * Returns a bounding rectangle for a given element in page space.
+ * @param {Element} element Element to get bounds of. Must not be display none.
+ * @return {!goog.math.Rect} Bounding rectangle for the element.
  */
-ol.pointer.PointerEventHandler.prototype.contains_ =
-    function(container, contained) {
-  return container.contains(contained);
+goog.style.getBounds = function(element) {
+  var o = goog.style.getPageOffset(element);
+  var s = goog.style.getSize(element);
+  return new goog.math.Rect(o.x, o.y, s.width, s.height);
 };
 
 
-// EVENT CREATION AND TRACKING
 /**
- * Creates a new Event of type `inType`, based on the information in
- * `pointerEventData`.
- *
- * @param {string} inType A string representing the type of event to create.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
- * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`.
+ * Converts a CSS selector in the form style-property to styleProperty.
+ * @param {*} selector CSS Selector.
+ * @return {string} Camel case selector.
+ * @deprecated Use goog.string.toCamelCase instead.
  */
-ol.pointer.PointerEventHandler.prototype.makeEvent =
-    function(inType, pointerEventData, browserEvent) {
-  return new ol.pointer.PointerEvent(inType, browserEvent, pointerEventData);
+goog.style.toCamelCase = function(selector) {
+  return goog.string.toCamelCase(String(selector));
 };
 
 
 /**
- * Make and dispatch an event in one call.
- * @param {string} inType A string representing the type of event.
- * @param {Object} pointerEventData
- * @param {goog.events.BrowserEvent } browserEvent
+ * Converts a CSS selector in the form styleProperty to style-property.
+ * @param {string} selector Camel case selector.
+ * @return {string} Selector cased.
+ * @deprecated Use goog.string.toSelectorCase instead.
  */
-ol.pointer.PointerEventHandler.prototype.fireEvent =
-    function(inType, pointerEventData, browserEvent) {
-  var e = this.makeEvent(inType, pointerEventData, browserEvent);
-  this.dispatchEvent(e);
+goog.style.toSelectorCase = function(selector) {
+  return goog.string.toSelectorCase(selector);
 };
 
 
 /**
- * Creates a pointer event from a native pointer event
- * and dispatches this event.
- * @param {goog.events.BrowserEvent} nativeEvent A platform event with a target.
+ * Gets the opacity of a node (x-browser). This gets the inline style opacity
+ * of the node, and does not take into account the cascaded or the computed
+ * style for this node.
+ * @param {Element} el Element whose opacity has to be found.
+ * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
+ *     if the opacity is not set.
  */
-ol.pointer.PointerEventHandler.prototype.fireNativeEvent =
-    function(nativeEvent) {
-  var e = this.makeEvent(nativeEvent.type, nativeEvent.getBrowserEvent(),
-      nativeEvent);
-  this.dispatchEvent(e);
+goog.style.getOpacity = function(el) {
+  var style = el.style;
+  var result = '';
+  if ('opacity' in style) {
+    result = style.opacity;
+  } else if ('MozOpacity' in style) {
+    result = style.MozOpacity;
+  } else if ('filter' in style) {
+    var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
+    if (match) {
+      result = String(match[1] / 100);
+    }
+  }
+  return result == '' ? result : Number(result);
 };
 
 
 /**
- * Wrap a native mouse event into a pointer event.
- * This proxy method is required for the legacy IE support.
- * @param {string} eventType The pointer event type.
- * @param {goog.events.BrowserEvent} browserEvent
- * @return {ol.pointer.PointerEvent}
+ * Sets the opacity of a node (x-browser).
+ * @param {Element} el Elements whose opacity has to be set.
+ * @param {number|string} alpha Opacity between 0 and 1 or an empty string
+ *     {@code ''} to clear the opacity.
  */
-ol.pointer.PointerEventHandler.prototype.wrapMouseEvent =
-    function(eventType, browserEvent) {
-  var pointerEvent = this.makeEvent(
-      eventType,
-      ol.pointer.MouseSource.prepareEvent(browserEvent, this),
-      browserEvent
-      );
-  return pointerEvent;
+goog.style.setOpacity = function(el, alpha) {
+  var style = el.style;
+  if ('opacity' in style) {
+    style.opacity = alpha;
+  } else if ('MozOpacity' in style) {
+    style.MozOpacity = alpha;
+  } else if ('filter' in style) {
+    // TODO(arv): Overwriting the filter might have undesired side effects.
+    if (alpha === '') {
+      style.filter = '';
+    } else {
+      style.filter = 'alpha(opacity=' + alpha * 100 + ')';
+    }
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Sets the background of an element to a transparent image in a browser-
+ * independent manner.
+ *
+ * This function does not support repeating backgrounds or alternate background
+ * positions to match the behavior of Internet Explorer. It also does not
+ * support sizingMethods other than crop since they cannot be replicated in
+ * browsers other than Internet Explorer.
+ *
+ * @param {Element} el The element to set background on.
+ * @param {string} src The image source URL.
  */
-ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
-  this.unregister_();
-  goog.base(this, 'disposeInternal');
+goog.style.setTransparentBackgroundImage = function(el, src) {
+  var style = el.style;
+  // It is safe to use the style.filter in IE only. In Safari 'filter' is in
+  // style object but access to style.filter causes it to throw an exception.
+  // Note: IE8 supports images with an alpha channel.
+  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
+    // See TODO in setOpacity.
+    style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
+        'src="' + src + '", sizingMethod="crop")';
+  } else {
+    // Set style properties individually instead of using background shorthand
+    // to prevent overwriting a pre-existing background color.
+    style.backgroundImage = 'url(' + src + ')';
+    style.backgroundPosition = 'top left';
+    style.backgroundRepeat = 'no-repeat';
+  }
 };
 
 
 /**
- * Constants for event names.
- * @enum {string}
+ * Clears the background image of an element in a browser independent manner.
+ * @param {Element} el The element to clear background image for.
  */
-ol.pointer.EventType = {
-  POINTERMOVE: 'pointermove',
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
+goog.style.clearTransparentBackgroundImage = function(el) {
+  var style = el.style;
+  if ('filter' in style) {
+    // See TODO in setOpacity.
+    style.filter = '';
+  } else {
+    // Set style properties individually instead of using background shorthand
+    // to prevent overwriting a pre-existing background color.
+    style.backgroundImage = 'none';
+  }
 };
 
 
 /**
- * Properties to copy when cloning an event, with default values.
- * @type {Array.<Array>}
+ * Shows or hides an element from the page. Hiding the element is done by
+ * setting the display property to "none", removing the element from the
+ * rendering hierarchy so it takes up no space. To show the element, the default
+ * inherited display property is restored (defined either in stylesheets or by
+ * the browser's default style rules.)
+ *
+ * Caveat 1: if the inherited display property for the element is set to "none"
+ * by the stylesheets, that is the property that will be restored by a call to
+ * showElement(), effectively toggling the display between "none" and "none".
+ *
+ * Caveat 2: if the element display style is set inline (by setting either
+ * element.style.display or a style attribute in the HTML), a call to
+ * showElement will clear that setting and defer to the inherited style in the
+ * stylesheet.
+ * @param {Element} el Element to show or hide.
+ * @param {*} display True to render the element in its default style,
+ *     false to disable rendering the element.
+ * @deprecated Use goog.style.setElementShown instead.
  */
-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]
-];
-
-// FIXME handle date line wrap
-
-goog.provide('ol.control.Attribution');
-
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.dom.classlist');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('goog.style');
-goog.require('ol.Attribution');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.pointer.PointerEventHandler');
-
+goog.style.showElement = function(el, display) {
+  goog.style.setElementShown(el, display);
+};
 
 
 /**
- * @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`.
+ * Shows or hides an element from the page. Hiding the element is done by
+ * setting the display property to "none", removing the element from the
+ * rendering hierarchy so it takes up no space. To show the element, the default
+ * inherited display property is restored (defined either in stylesheets or by
+ * the browser's default style rules).
  *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.AttributionOptions=} opt_options Attribution options.
- * @api stable
+ * Caveat 1: if the inherited display property for the element is set to "none"
+ * by the stylesheets, that is the property that will be restored by a call to
+ * setElementShown(), effectively toggling the display between "none" and
+ * "none".
+ *
+ * Caveat 2: if the element display style is set inline (by setting either
+ * element.style.display or a style attribute in the HTML), a call to
+ * setElementShown will clear that setting and defer to the inherited style in
+ * the stylesheet.
+ * @param {Element} el Element to show or hide.
+ * @param {*} isShown True to render the element in its default style,
+ *     false to disable rendering the element.
  */
-ol.control.Attribution = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+goog.style.setElementShown = function(el, isShown) {
+  el.style.display = isShown ? '' : 'none';
+};
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.ulElement_ = goog.dom.createElement(goog.dom.TagName.UL);
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.logoLi_ = goog.dom.createElement(goog.dom.TagName.LI);
+/**
+ * Test whether the given element has been shown or hidden via a call to
+ * {@link #setElementShown}.
+ *
+ * Note this is strictly a companion method for a call
+ * to {@link #setElementShown} and the same caveats apply; in particular, this
+ * method does not guarantee that the return value will be consistent with
+ * whether or not the element is actually visible.
+ *
+ * @param {Element} el The element to test.
+ * @return {boolean} Whether the element has been shown.
+ * @see #setElementShown
+ */
+goog.style.isElementShown = function(el) {
+  return el.style.display != 'none';
+};
 
-  goog.dom.appendChild(this.ulElement_, this.logoLi_);
-  goog.style.setElementShown(this.logoLi_, false);
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.collapsed_ = goog.isDef(options.collapsed) ? options.collapsed : true;
+/**
+ * Installs the styles string into the window that contains opt_element.  If
+ * opt_element is null, the main window is used.
+ * @param {string} stylesString The style string to install.
+ * @param {Node=} opt_node Node whose parent document should have the
+ *     styles installed.
+ * @return {Element|StyleSheet} The style element created.
+ */
+goog.style.installStyles = function(stylesString, opt_node) {
+  var dh = goog.dom.getDomHelper(opt_node);
+  var styleSheet = null;
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.collapsible_ = goog.isDef(options.collapsible) ?
-      options.collapsible : true;
+  // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
+  // undefined as of IE 11.
+  var doc = dh.getDocument();
+  if (goog.userAgent.IE && doc.createStyleSheet) {
+    styleSheet = doc.createStyleSheet();
+    goog.style.setStyles(styleSheet, stylesString);
+  } else {
+    var head = dh.getElementsByTagNameAndClass('head')[0];
 
-  if (!this.collapsible_) {
-    this.collapsed_ = false;
+    // In opera documents are not guaranteed to have a head element, thus we
+    // have to make sure one exists before using it.
+    if (!head) {
+      var body = dh.getElementsByTagNameAndClass('body')[0];
+      head = dh.createDom('head');
+      body.parentNode.insertBefore(head, body);
+    }
+    styleSheet = dh.createDom('style');
+    // NOTE(user): Setting styles after the style element has been appended
+    // to the head results in a nasty Webkit bug in certain scenarios. Please
+    // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
+    // details.
+    goog.style.setStyles(styleSheet, stylesString);
+    dh.appendChild(head, styleSheet);
   }
+  return styleSheet;
+};
 
-  var className = goog.isDef(options.className) ?
-      options.className : 'ol-attribution';
 
-  var tipLabel = goog.isDef(options.tipLabel) ?
-      options.tipLabel : 'Attributions';
-  var tip = goog.dom.createDom(goog.dom.TagName.SPAN, {
-    'role' : 'tooltip'
-  }, tipLabel);
+/**
+ * Removes the styles added by {@link #installStyles}.
+ * @param {Element|StyleSheet} styleSheet The value returned by
+ *     {@link #installStyles}.
+ */
+goog.style.uninstallStyles = function(styleSheet) {
+  var node = styleSheet.ownerNode || styleSheet.owningElement ||
+      /** @type {Element} */ (styleSheet);
+  goog.dom.removeNode(node);
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.collapseLabel_ = goog.isDef(options.collapseLabel) ?
-      options.collapseLabel : '\u00BB';
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.label_ = goog.isDef(options.label) ? options.label : 'i';
-  var label = goog.dom.createDom(goog.dom.TagName.SPAN, {},
-      (this.collapsible_ && !this.collapsed_) ?
-      this.collapseLabel_ : this.label_);
+/**
+ * Sets the content of a style element.  The style element can be any valid
+ * style element.  This element will have its content completely replaced by
+ * the new stylesString.
+ * @param {Element|StyleSheet} element A stylesheet element as returned by
+ *     installStyles.
+ * @param {string} stylesString The new content of the stylesheet.
+ */
+goog.style.setStyles = function(element, stylesString) {
+  if (goog.userAgent.IE && goog.isDef(element.cssText)) {
+    // Adding the selectors individually caused the browser to hang if the
+    // selector was invalid or there were CSS comments.  Setting the cssText of
+    // the style node works fine and ignores CSS that IE doesn't understand.
+    // However IE >= 11 doesn't support cssText any more, so we make sure that
+    // cssText is a defined property and otherwise fall back to innerHTML.
+    element.cssText = stylesString;
+  } else {
+    element.innerHTML = stylesString;
+  }
+};
 
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.labelSpan_ = label;
-  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': 'ol-has-tooltip',
-    'type': 'button'
-  }, this.labelSpan_);
-  goog.dom.appendChild(button, tip);
+/**
+ * Sets 'white-space: pre-wrap' for a node (x-browser).
+ *
+ * There are as many ways of specifying pre-wrap as there are browsers.
+ *
+ * CSS3/IE8: white-space: pre-wrap;
+ * Mozilla:  white-space: -moz-pre-wrap;
+ * Opera:    white-space: -o-pre-wrap;
+ * IE6/7:    white-space: pre; word-wrap: break-word;
+ *
+ * @param {Element} el Element to enable pre-wrap for.
+ */
+goog.style.setPreWrap = function(el) {
+  var style = el.style;
+  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
+    style.whiteSpace = 'pre';
+    style.wordWrap = 'break-word';
+  } else if (goog.userAgent.GECKO) {
+    style.whiteSpace = '-moz-pre-wrap';
+  } else {
+    style.whiteSpace = 'pre-wrap';
+  }
+};
 
-  var buttonHandler = new ol.pointer.PointerEventHandler(button);
-  this.registerDisposable(buttonHandler);
-  goog.events.listen(buttonHandler, ol.pointer.EventType.POINTERUP,
-      this.handlePointerUp_, false, this);
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      this.handleClick_, false, this);
 
-  goog.events.listen(button, [
-    goog.events.EventType.MOUSEOUT,
-    goog.events.EventType.FOCUSOUT
-  ], function() {
-    this.blur();
-  }, false);
+/**
+ * Sets 'display: inline-block' for an element (cross-browser).
+ * @param {Element} el Element to which the inline-block display style is to be
+ *    applied.
+ * @see ../demos/inline_block_quirks.html
+ * @see ../demos/inline_block_standards.html
+ */
+goog.style.setInlineBlock = function(el) {
+  var style = el.style;
+  // Without position:relative, weirdness ensues.  Just accept it and move on.
+  style.position = 'relative';
 
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, {
-    'class': className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-        ol.css.CLASS_CONTROL +
-        (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
-        (this.collapsible_ ? '' : ' ol-uncollapsible')
-  }, this.ulElement_, button);
+  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
+    // IE8 supports inline-block so fall through to the else
+    // Zoom:1 forces hasLayout, display:inline gives inline behavior.
+    style.zoom = '1';
+    style.display = 'inline';
+  } else if (goog.userAgent.GECKO) {
+    // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box
+    // is close enough.
+    style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' :
+        '-moz-inline-box';
+  } else {
+    // Opera, Webkit, and Safari seem to do OK with the standard inline-block
+    // style.
+    style.display = 'inline-block';
+  }
+};
 
-  goog.base(this, {
-    element: element,
-    target: options.target
-  });
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
+/**
+ * Returns true if the element is using right to left (rtl) direction.
+ * @param {Element} el  The element to test.
+ * @return {boolean} True for right to left, false for left to right.
+ */
+goog.style.isRightToLeft = function(el) {
+  return 'rtl' == goog.style.getStyle_(el, 'direction');
+};
 
-  /**
-   * @private
-   * @type {Object.<string, Element>}
-   */
-  this.attributionElements_ = {};
 
-  /**
-   * @private
-   * @type {Object.<string, boolean>}
-   */
-  this.attributionElementRenderedVisible_ = {};
+/**
+ * The CSS style property corresponding to an element being
+ * unselectable on the current browser platform (null if none).
+ * Opera and IE instead use a DOM attribute 'unselectable'.
+ * @type {?string}
+ * @private
+ */
+goog.style.unselectableStyle_ =
+    goog.userAgent.GECKO ? 'MozUserSelect' :
+    goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
+    null;
 
-  /**
-   * @private
-   * @type {Object.<string, Element>}
-   */
-  this.logoElements_ = {};
 
+/**
+ * Returns true if the element is set to be unselectable, false otherwise.
+ * Note that on some platforms (e.g. Mozilla), even if an element isn't set
+ * to be unselectable, it will behave as such if any of its ancestors is
+ * unselectable.
+ * @param {Element} el  Element to check.
+ * @return {boolean}  Whether the element is set to be unselectable.
+ */
+goog.style.isUnselectable = function(el) {
+  if (goog.style.unselectableStyle_) {
+    return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
+  } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
+    return el.getAttribute('unselectable') == 'on';
+  }
+  return false;
 };
-goog.inherits(ol.control.Attribution, ol.control.Control);
 
 
 /**
- * @param {?olx.FrameState} frameState Frame state.
- * @return {Array.<Object.<string, ol.Attribution>>} Attributions.
+ * Makes the element and its descendants selectable or unselectable.  Note
+ * that on some platforms (e.g. Mozilla), even if an element isn't set to
+ * be unselectable, it will behave as such if any of its ancestors is
+ * unselectable.
+ * @param {Element} el  The element to alter.
+ * @param {boolean} unselectable  Whether the element and its descendants
+ *     should be made unselectable.
+ * @param {boolean=} opt_noRecurse  Whether to only alter the element's own
+ *     selectable state, and leave its descendants alone; defaults to false.
  */
-ol.control.Attribution.prototype.getSourceAttributions = function(frameState) {
-  var i, ii, j, jj, tileRanges, source, sourceAttribution,
-      sourceAttributionKey, sourceAttributions, sourceKey;
-  var layerStatesArray = frameState.layerStatesArray;
-  /** @type {Object.<string, ol.Attribution>} */
-  var attributions = goog.object.clone(frameState.attributions);
-  /** @type {Object.<string, ol.Attribution>} */
-  var hiddenAttributions = {};
-  for (i = 0, ii = layerStatesArray.length; i < ii; i++) {
-    source = layerStatesArray[i].layer.getSource();
-    sourceKey = goog.getUid(source).toString();
-    sourceAttributions = source.getAttributions();
-    if (goog.isNull(sourceAttributions)) {
-      continue;
-    }
-    for (j = 0, jj = sourceAttributions.length; j < jj; j++) {
-      sourceAttribution = sourceAttributions[j];
-      sourceAttributionKey = goog.getUid(sourceAttribution).toString();
-      if (sourceAttributionKey in attributions) {
-        continue;
-      }
-      tileRanges = frameState.usedTiles[sourceKey];
-      if (goog.isDef(tileRanges) &&
-          sourceAttribution.intersectsAnyTileRange(tileRanges)) {
-        if (sourceAttributionKey in hiddenAttributions) {
-          delete hiddenAttributions[sourceAttributionKey];
-        }
-        attributions[sourceAttributionKey] = sourceAttribution;
+goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
+  // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
+  var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
+  var name = goog.style.unselectableStyle_;
+  if (name) {
+    // Add/remove the appropriate CSS style to/from the element and its
+    // descendants.
+    var value = unselectable ? 'none' : '';
+    el.style[name] = value;
+    if (descendants) {
+      for (var i = 0, descendant; descendant = descendants[i]; i++) {
+        descendant.style[name] = value;
       }
-      else {
-        hiddenAttributions[sourceAttributionKey] = sourceAttribution;
+    }
+  } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
+    // Toggle the 'unselectable' attribute on the element and its descendants.
+    var value = unselectable ? 'on' : '';
+    el.setAttribute('unselectable', value);
+    if (descendants) {
+      for (var i = 0, descendant; descendant = descendants[i]; i++) {
+        descendant.setAttribute('unselectable', value);
       }
     }
   }
-  return [attributions, hiddenAttributions];
 };
 
 
 /**
- * @inheritDoc
+ * Gets the border box size for an element.
+ * @param {Element} element  The element to get the size for.
+ * @return {!goog.math.Size} The border box size.
  */
-ol.control.Attribution.prototype.handleMapPostrender = function(mapEvent) {
-  this.updateElement_(mapEvent.frameState);
+goog.style.getBorderBoxSize = function(element) {
+  return new goog.math.Size(element.offsetWidth, element.offsetHeight);
 };
 
 
 /**
- * @private
- * @param {?olx.FrameState} frameState Frame state.
+ * Sets the border box size of an element. This is potentially expensive in IE
+ * if the document is CSS1Compat mode
+ * @param {Element} element  The element to set the size on.
+ * @param {goog.math.Size} size  The new size.
  */
-ol.control.Attribution.prototype.updateElement_ = function(frameState) {
-
-  if (goog.isNull(frameState)) {
-    if (this.renderedVisible_) {
-      goog.style.setElementShown(this.element, false);
-      this.renderedVisible_ = false;
-    }
-    return;
-  }
-
-  var attributions = this.getSourceAttributions(frameState);
-  /** @type {Object.<string, ol.Attribution>} */
-  var visibleAttributions = attributions[0];
-  /** @type {Object.<string, ol.Attribution>} */
-  var hiddenAttributions = attributions[1];
+goog.style.setBorderBoxSize = function(element, size) {
+  var doc = goog.dom.getOwnerDocument(element);
+  var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
 
-  var attributionElement, attributionKey;
-  for (attributionKey in this.attributionElements_) {
-    if (attributionKey in visibleAttributions) {
-      if (!this.attributionElementRenderedVisible_[attributionKey]) {
-        goog.style.setElementShown(
-            this.attributionElements_[attributionKey], true);
-        this.attributionElementRenderedVisible_[attributionKey] = true;
-      }
-      delete visibleAttributions[attributionKey];
-    }
-    else if (attributionKey in hiddenAttributions) {
-      if (this.attributionElementRenderedVisible_[attributionKey]) {
-        goog.style.setElementShown(
-            this.attributionElements_[attributionKey], false);
-        delete this.attributionElementRenderedVisible_[attributionKey];
-      }
-      delete hiddenAttributions[attributionKey];
-    }
-    else {
-      goog.dom.removeNode(this.attributionElements_[attributionKey]);
-      delete this.attributionElements_[attributionKey];
-      delete this.attributionElementRenderedVisible_[attributionKey];
+  if (goog.userAgent.IE &&
+      !goog.userAgent.isVersionOrHigher('10') &&
+      (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
+    var style = element.style;
+    if (isCss1CompatMode) {
+      var paddingBox = goog.style.getPaddingBox(element);
+      var borderBox = goog.style.getBorderBox(element);
+      style.pixelWidth = size.width - borderBox.left - paddingBox.left -
+                         paddingBox.right - borderBox.right;
+      style.pixelHeight = size.height - borderBox.top - paddingBox.top -
+                          paddingBox.bottom - borderBox.bottom;
+    } else {
+      style.pixelWidth = size.width;
+      style.pixelHeight = size.height;
     }
-  }
-  for (attributionKey in visibleAttributions) {
-    attributionElement = goog.dom.createElement(goog.dom.TagName.LI);
-    attributionElement.innerHTML =
-        visibleAttributions[attributionKey].getHTML();
-    goog.dom.appendChild(this.ulElement_, attributionElement);
-    this.attributionElements_[attributionKey] = attributionElement;
-    this.attributionElementRenderedVisible_[attributionKey] = true;
-  }
-  for (attributionKey in hiddenAttributions) {
-    attributionElement = goog.dom.createElement(goog.dom.TagName.LI);
-    attributionElement.innerHTML =
-        hiddenAttributions[attributionKey].getHTML();
-    goog.style.setElementShown(attributionElement, false);
-    goog.dom.appendChild(this.ulElement_, attributionElement);
-    this.attributionElements_[attributionKey] = attributionElement;
-  }
-
-  var renderVisible =
-      !goog.object.isEmpty(this.attributionElementRenderedVisible_) ||
-      !goog.object.isEmpty(frameState.logos);
-  if (this.renderedVisible_ != renderVisible) {
-    goog.style.setElementShown(this.element, renderVisible);
-    this.renderedVisible_ = renderVisible;
-  }
-  if (renderVisible &&
-      goog.object.isEmpty(this.attributionElementRenderedVisible_)) {
-    goog.dom.classlist.add(this.element, 'ol-logo-only');
   } else {
-    goog.dom.classlist.remove(this.element, 'ol-logo-only');
+    goog.style.setBoxSizingSize_(element, size, 'border-box');
   }
-
-  this.insertLogos_(frameState);
-
 };
 
 
 /**
- * @param {?olx.FrameState} frameState Frame state.
- * @private
+ * Gets the content box size for an element.  This is potentially expensive in
+ * all browsers.
+ * @param {Element} element  The element to get the size for.
+ * @return {!goog.math.Size} The content box size.
  */
-ol.control.Attribution.prototype.insertLogos_ = function(frameState) {
+goog.style.getContentBoxSize = function(element) {
+  var doc = goog.dom.getOwnerDocument(element);
+  var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
+  if (ieCurrentStyle &&
+      goog.dom.getDomHelper(doc).isCss1CompatMode() &&
+      ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
+      !ieCurrentStyle.boxSizing) {
+    // If IE in CSS1Compat mode than just use the width and height.
+    // If we have a boxSizing then fall back on measuring the borders etc.
+    var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width,
+                                            'width', 'pixelWidth');
+    var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height,
+                                             'height', 'pixelHeight');
+    return new goog.math.Size(width, height);
+  } else {
+    var borderBoxSize = goog.style.getBorderBoxSize(element);
+    var paddingBox = goog.style.getPaddingBox(element);
+    var borderBox = goog.style.getBorderBox(element);
+    return new goog.math.Size(borderBoxSize.width -
+                              borderBox.left - paddingBox.left -
+                              paddingBox.right - borderBox.right,
+                              borderBoxSize.height -
+                              borderBox.top - paddingBox.top -
+                              paddingBox.bottom - borderBox.bottom);
+  }
+};
 
-  var logo;
-  var logos = frameState.logos;
-  var logoElements = this.logoElements_;
 
-  for (logo in logoElements) {
-    if (!(logo in logos)) {
-      goog.dom.removeNode(logoElements[logo]);
-      delete logoElements[logo];
+/**
+ * Sets the content box size of an element. This is potentially expensive in IE
+ * if the document is BackCompat mode.
+ * @param {Element} element  The element to set the size on.
+ * @param {goog.math.Size} size  The new size.
+ */
+goog.style.setContentBoxSize = function(element, size) {
+  var doc = goog.dom.getOwnerDocument(element);
+  var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
+  if (goog.userAgent.IE &&
+      !goog.userAgent.isVersionOrHigher('10') &&
+      (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
+    var style = element.style;
+    if (isCss1CompatMode) {
+      style.pixelWidth = size.width;
+      style.pixelHeight = size.height;
+    } else {
+      var paddingBox = goog.style.getPaddingBox(element);
+      var borderBox = goog.style.getBorderBox(element);
+      style.pixelWidth = size.width + borderBox.left + paddingBox.left +
+                         paddingBox.right + borderBox.right;
+      style.pixelHeight = size.height + borderBox.top + paddingBox.top +
+                          paddingBox.bottom + borderBox.bottom;
     }
+  } else {
+    goog.style.setBoxSizingSize_(element, size, 'content-box');
   }
+};
 
-  var image, logoElement, logoKey;
-  for (logoKey in logos) {
-    if (!(logoKey in logoElements)) {
-      image = new Image();
-      image.src = logoKey;
-      var logoValue = logos[logoKey];
-      if (logoValue === '') {
-        logoElement = image;
-      } else {
-        logoElement = goog.dom.createDom(goog.dom.TagName.A, {
-          'href': logoValue,
-          'target': '_blank'
-        });
-        logoElement.appendChild(image);
-      }
-      goog.dom.appendChild(this.logoLi_, logoElement);
-      logoElements[logoKey] = logoElement;
-    }
-  }
 
-  goog.style.setElementShown(this.logoLi_, !goog.object.isEmpty(logos));
+/**
+ * Helper function that sets the box sizing as well as the width and height
+ * @param {Element} element  The element to set the size on.
+ * @param {goog.math.Size} size  The new size to set.
+ * @param {string} boxSizing  The box-sizing value.
+ * @private
+ */
+goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
+  var style = element.style;
+  if (goog.userAgent.GECKO) {
+    style.MozBoxSizing = boxSizing;
+  } else if (goog.userAgent.WEBKIT) {
+    style.WebkitBoxSizing = boxSizing;
+  } else {
+    // Includes IE8 and Opera 9.50+
+    style.boxSizing = boxSizing;
+  }
 
+  // Setting this to a negative value will throw an exception on IE
+  // (and doesn't do anything different than setting it to 0).
+  style.width = Math.max(size.width, 0) + 'px';
+  style.height = Math.max(size.height, 0) + 'px';
 };
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
+ * IE specific function that converts a non pixel unit to pixels.
+ * @param {Element} element  The element to convert the value for.
+ * @param {string} value  The current value as a string. The value must not be
+ *     ''.
+ * @param {string} name  The CSS property name to use for the converstion. This
+ *     should be 'left', 'top', 'width' or 'height'.
+ * @param {string} pixelName  The CSS pixel property name to use to get the
+ *     value in pixels.
+ * @return {number} The value in pixels.
  * @private
  */
-ol.control.Attribution.prototype.handleClick_ = function(event) {
-  if (event.screenX !== 0 && event.screenY !== 0) {
-    return;
+goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
+  // Try if we already have a pixel value. IE does not do half pixels so we
+  // only check if it matches a number followed by 'px'.
+  if (/^\d+px?$/.test(value)) {
+    return parseInt(value, 10);
+  } else {
+    var oldStyleValue = element.style[name];
+    var oldRuntimeValue = element.runtimeStyle[name];
+    // set runtime style to prevent changes
+    element.runtimeStyle[name] = element.currentStyle[name];
+    element.style[name] = value;
+    var pixelValue = element.style[pixelName];
+    // restore
+    element.style[name] = oldStyleValue;
+    element.runtimeStyle[name] = oldRuntimeValue;
+    return pixelValue;
   }
-  this.handleToggle_();
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent The event to handle
+ * Helper function for getting the pixel padding or margin for IE.
+ * @param {Element} element  The element to get the padding for.
+ * @param {string} propName  The property name.
+ * @return {number} The pixel padding.
  * @private
  */
-ol.control.Attribution.prototype.handlePointerUp_ = function(pointerEvent) {
-  pointerEvent.browserEvent.preventDefault();
-  this.handleToggle_();
+goog.style.getIePixelDistance_ = function(element, propName) {
+  var value = goog.style.getCascadedStyle(element, propName);
+  return value ?
+      goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
 };
 
 
 /**
+ * Gets the computed paddings or margins (on all sides) in pixels.
+ * @param {Element} element  The element to get the padding for.
+ * @param {string} stylePrefix  Pass 'padding' to retrieve the padding box,
+ *     or 'margin' to retrieve the margin box.
+ * @return {!goog.math.Box} The computed paddings or margins.
  * @private
  */
-ol.control.Attribution.prototype.handleToggle_ = function() {
-  goog.dom.classlist.toggle(this.element, 'ol-collapsed');
-  goog.dom.setTextContent(this.labelSpan_,
-      (this.collapsed_) ? this.collapseLabel_ : this.label_);
-  this.collapsed_ = !this.collapsed_;
+goog.style.getBox_ = function(element, stylePrefix) {
+  if (goog.userAgent.IE) {
+    var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
+    var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
+    var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
+    var bottom = goog.style.getIePixelDistance_(
+        element, stylePrefix + 'Bottom');
+    return new goog.math.Box(top, right, bottom, left);
+  } else {
+    // On non-IE browsers, getComputedStyle is always non-null.
+    var left = /** @type {string} */ (
+        goog.style.getComputedStyle(element, stylePrefix + 'Left'));
+    var right = /** @type {string} */ (
+        goog.style.getComputedStyle(element, stylePrefix + 'Right'));
+    var top = /** @type {string} */ (
+        goog.style.getComputedStyle(element, stylePrefix + 'Top'));
+    var bottom = /** @type {string} */ (
+        goog.style.getComputedStyle(element, stylePrefix + 'Bottom'));
+
+    // NOTE(arv): Gecko can return floating point numbers for the computed
+    // style values.
+    return new goog.math.Box(parseFloat(top),
+                             parseFloat(right),
+                             parseFloat(bottom),
+                             parseFloat(left));
+  }
 };
 
 
 /**
- * @return {boolean} True if the widget is collapsible.
- * @api stable
+ * Gets the computed paddings (on all sides) in pixels.
+ * @param {Element} element  The element to get the padding for.
+ * @return {!goog.math.Box} The computed paddings.
  */
-ol.control.Attribution.prototype.getCollapsible = function() {
-  return this.collapsible_;
+goog.style.getPaddingBox = function(element) {
+  return goog.style.getBox_(element, 'padding');
 };
 
 
 /**
- * @param {boolean} collapsible True if the widget is collapsible.
- * @api stable
+ * Gets the computed margins (on all sides) in pixels.
+ * @param {Element} element  The element to get the margins for.
+ * @return {!goog.math.Box} The computed margins.
  */
-ol.control.Attribution.prototype.setCollapsible = function(collapsible) {
-  if (this.collapsible_ === collapsible) {
-    return;
-  }
-  this.collapsible_ = collapsible;
-  goog.dom.classlist.toggle(this.element, 'ol-uncollapsible');
-  if (!collapsible && this.collapsed_) {
-    this.handleToggle_();
-  }
+goog.style.getMarginBox = function(element) {
+  return goog.style.getBox_(element, 'margin');
 };
 
 
 /**
- * @param {boolean} collapsed True if the widget is collapsed.
- * @api stable
+ * A map used to map the border width keywords to a pixel width.
+ * @type {Object}
+ * @private
  */
-ol.control.Attribution.prototype.setCollapsed = function(collapsed) {
-  if (!this.collapsible_ || this.collapsed_ === collapsed) {
-    return;
-  }
-  this.handleToggle_();
+goog.style.ieBorderWidthKeywords_ = {
+  'thin': 2,
+  'medium': 4,
+  'thick': 6
 };
 
 
 /**
- * @return {boolean} True if the widget is collapsed.
- * @api stable
+ * Helper function for IE to get the pixel border.
+ * @param {Element} element  The element to get the pixel border for.
+ * @param {string} prop  The part of the property name.
+ * @return {number} The value in pixels.
+ * @private
  */
-ol.control.Attribution.prototype.getCollapsed = function() {
-  return this.collapsed_;
+goog.style.getIePixelBorder_ = function(element, prop) {
+  if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
+    return 0;
+  }
+  var width = goog.style.getCascadedStyle(element, prop + 'Width');
+  if (width in goog.style.ieBorderWidthKeywords_) {
+    return goog.style.ieBorderWidthKeywords_[width];
+  }
+  return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
 };
 
-goog.provide('ol.control.Rotate');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.math');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
-goog.require('ol.pointer.PointerEventHandler');
-
-
 
 /**
- * @classdesc
- * A button control to reset rotation to 0.
- * To style this control use css selector `.ol-rotate`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.RotateOptions=} opt_options Rotate options.
- * @api stable
+ * Gets the computed border widths (on all sides) in pixels
+ * @param {Element} element  The element to get the border widths for.
+ * @return {!goog.math.Box} The computed border widths.
  */
-ol.control.Rotate = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  var className = goog.isDef(options.className) ?
-      options.className : 'ol-rotate';
-
-  /**
-   * @type {Element}
-   * @private
-   */
-  this.label_ = goog.dom.createDom(goog.dom.TagName.SPAN,
-      { 'class': 'ol-compass' },
-      goog.isDef(options.label) ? options.label : '\u21E7');
-
-  var tipLabel = goog.isDef(options.tipLabel) ?
-      options.tipLabel : 'Reset rotation';
+goog.style.getBorderBox = function(element) {
+  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
+    var left = goog.style.getIePixelBorder_(element, 'borderLeft');
+    var right = goog.style.getIePixelBorder_(element, 'borderRight');
+    var top = goog.style.getIePixelBorder_(element, 'borderTop');
+    var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
+    return new goog.math.Box(top, right, bottom, left);
+  } else {
+    // On non-IE browsers, getComputedStyle is always non-null.
+    var left = /** @type {string} */ (
+        goog.style.getComputedStyle(element, 'borderLeftWidth'));
+    var right = /** @type {string} */ (
+        goog.style.getComputedStyle(element, 'borderRightWidth'));
+    var top = /** @type {string} */ (
+        goog.style.getComputedStyle(element, 'borderTopWidth'));
+    var bottom = /** @type {string} */ (
+        goog.style.getComputedStyle(element, 'borderBottomWidth'));
 
-  var tip = goog.dom.createDom(goog.dom.TagName.SPAN, {
-    'role' : 'tooltip'
-  }, tipLabel);
-  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': className + '-reset ol-has-tooltip',
-    'name' : 'ResetRotation',
-    'type' : 'button'
-  }, tip, this.label_);
-
-  var handler = new ol.pointer.PointerEventHandler(button);
-  this.registerDisposable(handler);
-  goog.events.listen(handler, ol.pointer.EventType.POINTERUP,
-      ol.control.Rotate.prototype.handlePointerUp_, false, this);
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      ol.control.Rotate.prototype.handleClick_, false, this);
+    return new goog.math.Box(parseFloat(top),
+                             parseFloat(right),
+                             parseFloat(bottom),
+                             parseFloat(left));
+  }
+};
 
-  goog.events.listen(button, [
-    goog.events.EventType.MOUSEOUT,
-    goog.events.EventType.FOCUSOUT
-  ], function() {
-    this.blur();
-  }, false);
 
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
+/**
+ * Returns the font face applied to a given node. Opera and IE should return
+ * the font actually displayed. Firefox returns the author's most-preferred
+ * font (whether the browser is capable of displaying it or not.)
+ * @param {Element} el  The element whose font family is returned.
+ * @return {string} The font family applied to el.
+ */
+goog.style.getFontFamily = function(el) {
+  var doc = goog.dom.getOwnerDocument(el);
+  var font = '';
+  // The moveToElementText method from the TextRange only works if the element
+  // is attached to the owner document.
+  if (doc.body.createTextRange && goog.dom.contains(doc, el)) {
+    var range = doc.body.createTextRange();
+    range.moveToElementText(el);
+    /** @preserveTry */
+    try {
+      font = range.queryCommandValue('FontName');
+    } catch (e) {
+      // This is a workaround for a awkward exception.
+      // On some IE, there is an exception coming from it.
+      // The error description from this exception is:
+      // This window has already been registered as a drop target
+      // This is bogus description, likely due to a bug in ie.
+      font = '';
+    }
+  }
+  if (!font) {
+    // Note if for some reason IE can't derive FontName with a TextRange, we
+    // fallback to using currentStyle
+    font = goog.style.getStyle_(el, 'fontFamily');
+  }
 
-  goog.base(this, {
-    element: element,
-    target: options.target
-  });
+  // Firefox returns the applied font-family string (author's list of
+  // preferred fonts.) We want to return the most-preferred font, in lieu of
+  // the *actually* applied font.
+  var fontsArray = font.split(',');
+  if (fontsArray.length > 1) font = fontsArray[0];
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
+  // Sanitize for x-browser consistency:
+  // Strip quotes because browsers aren't consistent with how they're
+  // applied; Opera always encloses, Firefox sometimes, and IE never.
+  return goog.string.stripQuotes(font, '"\'');
+};
 
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.autoHide_ = goog.isDef(options.autoHide) ? options.autoHide : true;
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.rotation_ = undefined;
+/**
+ * Regular expression used for getLengthUnits.
+ * @type {RegExp}
+ * @private
+ */
+goog.style.lengthUnitRegex_ = /[^\d]+$/;
 
-  element.style.opacity = (this.autoHide_) ? 0 : 1;
 
+/**
+ * Returns the units used for a CSS length measurement.
+ * @param {string} value  A CSS length quantity.
+ * @return {?string} The units of measurement.
+ */
+goog.style.getLengthUnits = function(value) {
+  var units = value.match(goog.style.lengthUnitRegex_);
+  return units && units[0] || null;
 };
-goog.inherits(ol.control.Rotate, ol.control.Control);
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
+ * Map of absolute CSS length units
+ * @type {Object}
  * @private
  */
-ol.control.Rotate.prototype.handleClick_ = function(event) {
-  if (event.screenX !== 0 && event.screenY !== 0) {
-    return;
-  }
-  this.resetNorth_();
+goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
+  'cm' : 1,
+  'in' : 1,
+  'mm' : 1,
+  'pc' : 1,
+  'pt' : 1
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent The event to handle
+ * Map of relative CSS length units that can be accurately converted to px
+ * font-size values using getIePixelValue_. Only units that are defined in
+ * relation to a font size are convertible (%, small, etc. are not).
+ * @type {Object}
  * @private
  */
-ol.control.Rotate.prototype.handlePointerUp_ = function(pointerEvent) {
-  pointerEvent.browserEvent.preventDefault();
-  this.resetNorth_();
+goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
+  'em' : 1,
+  'ex' : 1
 };
 
 
 /**
- * @private
+ * Returns the font size, in pixels, of text in an element.
+ * @param {Element} el  The element whose font size is returned.
+ * @return {number} The font size (in pixels).
  */
-ol.control.Rotate.prototype.resetNorth_ = function() {
-  var map = this.getMap();
-  var view = map.getView();
-  goog.asserts.assert(goog.isDef(view));
-  var currentRotation = view.getRotation();
-  while (currentRotation < -Math.PI) {
-    currentRotation += 2 * Math.PI;
+goog.style.getFontSize = function(el) {
+  var fontSize = goog.style.getStyle_(el, 'fontSize');
+  var sizeUnits = goog.style.getLengthUnits(fontSize);
+  if (fontSize && 'px' == sizeUnits) {
+    // NOTE(user): This could be parseFloat instead, but IE doesn't return
+    // decimal fractions in getStyle_ and Firefox reports the fractions, but
+    // ignores them when rendering. Interestingly enough, when we force the
+    // issue and size something to e.g., 50% of 25px, the browsers round in
+    // opposite directions with Firefox reporting 12px and IE 13px. I punt.
+    return parseInt(fontSize, 10);
   }
-  while (currentRotation > Math.PI) {
-    currentRotation -= 2 * Math.PI;
-  }
-  if (goog.isDef(currentRotation)) {
-    if (this.duration_ > 0) {
-      map.beforeRender(ol.animation.rotate({
-        rotation: currentRotation,
-        duration: this.duration_,
-        easing: ol.easing.easeOut
-      }));
+
+  // In IE, we can convert absolute length units to a px value using
+  // goog.style.getIePixelValue_. Units defined in relation to a font size
+  // (em, ex) are applied relative to the element's parentNode and can also
+  // be converted.
+  if (goog.userAgent.IE) {
+    if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
+      return goog.style.getIePixelValue_(el,
+                                         fontSize,
+                                         'left',
+                                         'pixelLeft');
+    } else if (el.parentNode &&
+               el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
+               sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
+      // Check the parent size - if it is the same it means the relative size
+      // value is inherited and we therefore don't want to count it twice.  If
+      // it is different, this element either has explicit style or has a CSS
+      // rule applying to it.
+      var parentElement = /** @type {!Element} */ (el.parentNode);
+      var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
+      return goog.style.getIePixelValue_(parentElement,
+                                         fontSize == parentSize ?
+                                             '1em' : fontSize,
+                                         'left',
+                                         'pixelLeft');
     }
-    view.setRotation(0);
   }
+
+  // Sometimes we can't cleanly find the font size (some units relative to a
+  // node's parent's font size are difficult: %, smaller et al), so we create
+  // an invisible, absolutely-positioned span sized to be the height of an 'M'
+  // rendered in its parent's (i.e., our target element's) font size. This is
+  // the definition of CSS's font size attribute.
+  var sizeElement = goog.dom.createDom(
+      'span',
+      {'style': 'visibility:hidden;position:absolute;' +
+            'line-height:0;padding:0;margin:0;border:0;height:1em;'});
+  goog.dom.appendChild(el, sizeElement);
+  fontSize = sizeElement.offsetHeight;
+  goog.dom.removeNode(sizeElement);
+
+  return fontSize;
 };
 
 
 /**
- * @inheritDoc
+ * Parses a style attribute value.  Converts CSS property names to camel case.
+ * @param {string} value The style attribute value.
+ * @return {!Object} Map of CSS properties to string values.
  */
-ol.control.Rotate.prototype.handleMapPostrender = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (goog.isNull(frameState)) {
-    return;
-  }
-  var rotation = frameState.viewState.rotation;
-  if (rotation != this.rotation_) {
-    var transform = 'rotate(' + goog.math.toDegrees(rotation) + 'deg)';
-    if (this.autoHide_) {
-      this.element.style.opacity = (rotation === 0) ? 0 : 1;
+goog.style.parseStyleAttribute = function(value) {
+  var result = {};
+  goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
+    var keyValue = pair.split(/\s*:\s*/);
+    if (keyValue.length == 2) {
+      result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1];
     }
-    this.label_.style.msTransform = transform;
-    this.label_.style.webkitTransform = transform;
-    this.label_.style.transform = transform;
-  }
-  this.rotation_ = rotation;
+  });
+  return result;
 };
 
-goog.provide('ol.control.Zoom');
-
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
-goog.require('ol.pointer.PointerEventHandler');
-
-
 
 /**
- * @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
+ * Reverse of parseStyleAttribute; that is, takes a style object and returns the
+ * corresponding attribute value.  Converts camel case property names to proper
+ * CSS selector names.
+ * @param {Object} obj Map of CSS properties to values.
+ * @return {string} The style attribute value.
  */
-ol.control.Zoom = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  var className = goog.isDef(options.className) ? options.className : 'ol-zoom';
-
-  var delta = goog.isDef(options.delta) ? options.delta : 1;
-
-  var zoomInLabel = goog.isDef(options.zoomInLabel) ?
-      options.zoomInLabel : '+';
-  var zoomOutLabel = goog.isDef(options.zoomOutLabel) ?
-      options.zoomOutLabel : '\u2212';
-
-  var zoomInTipLabel = goog.isDef(options.zoomInTipLabel) ?
-      options.zoomInTipLabel : 'Zoom in';
-  var zoomOutTipLabel = goog.isDef(options.zoomOutTipLabel) ?
-      options.zoomOutTipLabel : 'Zoom out';
-
-  var tTipZoomIn = goog.dom.createDom(goog.dom.TagName.SPAN, {
-    'role' : 'tooltip'
-  }, zoomInTipLabel);
-  var inElement = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': className + '-in ol-has-tooltip',
-    'type' : 'button'
-  }, tTipZoomIn, zoomInLabel);
-
-  var inElementHandler = new ol.pointer.PointerEventHandler(inElement);
-  this.registerDisposable(inElementHandler);
-  goog.events.listen(inElementHandler,
-      ol.pointer.EventType.POINTERUP, goog.partial(
-          ol.control.Zoom.prototype.handlePointerUp_, delta), false, this);
-  goog.events.listen(inElement,
-      goog.events.EventType.CLICK, goog.partial(
-          ol.control.Zoom.prototype.handleClick_, delta), false, this);
-
-  goog.events.listen(inElement, [
-    goog.events.EventType.MOUSEOUT,
-    goog.events.EventType.FOCUSOUT
-  ], function() {
-    this.blur();
-  }, false);
-
-  var tTipsZoomOut = goog.dom.createDom(goog.dom.TagName.SPAN, {
-    'role' : 'tooltip'
-  }, zoomOutTipLabel);
-  var outElement = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': className + '-out  ol-has-tooltip',
-    'type' : 'button'
-  }, tTipsZoomOut, zoomOutLabel);
-
-  var outElementHandler = new ol.pointer.PointerEventHandler(outElement);
-  this.registerDisposable(outElementHandler);
-  goog.events.listen(outElementHandler,
-      ol.pointer.EventType.POINTERUP, goog.partial(
-          ol.control.Zoom.prototype.handlePointerUp_, -delta), false, this);
-  goog.events.listen(outElement,
-      goog.events.EventType.CLICK, goog.partial(
-          ol.control.Zoom.prototype.handleClick_, -delta), false, this);
-
-  goog.events.listen(outElement, [
-    goog.events.EventType.MOUSEOUT,
-    goog.events.EventType.FOCUSOUT
-  ], function() {
-    this.blur();
-  }, false);
+goog.style.toStyleAttribute = function(obj) {
+  var buffer = [];
+  goog.object.forEach(obj, function(value, key) {
+    buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
+  });
+  return buffer.join('');
+};
 
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, inElement,
-      outElement);
 
-  goog.base(this, {
-    element: element,
-    target: options.target
-  });
+/**
+ * Sets CSS float property on an element.
+ * @param {Element} el The element to set float property on.
+ * @param {string} value The value of float CSS property to set on this element.
+ */
+goog.style.setFloat = function(el, value) {
+  el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
+};
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
 
+/**
+ * Gets value of explicitly-set float CSS property on an element.
+ * @param {Element} el The element to get float property of.
+ * @return {string} The value of explicitly-set float CSS property on this
+ *     element.
+ */
+goog.style.getFloat = function(el) {
+  return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
 };
-goog.inherits(ol.control.Zoom, ol.control.Control);
 
 
 /**
- * @param {number} delta Zoom delta.
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * Returns the scroll bar width (represents the width of both horizontal
+ * and vertical scroll).
+ *
+ * @param {string=} opt_className An optional class name (or names) to apply
+ *     to the invisible div created to measure the scrollbar. This is necessary
+ *     if some scrollbars are styled differently than others.
+ * @return {number} The scroll bar width in px.
  */
-ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
-  if (event.screenX !== 0 && event.screenY !== 0) {
-    return;
+goog.style.getScrollbarWidth = function(opt_className) {
+  // Add two hidden divs.  The child div is larger than the parent and
+  // forces scrollbars to appear on it.
+  // Using overflow:scroll does not work consistently with scrollbars that
+  // are styled with ::-webkit-scrollbar.
+  var outerDiv = goog.dom.createElement('div');
+  if (opt_className) {
+    outerDiv.className = opt_className;
   }
-  this.zoomByDelta_(delta);
+  outerDiv.style.cssText = 'overflow:auto;' +
+      'position:absolute;top:0;width:100px;height:100px';
+  var innerDiv = goog.dom.createElement('div');
+  goog.style.setSize(innerDiv, '200px', '200px');
+  outerDiv.appendChild(innerDiv);
+  goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
+  var width = outerDiv.offsetWidth - outerDiv.clientWidth;
+  goog.dom.removeNode(outerDiv);
+  return width;
 };
 
 
 /**
- * @param {number} delta Zoom delta.
- * @param {ol.pointer.PointerEvent} pointerEvent The event to handle
+ * Regular expression to extract x and y translation components from a CSS
+ * transform Matrix representation.
+ *
+ * @type {!RegExp}
+ * @const
  * @private
  */
-ol.control.Zoom.prototype.handlePointerUp_ = function(delta, pointerEvent) {
-  pointerEvent.browserEvent.preventDefault();
-  this.zoomByDelta_(delta);
-};
+goog.style.MATRIX_TRANSLATION_REGEX_ =
+    new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
+               '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
+               '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');
 
 
 /**
- * @param {number} delta Zoom delta.
- * @private
+ * Returns the x,y translation component of any CSS transforms applied to the
+ * element, in pixels.
+ *
+ * @param {!Element} element The element to get the translation of.
+ * @return {!goog.math.Coordinate} The CSS translation of the element in px.
  */
-ol.control.Zoom.prototype.zoomByDelta_ = function(delta) {
-  var map = this.getMap();
-  var view = map.getView();
-  var currentResolution = view.getResolution();
-  if (goog.isDef(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.style.getCssTranslation = function(element) {
+  var transform = goog.style.getComputedTransform(element);
+  if (!transform) {
+    return new goog.math.Coordinate(0, 0);
+  }
+  var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
+  if (!matches) {
+    return new goog.math.Coordinate(0, 0);
   }
+  return new goog.math.Coordinate(parseFloat(matches[1]),
+                                  parseFloat(matches[2]));
 };
 
-goog.provide('ol.control');
+goog.provide('ol.MapEvent');
+goog.provide('ol.MapEventType');
 
-goog.require('ol.Collection');
-goog.require('ol.control.Attribution');
-goog.require('ol.control.Rotate');
-goog.require('ol.control.Zoom');
+goog.require('goog.events.Event');
 
 
 /**
- * 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
+ * @enum {string}
  */
-ol.control.defaults = function(opt_options) {
+ol.MapEventType = {
+  /**
+   * Triggered after a map frame is rendered.
+   * @event ol.MapEvent#postrender
+   * @api
+   */
+  POSTRENDER: 'postrender',
+  /**
+   * Triggered after the map is moved.
+   * @event ol.MapEvent#moveend
+   * @api
+   */
+  MOVEEND: 'moveend'
+};
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  var controls = new ol.Collection();
 
-  var zoomControl = goog.isDef(options.zoom) ?
-      options.zoom : true;
-  if (zoomControl) {
-    controls.push(new ol.control.Zoom(options.zoomOptions));
-  }
+/**
+ * @classdesc
+ * Events emitted as map events are instances of this type.
+ * See {@link ol.Map} for which events trigger a map event.
+ *
+ * @constructor
+ * @extends {goog.events.Event}
+ * @implements {oli.MapEvent}
+ * @param {string} type Event type.
+ * @param {ol.Map} map Map.
+ * @param {?olx.FrameState=} opt_frameState Frame state.
+ */
+ol.MapEvent = function(type, map, opt_frameState) {
 
-  var rotateControl = goog.isDef(options.rotate) ?
-      options.rotate : true;
-  if (rotateControl) {
-    controls.push(new ol.control.Rotate(options.rotateOptions));
-  }
+  goog.base(this, type);
 
-  var attributionControl = goog.isDef(options.attribution) ?
-      options.attribution : true;
-  if (attributionControl) {
-    controls.push(new ol.control.Attribution(options.attributionOptions));
-  }
+  /**
+   * The map where the event occurred.
+   * @type {ol.Map}
+   * @api stable
+   */
+  this.map = map;
 
-  return controls;
+  /**
+   * The frame state at the time of the event.
+   * @type {?olx.FrameState}
+   * @api
+   */
+  this.frameState = goog.isDef(opt_frameState) ? opt_frameState : null;
 
 };
+goog.inherits(ol.MapEvent, goog.events.Event);
+
+goog.provide('ol.control.Control');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
+
 
-// Copyright 2012 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Functions for managing full screen status of the DOM.
+ * @classdesc
+ * A control is a visible widget with a DOM element in a fixed position on the
+ * screen. They can involve user input (buttons), or be informational only;
+ * the position is determined using CSS. By default these are placed in the
+ * container with CSS class name `ol-overlaycontainer-stopevent`, but can use
+ * any outside DOM element.
+ *
+ * This is the base class for controls. You can use it for simple custom
+ * controls by creating the element with listeners, creating an instance:
+ * ```js
+ * var myControl = new ol.control.Control({element: myElement});
+ * ```
+ * and then adding this to the map.
  *
+ * The main advantage of having this as a control rather than a simple separate
+ * DOM element is that preventing propagation is handled for you. Controls
+ * will also be `ol.Object`s in a `ol.Collection`, so you can use their
+ * methods.
+ *
+ * You can also extend this base for your own control class. See
+ * examples/custom-controls for an example of how to do this.
+ *
+ * @constructor
+ * @extends {ol.Object}
+ * @implements {oli.control.Control}
+ * @param {olx.control.ControlOptions} options Control options.
+ * @api stable
  */
+ol.control.Control = function(options) {
 
-goog.provide('goog.dom.fullscreen');
-goog.provide('goog.dom.fullscreen.EventType');
+  goog.base(this);
 
-goog.require('goog.dom');
-goog.require('goog.userAgent');
+  /**
+   * @protected
+   * @type {Element}
+   */
+  this.element = goog.isDef(options.element) ? options.element : null;
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.target_ = goog.isDef(options.target) ?
+      goog.dom.getElement(options.target) : null;
+
+  /**
+   * @private
+   * @type {ol.Map}
+   */
+  this.map_ = null;
+
+  /**
+   * @protected
+   * @type {!Array.<?number>}
+   */
+  this.listenerKeys = [];
+
+  /**
+   * @type {function(ol.MapEvent)}
+   */
+  this.render = goog.isDef(options.render) ? options.render : goog.nullFunction;
+
+};
+goog.inherits(ol.control.Control, ol.Object);
 
 
 /**
- * Event types for full screen.
- * @enum {string}
+ * @inheritDoc
  */
-goog.dom.fullscreen.EventType = {
-  /** Dispatched by the Document when the fullscreen status changes. */
-  CHANGE: (function() {
-    if (goog.userAgent.WEBKIT) {
-      return 'webkitfullscreenchange';
-    }
-    if (goog.userAgent.GECKO) {
-      return 'mozfullscreenchange';
-    }
-    if (goog.userAgent.IE) {
-      return 'MSFullscreenChange';
-    }
-    // Opera 12-14, and W3C standard (Draft):
-    // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
-    return 'fullscreenchange';
-  })()
+ol.control.Control.prototype.disposeInternal = function() {
+  goog.dom.removeNode(this.element);
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * Determines if full screen is supported.
- * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
- *     queried. If not provided, use the current DOM.
- * @return {boolean} True iff full screen is supported.
+ * Get the map associated with this control.
+ * @return {ol.Map} Map.
+ * @api stable
  */
-goog.dom.fullscreen.isSupported = function(opt_domHelper) {
-  var doc = goog.dom.fullscreen.getDocument_(opt_domHelper);
-  var body = doc.body;
-  return !!(body.webkitRequestFullscreen ||
-      (body.mozRequestFullScreen && doc.mozFullScreenEnabled) ||
-      (body.msRequestFullscreen && doc.msFullscreenEnabled) ||
-      (body.requestFullscreen && doc.fullscreenEnabled));
+ol.control.Control.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
- * Requests putting the element in full screen.
- * @param {!Element} element The element to put full screen.
+ * 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
  */
-goog.dom.fullscreen.requestFullScreen = function(element) {
-  if (element.webkitRequestFullscreen) {
-    element.webkitRequestFullscreen();
-  } else if (element.mozRequestFullScreen) {
-    element.mozRequestFullScreen();
-  } else if (element.msRequestFullscreen) {
-    element.msRequestFullscreen();
-  } else if (element.requestFullscreen) {
-    element.requestFullscreen();
+ol.control.Control.prototype.setMap = function(map) {
+  if (!goog.isNull(this.map_)) {
+    goog.dom.removeNode(this.element);
+  }
+  if (!goog.array.isEmpty(this.listenerKeys)) {
+    goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey);
+    this.listenerKeys.length = 0;
+  }
+  this.map_ = map;
+  if (!goog.isNull(this.map_)) {
+    var target = !goog.isNull(this.target_) ?
+        this.target_ : map.getOverlayContainerStopEvent();
+    goog.dom.appendChild(target, this.element);
+    if (this.render !== goog.nullFunction) {
+      this.listenerKeys.push(goog.events.listen(map,
+          ol.MapEventType.POSTRENDER, this.render, false, this));
+    }
+    map.render();
   }
 };
 
+goog.provide('ol.css');
+
 
 /**
- * Requests putting the element in full screen with full keyboard access.
- * @param {!Element} element The element to put full screen.
+ * The CSS class for hidden feature.
+ *
+ * @const
+ * @type {string}
  */
-goog.dom.fullscreen.requestFullScreenWithKeys = function(
-    element) {
-  if (element.mozRequestFullScreenWithKeys) {
-    element.mozRequestFullScreenWithKeys();
-  } else if (element.webkitRequestFullscreen) {
-    element.webkitRequestFullscreen();
-  } else {
-    goog.dom.fullscreen.requestFullScreen(element);
-  }
-};
+ol.css.CLASS_HIDDEN = 'ol-hidden';
 
 
 /**
- * Exits full screen.
- * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
- *     queried. If not provided, use the current DOM.
+ * The CSS class that we'll give the DOM elements to have them unselectable.
+ *
+ * @const
+ * @type {string}
  */
-goog.dom.fullscreen.exitFullScreen = function(opt_domHelper) {
-  var doc = goog.dom.fullscreen.getDocument_(opt_domHelper);
-  if (doc.webkitCancelFullScreen) {
-    doc.webkitCancelFullScreen();
-  } else if (doc.mozCancelFullScreen) {
-    doc.mozCancelFullScreen();
-  } else if (doc.msExitFullscreen) {
-    doc.msExitFullscreen();
-  } else if (doc.exitFullscreen) {
-    doc.exitFullscreen();
-  }
-};
+ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
 
 
 /**
- * Determines if the document is full screen.
- * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
- *     queried. If not provided, use the current DOM.
- * @return {boolean} Whether the document is full screen.
+ * The CSS class for unsupported feature.
+ *
+ * @const
+ * @type {string}
  */
-goog.dom.fullscreen.isFullScreen = function(opt_domHelper) {
-  var doc = goog.dom.fullscreen.getDocument_(opt_domHelper);
-  // IE 11 doesn't have similar boolean property, so check whether
-  // document.msFullscreenElement is null instead.
-  return !!(doc.webkitIsFullScreen || doc.mozFullScreen ||
-      doc.msFullscreenElement || doc.fullscreenElement);
-};
+ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
 
 
 /**
- * Gets the document object of the dom.
- * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
- *     queried. If not provided, use the current DOM.
- * @return {!Document} The dom document.
- * @private
+ * The CSS class for controls.
+ *
+ * @const
+ * @type {string}
  */
-goog.dom.fullscreen.getDocument_ = function(opt_domHelper) {
-  return opt_domHelper ?
-      opt_domHelper.getDocument() :
-      goog.dom.getDomHelper().getDocument();
-};
+ol.css.CLASS_CONTROL = 'ol-control';
 
-goog.provide('ol.control.FullScreen');
+// FIXME handle date line wrap
+
+goog.provide('ol.control.Attribution');
 
-goog.require('goog.asserts');
 goog.require('goog.dom');
 goog.require('goog.dom.TagName');
 goog.require('goog.dom.classlist');
-goog.require('goog.dom.fullscreen');
-goog.require('goog.dom.fullscreen.EventType');
 goog.require('goog.events');
 goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('ol.Attribution');
 goog.require('ol.control.Control');
 goog.require('ol.css');
-goog.require('ol.pointer.PointerEventHandler');
 
 
 
 /**
  * @classdesc
- * Provides a button that when clicked fills up the full screen with the map.
- * When in full screen mode, a close button is shown to exit full screen mode.
- * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to
- * toggle the map in full screen mode.
- *
+ * 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.FullScreenOptions=} opt_options Options.
+ * @param {olx.control.AttributionOptions=} opt_options Attribution options.
  * @api stable
  */
-ol.control.FullScreen = function(opt_options) {
+ol.control.Attribution = function(opt_options) {
 
   var options = goog.isDef(opt_options) ? opt_options : {};
 
   /**
    * @private
-   * @type {string}
+   * @type {Element}
    */
-  this.cssClassName_ = goog.isDef(options.className) ?
-      options.className : 'ol-full-screen';
+  this.ulElement_ = goog.dom.createElement(goog.dom.TagName.UL);
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.logoLi_ = goog.dom.createElement(goog.dom.TagName.LI);
+
+  goog.dom.appendChild(this.ulElement_, this.logoLi_);
+  goog.style.setElementShown(this.logoLi_, false);
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.collapsed_ = goog.isDef(options.collapsed) ? options.collapsed : true;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.collapsible_ = goog.isDef(options.collapsible) ?
+      options.collapsible : true;
+
+  if (!this.collapsible_) {
+    this.collapsed_ = false;
+  }
+
+  var className = goog.isDef(options.className) ?
+      options.className : 'ol-attribution';
 
   var tipLabel = goog.isDef(options.tipLabel) ?
-      options.tipLabel : 'Toggle full-screen';
-  var tip = goog.dom.createDom(goog.dom.TagName.SPAN, {
-    'role' : 'tooltip'
-  }, tipLabel);
+      options.tipLabel : 'Attributions';
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.collapseLabel_ = goog.isDef(options.collapseLabel) ?
+      options.collapseLabel : '\u00BB';
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.label_ = goog.isDef(options.label) ? options.label : 'i';
+  var label = goog.dom.createDom(goog.dom.TagName.SPAN, {},
+      (this.collapsible_ && !this.collapsed_) ?
+      this.collapseLabel_ : this.label_);
+
 
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.labelSpan_ = label;
   var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen() +
-        ' ol-has-tooltip'
-  });
-  goog.dom.appendChild(button, tip);
-  var buttonHandler = new ol.pointer.PointerEventHandler(button);
-  this.registerDisposable(buttonHandler);
-  goog.events.listen(buttonHandler,
-      ol.pointer.EventType.POINTERUP, this.handlePointerUp_, false, this);
+    'type': 'button',
+    'title': tipLabel
+  }, this.labelSpan_);
+
   goog.events.listen(button, goog.events.EventType.CLICK,
       this.handleClick_, false, this);
 
@@ -32132,17 +33619,19 @@ ol.control.FullScreen = function(opt_options) {
     this.blur();
   }, false);
 
-  goog.events.listen(goog.global.document,
-      goog.dom.fullscreen.EventType.CHANGE,
-      this.handleFullScreenChange_, false, this);
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL +
+      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
+      (this.collapsible_ ? '' : ' ol-uncollapsible');
+  var element = goog.dom.createDom(goog.dom.TagName.DIV,
+      cssClasses, this.ulElement_, button);
 
-  var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE +
-      ' ' + ol.css.CLASS_CONTROL +
-      (!goog.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : '');
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
+  var render = goog.isDef(options.render) ?
+      options.render : ol.control.Attribution.render;
 
   goog.base(this, {
     element: element,
+    render: render,
     target: options.target
   });
 
@@ -32150,1340 +33639,1172 @@ ol.control.FullScreen = function(opt_options) {
    * @private
    * @type {boolean}
    */
-  this.keys_ = goog.isDef(options.keys) ? options.keys : false;
+  this.renderedVisible_ = true;
+
+  /**
+   * @private
+   * @type {Object.<string, Element>}
+   */
+  this.attributionElements_ = {};
+
+  /**
+   * @private
+   * @type {Object.<string, boolean>}
+   */
+  this.attributionElementRenderedVisible_ = {};
+
+  /**
+   * @private
+   * @type {Object.<string, Element>}
+   */
+  this.logoElements_ = {};
 
 };
-goog.inherits(ol.control.FullScreen, ol.control.Control);
+goog.inherits(ol.control.Attribution, ol.control.Control);
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * @param {?olx.FrameState} frameState Frame state.
+ * @return {Array.<Object.<string, ol.Attribution>>} Attributions.
  */
-ol.control.FullScreen.prototype.handleClick_ = function(event) {
-  if (event.screenX !== 0 && event.screenY !== 0) {
-    return;
+ol.control.Attribution.prototype.getSourceAttributions = function(frameState) {
+  var i, ii, j, jj, tileRanges, source, sourceAttribution,
+      sourceAttributionKey, sourceAttributions, sourceKey;
+  var layerStatesArray = frameState.layerStatesArray;
+  /** @type {Object.<string, ol.Attribution>} */
+  var attributions = goog.object.clone(frameState.attributions);
+  /** @type {Object.<string, ol.Attribution>} */
+  var hiddenAttributions = {};
+  for (i = 0, ii = layerStatesArray.length; i < ii; i++) {
+    source = layerStatesArray[i].layer.getSource();
+    if (goog.isNull(source)) {
+      continue;
+    }
+    sourceKey = goog.getUid(source).toString();
+    sourceAttributions = source.getAttributions();
+    if (goog.isNull(sourceAttributions)) {
+      continue;
+    }
+    for (j = 0, jj = sourceAttributions.length; j < jj; j++) {
+      sourceAttribution = sourceAttributions[j];
+      sourceAttributionKey = goog.getUid(sourceAttribution).toString();
+      if (sourceAttributionKey in attributions) {
+        continue;
+      }
+      tileRanges = frameState.usedTiles[sourceKey];
+      if (goog.isDef(tileRanges) &&
+          sourceAttribution.intersectsAnyTileRange(tileRanges)) {
+        if (sourceAttributionKey in hiddenAttributions) {
+          delete hiddenAttributions[sourceAttributionKey];
+        }
+        attributions[sourceAttributionKey] = sourceAttribution;
+      }
+      else {
+        hiddenAttributions[sourceAttributionKey] = sourceAttribution;
+      }
+    }
   }
-  this.handleFullScreen_();
+  return [attributions, hiddenAttributions];
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent The event to handle
- * @private
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Attribution}
+ * @api
  */
-ol.control.FullScreen.prototype.handlePointerUp_ = function(pointerEvent) {
-  pointerEvent.browserEvent.preventDefault();
-  this.handleFullScreen_();
+ol.control.Attribution.render = function(mapEvent) {
+  this.updateElement_(mapEvent.frameState);
 };
 
 
 /**
  * @private
+ * @param {?olx.FrameState} frameState Frame state.
  */
-ol.control.FullScreen.prototype.handleFullScreen_ = function() {
-  if (!goog.dom.fullscreen.isSupported()) {
+ol.control.Attribution.prototype.updateElement_ = function(frameState) {
+
+  if (goog.isNull(frameState)) {
+    if (this.renderedVisible_) {
+      goog.style.setElementShown(this.element, false);
+      this.renderedVisible_ = false;
+    }
     return;
   }
-  var map = this.getMap();
-  if (goog.isNull(map)) {
-    return;
+
+  var attributions = this.getSourceAttributions(frameState);
+  /** @type {Object.<string, ol.Attribution>} */
+  var visibleAttributions = attributions[0];
+  /** @type {Object.<string, ol.Attribution>} */
+  var hiddenAttributions = attributions[1];
+
+  var attributionElement, attributionKey;
+  for (attributionKey in this.attributionElements_) {
+    if (attributionKey in visibleAttributions) {
+      if (!this.attributionElementRenderedVisible_[attributionKey]) {
+        goog.style.setElementShown(
+            this.attributionElements_[attributionKey], true);
+        this.attributionElementRenderedVisible_[attributionKey] = true;
+      }
+      delete visibleAttributions[attributionKey];
+    }
+    else if (attributionKey in hiddenAttributions) {
+      if (this.attributionElementRenderedVisible_[attributionKey]) {
+        goog.style.setElementShown(
+            this.attributionElements_[attributionKey], false);
+        delete this.attributionElementRenderedVisible_[attributionKey];
+      }
+      delete hiddenAttributions[attributionKey];
+    }
+    else {
+      goog.dom.removeNode(this.attributionElements_[attributionKey]);
+      delete this.attributionElements_[attributionKey];
+      delete this.attributionElementRenderedVisible_[attributionKey];
+    }
   }
-  if (goog.dom.fullscreen.isFullScreen()) {
-    goog.dom.fullscreen.exitFullScreen();
+  for (attributionKey in visibleAttributions) {
+    attributionElement = goog.dom.createElement(goog.dom.TagName.LI);
+    attributionElement.innerHTML =
+        visibleAttributions[attributionKey].getHTML();
+    goog.dom.appendChild(this.ulElement_, attributionElement);
+    this.attributionElements_[attributionKey] = attributionElement;
+    this.attributionElementRenderedVisible_[attributionKey] = true;
+  }
+  for (attributionKey in hiddenAttributions) {
+    attributionElement = goog.dom.createElement(goog.dom.TagName.LI);
+    attributionElement.innerHTML =
+        hiddenAttributions[attributionKey].getHTML();
+    goog.style.setElementShown(attributionElement, false);
+    goog.dom.appendChild(this.ulElement_, attributionElement);
+    this.attributionElements_[attributionKey] = attributionElement;
+  }
+
+  var renderVisible =
+      !goog.object.isEmpty(this.attributionElementRenderedVisible_) ||
+      !goog.object.isEmpty(frameState.logos);
+  if (this.renderedVisible_ != renderVisible) {
+    goog.style.setElementShown(this.element, renderVisible);
+    this.renderedVisible_ = renderVisible;
+  }
+  if (renderVisible &&
+      goog.object.isEmpty(this.attributionElementRenderedVisible_)) {
+    goog.dom.classlist.add(this.element, 'ol-logo-only');
   } else {
-    var target = map.getTarget();
-    goog.asserts.assert(goog.isDefAndNotNull(target));
-    var element = goog.dom.getElement(target);
-    goog.asserts.assert(goog.isDefAndNotNull(element));
-    if (this.keys_) {
-      goog.dom.fullscreen.requestFullScreenWithKeys(element);
-    } else {
-      goog.dom.fullscreen.requestFullScreen(element);
-    }
+    goog.dom.classlist.remove(this.element, 'ol-logo-only');
   }
+
+  this.insertLogos_(frameState);
+
 };
 
 
 /**
+ * @param {?olx.FrameState} frameState Frame state.
  * @private
  */
-ol.control.FullScreen.prototype.handleFullScreenChange_ = function() {
-  var opened = this.cssClassName_ + '-true';
-  var closed = this.cssClassName_ + '-false';
-  var anchor = goog.dom.getFirstElementChild(this.element);
-  var map = this.getMap();
-  if (goog.dom.fullscreen.isFullScreen()) {
-    goog.dom.classlist.swap(anchor, closed, opened);
-  } else {
-    goog.dom.classlist.swap(anchor, opened, closed);
+ol.control.Attribution.prototype.insertLogos_ = function(frameState) {
+
+  var logo;
+  var logos = frameState.logos;
+  var logoElements = this.logoElements_;
+
+  for (logo in logoElements) {
+    if (!(logo in logos)) {
+      goog.dom.removeNode(logoElements[logo]);
+      delete logoElements[logo];
+    }
   }
-  if (!goog.isNull(map)) {
-    map.updateSize();
+
+  var image, logoElement, logoKey;
+  for (logoKey in logos) {
+    if (!(logoKey in logoElements)) {
+      image = new Image();
+      image.src = logoKey;
+      var logoValue = logos[logoKey];
+      if (logoValue === '') {
+        logoElement = image;
+      } else {
+        logoElement = goog.dom.createDom(goog.dom.TagName.A, {
+          'href': logoValue
+        });
+        logoElement.appendChild(image);
+      }
+      goog.dom.appendChild(this.logoLi_, logoElement);
+      logoElements[logoKey] = logoElement;
+    }
   }
+
+  goog.style.setElementShown(this.logoLi_, !goog.object.isEmpty(logos));
+
 };
 
-goog.provide('ol.Pixel');
+
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.Attribution.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleToggle_();
+};
 
 
 /**
- * An array with two elements, representing a pixel. The first element is the
- * x-coordinate, the second the y-coordinate of the pixel.
- * @typedef {Array.<number>}
+ * @private
+ */
+ol.control.Attribution.prototype.handleToggle_ = function() {
+  goog.dom.classlist.toggle(this.element, 'ol-collapsed');
+  goog.dom.setTextContent(this.labelSpan_,
+      (this.collapsed_) ? this.collapseLabel_ : this.label_);
+  this.collapsed_ = !this.collapsed_;
+};
+
+
+/**
+ * @return {boolean} True if the widget is collapsible.
  * @api stable
  */
-ol.Pixel;
+ol.control.Attribution.prototype.getCollapsible = function() {
+  return this.collapsible_;
+};
 
-// FIXME should listen on appropriate pane, once it is defined
 
-goog.provide('ol.control.MousePosition');
+/**
+ * @param {boolean} collapsible True if the widget is collapsible.
+ * @api stable
+ */
+ol.control.Attribution.prototype.setCollapsible = function(collapsible) {
+  if (this.collapsible_ === collapsible) {
+    return;
+  }
+  this.collapsible_ = collapsible;
+  goog.dom.classlist.toggle(this.element, 'ol-uncollapsible');
+  if (!collapsible && this.collapsed_) {
+    this.handleToggle_();
+  }
+};
 
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.style');
-goog.require('ol.CoordinateFormatType');
-goog.require('ol.Object');
-goog.require('ol.Pixel');
-goog.require('ol.TransformFunction');
-goog.require('ol.control.Control');
-goog.require('ol.proj');
-goog.require('ol.proj.Projection');
+
+/**
+ * @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_();
+};
 
 
 /**
- * @enum {string}
+ * @return {boolean} True if the widget is collapsed.
+ * @api stable
  */
-ol.control.MousePositionProperty = {
-  PROJECTION: 'projection',
-  COORDINATE_FORMAT: 'coordinateFormat'
+ol.control.Attribution.prototype.getCollapsed = function() {
+  return this.collapsed_;
 };
 
+goog.provide('ol.control.Rotate');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.math');
+goog.require('ol.animation');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.easing');
+
 
 
 /**
  * @classdesc
- * A control to show the 2D coordinates of the mouse cursor. By default, these
- * are in the view projection, but can be in any supported projection.
- * By default the control is shown in the top right corner of the map, but this
- * can be changed by using the css selector `.ol-mouse-position`.
+ * A 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.MousePositionOptions=} opt_options Mouse position
- *     options.
+ * @param {olx.control.RotateOptions=} opt_options Rotate options.
  * @api stable
  */
-ol.control.MousePosition = function(opt_options) {
+ol.control.Rotate = function(opt_options) {
 
   var options = goog.isDef(opt_options) ? opt_options : {};
 
   var className = goog.isDef(options.className) ?
-      options.className : 'ol-mouse-position';
+      options.className : 'ol-rotate';
 
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, {
-    'class': className
-  });
+  /**
+   * @type {Element}
+   * @private
+   */
+  this.label_ = goog.dom.createDom(goog.dom.TagName.SPAN,
+      'ol-compass', goog.isDef(options.label) ? options.label : '\u21E7');
+
+  var tipLabel = goog.isDef(options.tipLabel) ?
+      options.tipLabel : 'Reset rotation';
+
+  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
+    'class': className + '-reset',
+    'type' : 'button',
+    'title': tipLabel
+  }, this.label_);
+
+  goog.events.listen(button, goog.events.EventType.CLICK,
+      ol.control.Rotate.prototype.handleClick_, false, this);
+
+  goog.events.listen(button, [
+    goog.events.EventType.MOUSEOUT,
+    goog.events.EventType.FOCUSOUT
+  ], function() {
+    this.blur();
+  }, false);
+
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL;
+  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
+
+  var render = goog.isDef(options.render) ?
+      options.render : ol.control.Rotate.render;
 
   goog.base(this, {
     element: element,
+    render: render,
     target: options.target
   });
 
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.control.MousePositionProperty.PROJECTION),
-      this.handleProjectionChanged_, false, this);
-
-  if (goog.isDef(options.coordinateFormat)) {
-    this.setCoordinateFormat(options.coordinateFormat);
-  }
-  if (goog.isDef(options.projection)) {
-    this.setProjection(ol.proj.get(options.projection));
-  }
-
   /**
+   * @type {number}
    * @private
-   * @type {string}
    */
-  this.undefinedHTML_ = goog.isDef(options.undefinedHTML) ?
-      options.undefinedHTML : '';
+  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
 
   /**
+   * @type {boolean}
    * @private
-   * @type {string}
    */
-  this.renderedHTML_ = element.innerHTML;
+  this.autoHide_ = goog.isDef(options.autoHide) ? options.autoHide : true;
 
   /**
    * @private
-   * @type {ol.proj.Projection}
+   * @type {number|undefined}
    */
-  this.mapProjection_ = null;
+  this.rotation_ = undefined;
 
-  /**
-   * @private
-   * @type {?ol.TransformFunction}
-   */
-  this.transform_ = null;
+  if (this.autoHide_) {
+    goog.dom.classlist.add(this.element, ol.css.CLASS_HIDDEN);
+  }
 
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.lastMouseMovePixel_ = null;
+};
+goog.inherits(ol.control.Rotate, ol.control.Control);
 
+
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.Rotate.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.resetNorth_();
 };
-goog.inherits(ol.control.MousePosition, ol.control.Control);
 
 
 /**
- * @inheritDoc
+ * @private
  */
-ol.control.MousePosition.prototype.handleMapPostrender = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (goog.isNull(frameState)) {
-    this.mapProjection_ = null;
-  } else {
-    if (this.mapProjection_ != frameState.viewState.projection) {
-      this.mapProjection_ = frameState.viewState.projection;
-      this.transform_ = null;
+ol.control.Rotate.prototype.resetNorth_ = function() {
+  var map = this.getMap();
+  var view = map.getView();
+  if (goog.isNull(view)) {
+    // the map does not have a view, so we can't act
+    // upon it
+    return;
+  }
+  var currentRotation = view.getRotation();
+  while (currentRotation < -Math.PI) {
+    currentRotation += 2 * Math.PI;
+  }
+  while (currentRotation > Math.PI) {
+    currentRotation -= 2 * Math.PI;
+  }
+  if (goog.isDef(currentRotation)) {
+    if (this.duration_ > 0) {
+      map.beforeRender(ol.animation.rotate({
+        rotation: currentRotation,
+        duration: this.duration_,
+        easing: ol.easing.easeOut
+      }));
     }
+    view.setRotation(0);
   }
-  this.updateHTML_(this.lastMouseMovePixel_);
 };
 
 
 /**
- * @private
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Rotate}
+ * @api
  */
-ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
-  this.transform_ = null;
+ol.control.Rotate.render = function(mapEvent) {
+  var frameState = mapEvent.frameState;
+  if (goog.isNull(frameState)) {
+    return;
+  }
+  var rotation = frameState.viewState.rotation;
+  if (rotation != this.rotation_) {
+    var transform = 'rotate(' + goog.math.toDegrees(rotation) + 'deg)';
+    if (this.autoHide_) {
+      goog.dom.classlist.enable(
+          this.element, ol.css.CLASS_HIDDEN, rotation === 0);
+    }
+    this.label_.style.msTransform = transform;
+    this.label_.style.webkitTransform = transform;
+    this.label_.style.transform = transform;
+  }
+  this.rotation_ = rotation;
 };
 
+goog.provide('ol.control.Zoom');
+
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.animation');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.easing');
 
-/**
- * @return {ol.CoordinateFormatType|undefined} The format to render the current
- *     position in.
- * @observable
- * @api stable
- */
-ol.control.MousePosition.prototype.getCoordinateFormat = function() {
-  return /** @type {ol.CoordinateFormatType|undefined} */ (
-      this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT));
-};
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getCoordinateFormat',
-    ol.control.MousePosition.prototype.getCoordinateFormat);
 
 
 /**
- * @return {ol.proj.Projection|undefined} The projection to report mouse
- *     position in.
- * @observable
+ * @classdesc
+ * A control with 2 buttons, one for zoom in and one for zoom out.
+ * This control is one of the default controls of a map. To style this control
+ * use css selectors `.ol-zoom-in` and `.ol-zoom-out`.
+ *
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.ZoomOptions=} opt_options Zoom options.
  * @api stable
  */
-ol.control.MousePosition.prototype.getProjection = function() {
-  return /** @type {ol.proj.Projection|undefined} */ (
-      this.get(ol.control.MousePositionProperty.PROJECTION));
-};
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getProjection',
-    ol.control.MousePosition.prototype.getProjection);
+ol.control.Zoom = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @protected
- */
-ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) {
-  var map = this.getMap();
-  var eventPosition = goog.style.getRelativePosition(
-      browserEvent, map.getViewport());
-  this.lastMouseMovePixel_ = [eventPosition.x, eventPosition.y];
-  this.updateHTML_(this.lastMouseMovePixel_);
-};
+  var className = goog.isDef(options.className) ? options.className : 'ol-zoom';
 
+  var delta = goog.isDef(options.delta) ? options.delta : 1;
 
-/**
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @protected
- */
-ol.control.MousePosition.prototype.handleMouseOut = function(browserEvent) {
-  this.updateHTML_(null);
-  this.lastMouseMovePixel_ = null;
-};
+  var zoomInLabel = goog.isDef(options.zoomInLabel) ?
+      options.zoomInLabel : '+';
+  var zoomOutLabel = goog.isDef(options.zoomOutLabel) ?
+      options.zoomOutLabel : '\u2212';
 
+  var zoomInTipLabel = goog.isDef(options.zoomInTipLabel) ?
+      options.zoomInTipLabel : 'Zoom in';
+  var zoomOutTipLabel = goog.isDef(options.zoomOutTipLabel) ?
+      options.zoomOutTipLabel : 'Zoom out';
 
-/**
- * @inheritDoc
- * @api stable
- */
-ol.control.MousePosition.prototype.setMap = function(map) {
-  goog.base(this, 'setMap', map);
-  if (!goog.isNull(map)) {
-    var viewport = map.getViewport();
-    this.listenerKeys.push(
-        goog.events.listen(viewport, goog.events.EventType.MOUSEMOVE,
-            this.handleMouseMove, false, this),
-        goog.events.listen(viewport, goog.events.EventType.MOUSEOUT,
-            this.handleMouseOut, false, this)
-    );
-  }
-};
+  var inElement = goog.dom.createDom(goog.dom.TagName.BUTTON, {
+    'class': className + '-in',
+    'type' : 'button',
+    'title': zoomInTipLabel
+  }, zoomInLabel);
 
+  goog.events.listen(inElement,
+      goog.events.EventType.CLICK, goog.partial(
+          ol.control.Zoom.prototype.handleClick_, delta), false, this);
 
-/**
- * @param {ol.CoordinateFormatType} format The format to render the current
- *     position in.
- * @observable
- * @api stable
- */
-ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
-  this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format);
-};
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'setCoordinateFormat',
-    ol.control.MousePosition.prototype.setCoordinateFormat);
+  goog.events.listen(inElement, [
+    goog.events.EventType.MOUSEOUT,
+    goog.events.EventType.FOCUSOUT
+  ], function() {
+    this.blur();
+  }, false);
 
+  var outElement = goog.dom.createDom(goog.dom.TagName.BUTTON, {
+    'class': className + '-out',
+    'type' : 'button',
+    'title': zoomOutTipLabel
+  }, zoomOutLabel);
 
-/**
- * @param {ol.proj.Projection} projection The projection to report mouse
- *     position in.
- * @observable
- * @api stable
- */
-ol.control.MousePosition.prototype.setProjection = function(projection) {
-  this.set(ol.control.MousePositionProperty.PROJECTION, projection);
-};
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'setProjection',
-    ol.control.MousePosition.prototype.setProjection);
+  goog.events.listen(outElement,
+      goog.events.EventType.CLICK, goog.partial(
+          ol.control.Zoom.prototype.handleClick_, -delta), false, this);
 
+  goog.events.listen(outElement, [
+    goog.events.EventType.MOUSEOUT,
+    goog.events.EventType.FOCUSOUT
+  ], function() {
+    this.blur();
+  }, false);
 
-/**
- * @param {?ol.Pixel} pixel Pixel.
- * @private
- */
-ol.control.MousePosition.prototype.updateHTML_ = function(pixel) {
-  var html = this.undefinedHTML_;
-  if (!goog.isNull(pixel) && !goog.isNull(this.mapProjection_)) {
-    if (goog.isNull(this.transform_)) {
-      var projection = this.getProjection();
-      if (goog.isDef(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 (!goog.isNull(coordinate)) {
-      this.transform_(coordinate, coordinate);
-      var coordinateFormat = this.getCoordinateFormat();
-      if (goog.isDef(coordinateFormat)) {
-        html = coordinateFormat(coordinate);
-      } else {
-        html = coordinate.toString();
-      }
-    }
-  }
-  if (!goog.isDef(this.renderedHTML_) || html != this.renderedHTML_) {
-    this.element.innerHTML = html;
-    this.renderedHTML_ = html;
-  }
-};
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL;
+  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, inElement,
+      outElement);
 
-goog.provide('ol.control.ScaleLine');
-goog.provide('ol.control.ScaleLineProperty');
-goog.provide('ol.control.ScaleLineUnits');
+  goog.base(this, {
+    element: element,
+    target: options.target
+  });
 
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.math');
-goog.require('goog.style');
-goog.require('ol.Object');
-goog.require('ol.TransformFunction');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.proj');
-goog.require('ol.proj.Units');
-goog.require('ol.sphere.NORMAL');
+  /**
+   * @type {number}
+   * @private
+   */
+  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
+
+};
+goog.inherits(ol.control.Zoom, ol.control.Control);
 
 
 /**
- * @enum {string}
+ * @param {number} delta Zoom delta.
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
  */
-ol.control.ScaleLineProperty = {
-  UNITS: 'units'
+ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
+  event.preventDefault();
+  this.zoomByDelta_(delta);
 };
 
 
 /**
- * Units for the scale line. Supported values are `'degrees'`, `'imperial'`,
- * `'nautical'`, `'metric'`, `'us'`.
- * @enum {string}
- * @api stable
+ * @param {number} delta Zoom delta.
+ * @private
  */
-ol.control.ScaleLineUnits = {
-  DEGREES: 'degrees',
-  IMPERIAL: 'imperial',
-  NAUTICAL: 'nautical',
-  METRIC: 'metric',
-  US: 'us'
+ol.control.Zoom.prototype.zoomByDelta_ = function(delta) {
+  var map = this.getMap();
+  var view = map.getView();
+  if (goog.isNull(view)) {
+    // the map does not have a view, so we can't act
+    // upon it
+    return;
+  }
+  var currentResolution = view.getResolution();
+  if (goog.isDef(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.Collection');
+goog.require('ol.control.Attribution');
+goog.require('ol.control.Rotate');
+goog.require('ol.control.Zoom');
 
 
 /**
- * @classdesc
- * A control displaying rough x-axis distances, calculated for the center of the
- * viewport.
- * No scale line will be shown when the x-axis distance cannot be calculated in
- * the view projection (e.g. at or beyond the poles in EPSG:4326).
- * By default the scale line will show in the bottom left portion of the map,
- * but this can be changed by using the css selector `.ol-scale-line`.
+ * 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}
  *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ScaleLineOptions=} opt_options Scale line options.
+ * @param {olx.control.DefaultsOptions=} opt_options Defaults options.
+ * @return {ol.Collection.<ol.control.Control>} Controls.
  * @api stable
  */
-ol.control.ScaleLine = function(opt_options) {
+ol.control.defaults = function(opt_options) {
 
   var options = goog.isDef(opt_options) ? opt_options : {};
 
-  var className = goog.isDef(options.className) ?
-      options.className : 'ol-scale-line';
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.innerElement_ = goog.dom.createDom(goog.dom.TagName.DIV, {
-    'class': className + '-inner'
-  });
+  var controls = new ol.Collection();
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.element_ = goog.dom.createDom(goog.dom.TagName.DIV, {
-    'class': className + ' ' + ol.css.CLASS_UNSELECTABLE
-  }, this.innerElement_);
+  var zoomControl = goog.isDef(options.zoom) ?
+      options.zoom : true;
+  if (zoomControl) {
+    controls.push(new ol.control.Zoom(options.zoomOptions));
+  }
 
-  /**
-   * @private
-   * @type {?olx.ViewState}
-   */
-  this.viewState_ = null;
+  var rotateControl = goog.isDef(options.rotate) ?
+      options.rotate : true;
+  if (rotateControl) {
+    controls.push(new ol.control.Rotate(options.rotateOptions));
+  }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minWidth_ = goog.isDef(options.minWidth) ? options.minWidth : 64;
+  var attributionControl = goog.isDef(options.attribution) ?
+      options.attribution : true;
+  if (attributionControl) {
+    controls.push(new ol.control.Attribution(options.attributionOptions));
+  }
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = false;
+  return controls;
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.renderedWidth_ = undefined;
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.renderedHTML_ = '';
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-  /**
-   * @private
-   * @type {?ol.TransformFunction}
-   */
-  this.toEPSG4326_ = null;
+/**
+ * @fileoverview Functions for managing full screen status of the DOM.
+ *
+ */
 
-  goog.base(this, {
-    element: this.element_,
-    target: options.target
-  });
+goog.provide('goog.dom.fullscreen');
+goog.provide('goog.dom.fullscreen.EventType');
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.control.ScaleLineProperty.UNITS),
-      this.handleUnitsChanged_, false, this);
+goog.require('goog.dom');
+goog.require('goog.userAgent');
 
-  this.setUnits(/** @type {ol.control.ScaleLineUnits} */ (options.units) ||
-      ol.control.ScaleLineUnits.METRIC);
 
+/**
+ * Event types for full screen.
+ * @enum {string}
+ */
+goog.dom.fullscreen.EventType = {
+  /** Dispatched by the Document when the fullscreen status changes. */
+  CHANGE: (function() {
+    if (goog.userAgent.WEBKIT) {
+      return 'webkitfullscreenchange';
+    }
+    if (goog.userAgent.GECKO) {
+      return 'mozfullscreenchange';
+    }
+    if (goog.userAgent.IE) {
+      return 'MSFullscreenChange';
+    }
+    // Opera 12-14, and W3C standard (Draft):
+    // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
+    return 'fullscreenchange';
+  })()
 };
-goog.inherits(ol.control.ScaleLine, ol.control.Control);
 
 
 /**
- * @const
- * @type {Array.<number>}
+ * Determines if full screen is supported.
+ * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
+ *     queried. If not provided, use the current DOM.
+ * @return {boolean} True iff full screen is supported.
  */
-ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5];
+goog.dom.fullscreen.isSupported = function(opt_domHelper) {
+  var doc = goog.dom.fullscreen.getDocument_(opt_domHelper);
+  var body = doc.body;
+  return !!(body.webkitRequestFullscreen ||
+      (body.mozRequestFullScreen && doc.mozFullScreenEnabled) ||
+      (body.msRequestFullscreen && doc.msFullscreenEnabled) ||
+      (body.requestFullscreen && doc.fullscreenEnabled));
+};
 
 
 /**
- * @return {ol.control.ScaleLineUnits|undefined} The units to use in the scale
- *     line.
- * @observable
- * @api stable
+ * Requests putting the element in full screen.
+ * @param {!Element} element The element to put full screen.
  */
-ol.control.ScaleLine.prototype.getUnits = function() {
-  return /** @type {ol.control.ScaleLineUnits|undefined} */ (
-      this.get(ol.control.ScaleLineProperty.UNITS));
+goog.dom.fullscreen.requestFullScreen = function(element) {
+  if (element.webkitRequestFullscreen) {
+    element.webkitRequestFullscreen();
+  } else if (element.mozRequestFullScreen) {
+    element.mozRequestFullScreen();
+  } else if (element.msRequestFullscreen) {
+    element.msRequestFullscreen();
+  } else if (element.requestFullscreen) {
+    element.requestFullscreen();
+  }
 };
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'getUnits',
-    ol.control.ScaleLine.prototype.getUnits);
 
 
 /**
- * @inheritDoc
+ * Requests putting the element in full screen with full keyboard access.
+ * @param {!Element} element The element to put full screen.
  */
-ol.control.ScaleLine.prototype.handleMapPostrender = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (goog.isNull(frameState)) {
-    this.viewState_ = null;
+goog.dom.fullscreen.requestFullScreenWithKeys = function(
+    element) {
+  if (element.mozRequestFullScreenWithKeys) {
+    element.mozRequestFullScreenWithKeys();
+  } else if (element.webkitRequestFullscreen) {
+    element.webkitRequestFullscreen();
   } else {
-    this.viewState_ = frameState.viewState;
+    goog.dom.fullscreen.requestFullScreen(element);
   }
-  this.updateElement_();
 };
 
 
 /**
- * @private
+ * Exits full screen.
+ * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
+ *     queried. If not provided, use the current DOM.
  */
-ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() {
-  this.updateElement_();
+goog.dom.fullscreen.exitFullScreen = function(opt_domHelper) {
+  var doc = goog.dom.fullscreen.getDocument_(opt_domHelper);
+  if (doc.webkitCancelFullScreen) {
+    doc.webkitCancelFullScreen();
+  } else if (doc.mozCancelFullScreen) {
+    doc.mozCancelFullScreen();
+  } else if (doc.msExitFullscreen) {
+    doc.msExitFullscreen();
+  } else if (doc.exitFullscreen) {
+    doc.exitFullscreen();
+  }
 };
 
 
 /**
- * @param {ol.control.ScaleLineUnits} units The units to use in the scale line.
- * @observable
- * @api stable
+ * Determines if the document is full screen.
+ * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
+ *     queried. If not provided, use the current DOM.
+ * @return {boolean} Whether the document is full screen.
  */
-ol.control.ScaleLine.prototype.setUnits = function(units) {
-  this.set(ol.control.ScaleLineProperty.UNITS, units);
+goog.dom.fullscreen.isFullScreen = function(opt_domHelper) {
+  var doc = goog.dom.fullscreen.getDocument_(opt_domHelper);
+  // IE 11 doesn't have similar boolean property, so check whether
+  // document.msFullscreenElement is null instead.
+  return !!(doc.webkitIsFullScreen || doc.mozFullScreen ||
+      doc.msFullscreenElement || doc.fullscreenElement);
 };
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'setUnits',
-    ol.control.ScaleLine.prototype.setUnits);
 
 
 /**
+ * Gets the document object of the dom.
+ * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being
+ *     queried. If not provided, use the current DOM.
+ * @return {!Document} The dom document.
  * @private
  */
-ol.control.ScaleLine.prototype.updateElement_ = function() {
-  var viewState = this.viewState_;
-
-  if (goog.isNull(viewState)) {
-    if (this.renderedVisible_) {
-      goog.style.setElementShown(this.element_, false);
-      this.renderedVisible_ = false;
-    }
-    return;
-  }
-
-  var center = viewState.center;
-  var projection = viewState.projection;
-  var pointResolution =
-      projection.getPointResolution(viewState.resolution, center);
-  var projectionUnits = projection.getUnits();
-
-  var cosLatitude;
-  var units = this.getUnits();
-  if (projectionUnits == ol.proj.Units.DEGREES &&
-      (units == ol.control.ScaleLineUnits.METRIC ||
-       units == ol.control.ScaleLineUnits.IMPERIAL ||
-       units == ol.control.ScaleLineUnits.US ||
-       units == ol.control.ScaleLineUnits.NAUTICAL)) {
-
-    // Convert pointResolution from degrees to meters
-    this.toEPSG4326_ = null;
-    cosLatitude = Math.cos(goog.math.toRadians(center[1]));
-    pointResolution *= Math.PI * cosLatitude * ol.sphere.NORMAL.radius / 180;
-    projectionUnits = ol.proj.Units.METERS;
-
-  } else if ((projectionUnits == ol.proj.Units.FEET ||
-      projectionUnits == ol.proj.Units.METERS) &&
-      units == ol.control.ScaleLineUnits.DEGREES) {
-
-    // Convert pointResolution from meters or feet to degrees
-    if (goog.isNull(this.toEPSG4326_)) {
-      this.toEPSG4326_ = ol.proj.getTransformFromProjections(
-          projection, ol.proj.get('EPSG:4326'));
-    }
-    cosLatitude = Math.cos(goog.math.toRadians(this.toEPSG4326_(center)[1]));
-    var radius = ol.sphere.NORMAL.radius;
-    if (projectionUnits == ol.proj.Units.FEET) {
-      radius /= 0.3048;
-    }
-    pointResolution *= 180 / (Math.PI * cosLatitude * radius);
-    projectionUnits = ol.proj.Units.DEGREES;
-
-  } else {
-    this.toEPSG4326_ = null;
-  }
-
-  goog.asserts.assert(
-      ((units == ol.control.ScaleLineUnits.METRIC ||
-        units == ol.control.ScaleLineUnits.IMPERIAL ||
-        units == ol.control.ScaleLineUnits.US ||
-        units == ol.control.ScaleLineUnits.NAUTICAL) &&
-       projectionUnits == ol.proj.Units.METERS) ||
-      (units == ol.control.ScaleLineUnits.DEGREES &&
-       projectionUnits == ol.proj.Units.DEGREES));
-
-  var nominalCount = this.minWidth_ * pointResolution;
-  var suffix = '';
-  if (units == ol.control.ScaleLineUnits.DEGREES) {
-    if (nominalCount < 1 / 60) {
-      suffix = '\u2033'; // seconds
-      pointResolution *= 3600;
-    } else if (nominalCount < 1) {
-      suffix = '\u2032'; // minutes
-      pointResolution *= 60;
-    } else {
-      suffix = '\u00b0'; // degrees
-    }
-  } else if (units == ol.control.ScaleLineUnits.IMPERIAL) {
-    if (nominalCount < 0.9144) {
-      suffix = 'in';
-      pointResolution /= 0.0254;
-    } else if (nominalCount < 1609.344) {
-      suffix = 'ft';
-      pointResolution /= 0.3048;
-    } else {
-      suffix = 'mi';
-      pointResolution /= 1609.344;
-    }
-  } else if (units == ol.control.ScaleLineUnits.NAUTICAL) {
-    pointResolution /= 1852;
-    suffix = 'nm';
-  } else if (units == ol.control.ScaleLineUnits.METRIC) {
-    if (nominalCount < 1) {
-      suffix = 'mm';
-      pointResolution *= 1000;
-    } else if (nominalCount < 1000) {
-      suffix = 'm';
-    } else {
-      suffix = 'km';
-      pointResolution /= 1000;
-    }
-  } else if (units == ol.control.ScaleLineUnits.US) {
-    if (nominalCount < 0.9144) {
-      suffix = 'in';
-      pointResolution *= 39.37;
-    } else if (nominalCount < 1609.344) {
-      suffix = 'ft';
-      pointResolution /= 0.30480061;
-    } else {
-      suffix = 'mi';
-      pointResolution /= 1609.3472;
-    }
-  } else {
-    goog.asserts.fail();
-  }
-
-  var i = 3 * Math.floor(
-      Math.log(this.minWidth_ * pointResolution) / Math.log(10));
-  var count, width;
-  while (true) {
-    count = ol.control.ScaleLine.LEADING_DIGITS[i % 3] *
-        Math.pow(10, Math.floor(i / 3));
-    width = Math.round(count / pointResolution);
-    if (isNaN(width)) {
-      goog.style.setElementShown(this.element_, false);
-      this.renderedVisible_ = false;
-      return;
-    } else if (width >= this.minWidth_) {
-      break;
-    }
-    ++i;
-  }
-
-  var html = count + suffix;
-  if (this.renderedHTML_ != html) {
-    this.innerElement_.innerHTML = html;
-    this.renderedHTML_ = html;
-  }
+goog.dom.fullscreen.getDocument_ = function(opt_domHelper) {
+  return opt_domHelper ?
+      opt_domHelper.getDocument() :
+      goog.dom.getDomHelper().getDocument();
+};
 
-  if (this.renderedWidth_ != width) {
-    this.innerElement_.style.width = width + 'px';
-    this.renderedWidth_ = width;
-  }
+goog.provide('ol.control.FullScreen');
 
-  if (!this.renderedVisible_) {
-    goog.style.setElementShown(this.element_, true);
-    this.renderedVisible_ = true;
-  }
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.dom.fullscreen');
+goog.require('goog.dom.fullscreen.EventType');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.control.Control');
+goog.require('ol.css');
 
-};
 
-// Copyright 2005 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Class to create objects which want to handle multiple events
- * and have their listeners easily cleaned up via a dispose method.
- *
- * Example:
- * <pre>
- * function Something() {
- *   Something.base(this);
- *
- *   ... set up object ...
- *
- *   // Add event listeners
- *   this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);
- *   this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);
- *   this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);
- *   this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);
- *   this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);
- * }
- * goog.inherits(Something, goog.events.EventHandler);
- *
- * Something.prototype.disposeInternal = function() {
- *   Something.base(this, 'disposeInternal');
- *   goog.dom.removeNode(this.container);
- * };
- *
- *
- * // Then elsewhere:
- *
- * var activeSomething = null;
- * function openSomething() {
- *   activeSomething = new Something();
- * }
+ * @classdesc
+ * Provides a button that when clicked fills up the full screen with the map.
+ * When in full screen mode, a close button is shown to exit full screen mode.
+ * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to
+ * toggle the map in full screen mode.
  *
- * function closeSomething() {
- *   if (activeSomething) {
- *     activeSomething.dispose();  // Remove event listeners
- *     activeSomething = null;
- *   }
- * }
- * </pre>
  *
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.FullScreenOptions=} opt_options Options.
+ * @api stable
  */
+ol.control.FullScreen = function(opt_options) {
 
-goog.provide('goog.events.EventHandler');
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-goog.require('goog.Disposable');
-goog.require('goog.events');
-goog.require('goog.object');
+  /**
+   * @private
+   * @type {string}
+   */
+  this.cssClassName_ = goog.isDef(options.className) ?
+      options.className : 'ol-full-screen';
 
-goog.forwardDeclare('goog.events.EventWrapper');
+  var tipLabel = goog.isDef(options.tipLabel) ?
+      options.tipLabel : 'Toggle full-screen';
+  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
+    'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen(),
+    'type': 'button',
+    'title': tipLabel
+  });
 
+  goog.events.listen(button, goog.events.EventType.CLICK,
+      this.handleClick_, false, this);
 
+  goog.events.listen(button, [
+    goog.events.EventType.MOUSEOUT,
+    goog.events.EventType.FOCUSOUT
+  ], function() {
+    this.blur();
+  }, false);
 
-/**
- * Super class for objects that want to easily manage a number of event
- * listeners.  It allows a short cut to listen and also provides a quick way
- * to remove all events listeners belonging to this object.
- * @param {SCOPE=} opt_scope Object in whose scope to call the listeners.
- * @constructor
- * @extends {goog.Disposable}
- * @template SCOPE
- */
-goog.events.EventHandler = function(opt_scope) {
-  goog.Disposable.call(this);
-  // TODO(user): Rename this to this.scope_ and fix the classes in google3
-  // that access this private variable. :(
-  this.handler_ = opt_scope;
+  goog.events.listen(goog.global.document,
+      goog.dom.fullscreen.EventType.CHANGE,
+      this.handleFullScreenChange_, false, this);
+
+  var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE +
+      ' ' + ol.css.CLASS_CONTROL + ' ' +
+      (!goog.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : '');
+  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
+
+  goog.base(this, {
+    element: element,
+    target: options.target
+  });
 
   /**
-   * Keys for events that are being listened to.
-   * @type {!Object.<!goog.events.Key>}
    * @private
+   * @type {boolean}
    */
-  this.keys_ = {};
+  this.keys_ = goog.isDef(options.keys) ? options.keys : false;
+
 };
-goog.inherits(goog.events.EventHandler, goog.Disposable);
+goog.inherits(ol.control.FullScreen, ol.control.Control);
 
 
 /**
- * Utility array used to unify the cases of listening for an array of types
- * and listening for a single event, without using recursion or allocating
- * an array each time.
- * @type {!Array.<string>}
- * @const
+ * @param {goog.events.BrowserEvent} event The event to handle
  * @private
  */
-goog.events.EventHandler.typeArray_ = [];
-
-
-/**
- * Listen to an event on a Listenable.  If the function is omitted then the
- * EventHandler's handleEvent method will be used.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type to listen for or array of event types.
- * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
- *     opt_fn Optional callback function to be used as the listener or an object
- *     with handleEvent function.
- * @param {boolean=} opt_capture Optional whether to use capture phase.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @template EVENTOBJ
- */
-goog.events.EventHandler.prototype.listen = function(
-    src, type, opt_fn, opt_capture) {
-  return this.listen_(src, type, opt_fn, opt_capture);
+ol.control.FullScreen.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleFullScreen_();
 };
 
 
 /**
- * Listen to an event on a Listenable.  If the function is omitted then the
- * EventHandler's handleEvent method will be used.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type to listen for or array of event types.
- * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
- *     null|undefined} fn Optional callback function to be used as the
- *     listener or an object with handleEvent function.
- * @param {boolean|undefined} capture Optional whether to use capture phase.
- * @param {T} scope Object in whose scope to call the listener.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @template T,EVENTOBJ
+ * @private
  */
-goog.events.EventHandler.prototype.listenWithScope = function(
-    src, type, fn, capture, scope) {
-  // TODO(user): Deprecate this function.
-  return this.listen_(src, type, fn, capture, scope);
+ol.control.FullScreen.prototype.handleFullScreen_ = function() {
+  if (!goog.dom.fullscreen.isSupported()) {
+    return;
+  }
+  var map = this.getMap();
+  if (goog.isNull(map)) {
+    return;
+  }
+  if (goog.dom.fullscreen.isFullScreen()) {
+    goog.dom.fullscreen.exitFullScreen();
+  } else {
+    var target = map.getTarget();
+    goog.asserts.assert(goog.isDefAndNotNull(target));
+    var element = goog.dom.getElement(target);
+    goog.asserts.assert(goog.isDefAndNotNull(element));
+    if (this.keys_) {
+      goog.dom.fullscreen.requestFullScreenWithKeys(element);
+    } else {
+      goog.dom.fullscreen.requestFullScreen(element);
+    }
+  }
 };
 
 
 /**
- * Listen to an event on a Listenable.  If the function is omitted then the
- * EventHandler's handleEvent method will be used.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type to listen for or array of event types.
- * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
- *     Optional callback function to be used as the listener or an object with
- *     handleEvent function.
- * @param {boolean=} opt_capture Optional whether to use capture phase.
- * @param {Object=} opt_scope Object in whose scope to call the listener.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @template EVENTOBJ
  * @private
  */
-goog.events.EventHandler.prototype.listen_ = function(src, type, opt_fn,
-                                                      opt_capture,
-                                                      opt_scope) {
-  if (!goog.isArray(type)) {
-    if (type) {
-      goog.events.EventHandler.typeArray_[0] = type.toString();
-    }
-    type = goog.events.EventHandler.typeArray_;
+ol.control.FullScreen.prototype.handleFullScreenChange_ = function() {
+  var opened = this.cssClassName_ + '-true';
+  var closed = this.cssClassName_ + '-false';
+  var anchor = goog.dom.getFirstElementChild(this.element);
+  var map = this.getMap();
+  if (goog.dom.fullscreen.isFullScreen()) {
+    goog.dom.classlist.swap(anchor, closed, opened);
+  } else {
+    goog.dom.classlist.swap(anchor, opened, closed);
   }
-  for (var i = 0; i < type.length; i++) {
-    var listenerObj = goog.events.listen(
-        src, type[i], opt_fn || this.handleEvent,
-        opt_capture || false,
-        opt_scope || this.handler_ || this);
-
-    if (!listenerObj) {
-      // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
-      // (goog.events.CaptureSimulationMode) in IE8-, it will return null
-      // value.
-      return this;
-    }
-
-    var key = listenerObj.key;
-    this.keys_[key] = listenerObj;
+  if (!goog.isNull(map)) {
+    map.updateSize();
   }
-
-  return this;
 };
 
+goog.provide('ol.Pixel');
+
 
 /**
- * Listen to an event on a Listenable.  If the function is omitted, then the
- * EventHandler's handleEvent method will be used. After the event has fired the
- * event listener is removed from the target. If an array of event types is
- * provided, each event type will be listened to once.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type to listen for or array of event types.
- * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
- *    Optional callback function to be used as the listener or an object with
- *    handleEvent function.
- * @param {boolean=} opt_capture Optional whether to use capture phase.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @template EVENTOBJ
+ * An array with two elements, representing a pixel. The first element is the
+ * x-coordinate, the second the y-coordinate of the pixel.
+ * @typedef {Array.<number>}
+ * @api stable
  */
-goog.events.EventHandler.prototype.listenOnce = function(
-    src, type, opt_fn, opt_capture) {
-  // TODO(user): Remove the opt_scope from this function and then
-  // templatize it.
-  return this.listenOnce_(src, type, opt_fn, opt_capture);
-};
+ol.Pixel;
 
+// FIXME should listen on appropriate pane, once it is defined
 
-/**
- * Listen to an event on a Listenable.  If the function is omitted, then the
- * EventHandler's handleEvent method will be used. After the event has fired the
- * event listener is removed from the target. If an array of event types is
- * provided, each event type will be listened to once.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type to listen for or array of event types.
- * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
- *     null|undefined} fn Optional callback function to be used as the
- *     listener or an object with handleEvent function.
- * @param {boolean|undefined} capture Optional whether to use capture phase.
- * @param {T} scope Object in whose scope to call the listener.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @template T,EVENTOBJ
- */
-goog.events.EventHandler.prototype.listenOnceWithScope = function(
-    src, type, fn, capture, scope) {
-  // TODO(user): Deprecate this function.
-  return this.listenOnce_(src, type, fn, capture, scope);
-};
+goog.provide('ol.control.MousePosition');
+
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.CoordinateFormatType');
+goog.require('ol.Object');
+goog.require('ol.Pixel');
+goog.require('ol.TransformFunction');
+goog.require('ol.control.Control');
+goog.require('ol.proj');
+goog.require('ol.proj.Projection');
 
 
 /**
- * Listen to an event on a Listenable.  If the function is omitted, then the
- * EventHandler's handleEvent method will be used. After the event has fired
- * the event listener is removed from the target. If an array of event types is
- * provided, each event type will be listened to once.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type to listen for or array of event types.
- * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
- *    Optional callback function to be used as the listener or an object with
- *    handleEvent function.
- * @param {boolean=} opt_capture Optional whether to use capture phase.
- * @param {Object=} opt_scope Object in whose scope to call the listener.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @template EVENTOBJ
- * @private
+ * @enum {string}
  */
-goog.events.EventHandler.prototype.listenOnce_ = function(
-    src, type, opt_fn, opt_capture, opt_scope) {
-  if (goog.isArray(type)) {
-    for (var i = 0; i < type.length; i++) {
-      this.listenOnce_(src, type[i], opt_fn, opt_capture, opt_scope);
-    }
-  } else {
-    var listenerObj = goog.events.listenOnce(
-        src, type, opt_fn || this.handleEvent, opt_capture,
-        opt_scope || this.handler_ || this);
-    if (!listenerObj) {
-      // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
-      // (goog.events.CaptureSimulationMode) in IE8-, it will return null
-      // value.
-      return this;
-    }
-
-    var key = listenerObj.key;
-    this.keys_[key] = listenerObj;
-  }
-
-  return this;
+ol.control.MousePositionProperty = {
+  PROJECTION: 'projection',
+  COORDINATE_FORMAT: 'coordinateFormat'
 };
 
 
+
 /**
- * Adds an event listener with a specific event wrapper on a DOM Node or an
- * object that has implemented {@link goog.events.EventTarget}. A listener can
- * only be added once to an object.
+ * @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`.
  *
- * @param {EventTarget|goog.events.EventTarget} src The node to listen to
- *     events on.
- * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
- * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener
- *     Callback method, or an object with a handleEvent function.
- * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
- *     false).
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.MousePositionOptions=} opt_options Mouse position
+ *     options.
+ * @api stable
  */
-goog.events.EventHandler.prototype.listenWithWrapper = function(
-    src, wrapper, listener, opt_capt) {
-  // TODO(user): Remove the opt_scope from this function and then
-  // templatize it.
-  return this.listenWithWrapper_(src, wrapper, listener, opt_capt);
-};
+ol.control.MousePosition = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * Adds an event listener with a specific event wrapper on a DOM Node or an
- * object that has implemented {@link goog.events.EventTarget}. A listener can
- * only be added once to an object.
- *
- * @param {EventTarget|goog.events.EventTarget} src The node to listen to
- *     events on.
- * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
- * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null}
- *     listener Optional callback function to be used as the
- *     listener or an object with handleEvent function.
- * @param {boolean|undefined} capture Optional whether to use capture phase.
- * @param {T} scope Object in whose scope to call the listener.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @template T
- */
-goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(
-    src, wrapper, listener, capture, scope) {
-  // TODO(user): Deprecate this function.
-  return this.listenWithWrapper_(src, wrapper, listener, capture, scope);
-};
+  var className = goog.isDef(options.className) ?
+      options.className : 'ol-mouse-position';
 
+  var element = goog.dom.createDom(goog.dom.TagName.DIV, className);
 
-/**
- * Adds an event listener with a specific event wrapper on a DOM Node or an
- * object that has implemented {@link goog.events.EventTarget}. A listener can
- * only be added once to an object.
- *
- * @param {EventTarget|goog.events.EventTarget} src The node to listen to
- *     events on.
- * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
- * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
- *     method, or an object with a handleEvent function.
- * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
- *     false).
- * @param {Object=} opt_scope Element in whose scope to call the listener.
- * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
- *     chaining of calls.
- * @private
- */
-goog.events.EventHandler.prototype.listenWithWrapper_ = function(
-    src, wrapper, listener, opt_capt, opt_scope) {
-  wrapper.listen(src, listener, opt_capt, opt_scope || this.handler_ || this,
-                 this);
-  return this;
-};
+  var render = goog.isDef(options.render) ?
+      options.render : ol.control.MousePosition.render;
 
+  goog.base(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
 
-/**
- * @return {number} Number of listeners registered by this handler.
- */
-goog.events.EventHandler.prototype.getListenerCount = function() {
-  var count = 0;
-  for (var key in this.keys_) {
-    if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {
-      count++;
-    }
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.control.MousePositionProperty.PROJECTION),
+      this.handleProjectionChanged_, false, this);
+
+  if (goog.isDef(options.coordinateFormat)) {
+    this.setCoordinateFormat(options.coordinateFormat);
   }
-  return count;
+  if (goog.isDef(options.projection)) {
+    this.setProjection(ol.proj.get(options.projection));
+  }
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.undefinedHTML_ = goog.isDef(options.undefinedHTML) ?
+      options.undefinedHTML : '';
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.renderedHTML_ = element.innerHTML;
+
+  /**
+   * @private
+   * @type {ol.proj.Projection}
+   */
+  this.mapProjection_ = null;
+
+  /**
+   * @private
+   * @type {?ol.TransformFunction}
+   */
+  this.transform_ = null;
+
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.lastMouseMovePixel_ = null;
+
 };
+goog.inherits(ol.control.MousePosition, ol.control.Control);
 
 
 /**
- * Unlistens on an event.
- * @param {goog.events.ListenableType} src Event source.
- * @param {string|Array.<string>|
- *     !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
- *     type Event type or array of event types to unlisten to.
- * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
- *     Optional callback function to be used as the listener or an object with
- *     handleEvent function.
- * @param {boolean=} opt_capture Optional whether to use capture phase.
- * @param {Object=} opt_scope Object in whose scope to call the listener.
- * @return {!goog.events.EventHandler} This object, allowing for chaining of
- *     calls.
- * @template EVENTOBJ
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.MousePosition}
+ * @api
  */
-goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn,
-                                                       opt_capture,
-                                                       opt_scope) {
-  if (goog.isArray(type)) {
-    for (var i = 0; i < type.length; i++) {
-      this.unlisten(src, type[i], opt_fn, opt_capture, opt_scope);
-    }
+ol.control.MousePosition.render = function(mapEvent) {
+  var frameState = mapEvent.frameState;
+  if (goog.isNull(frameState)) {
+    this.mapProjection_ = null;
   } else {
-    var listener = goog.events.getListener(src, type,
-        opt_fn || this.handleEvent,
-        opt_capture, opt_scope || this.handler_ || this);
-
-    if (listener) {
-      goog.events.unlistenByKey(listener);
-      delete this.keys_[listener.key];
+    if (this.mapProjection_ != frameState.viewState.projection) {
+      this.mapProjection_ = frameState.viewState.projection;
+      this.transform_ = null;
     }
   }
-
-  return this;
+  this.updateHTML_(this.lastMouseMovePixel_);
 };
 
 
 /**
- * Removes an event listener which was added with listenWithWrapper().
- *
- * @param {EventTarget|goog.events.EventTarget} src The target to stop
- *     listening to events on.
- * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
- * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
- *     listener function to remove.
- * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
- *     whether the listener is fired during the capture or bubble phase of the
- *     event.
- * @param {Object=} opt_scope Element in whose scope to call the listener.
- * @return {!goog.events.EventHandler} This object, allowing for chaining of
- *     calls.
+ * @private
  */
-goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper,
-    listener, opt_capt, opt_scope) {
-  wrapper.unlisten(src, listener, opt_capt,
-                   opt_scope || this.handler_ || this, this);
-  return this;
+ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
+  this.transform_ = null;
 };
 
 
 /**
- * Unlistens to all events.
+ * @return {ol.CoordinateFormatType|undefined} The format to render the current
+ *     position in.
+ * @observable
+ * @api stable
  */
-goog.events.EventHandler.prototype.removeAll = function() {
-  goog.object.forEach(this.keys_, goog.events.unlistenByKey);
-  this.keys_ = {};
+ol.control.MousePosition.prototype.getCoordinateFormat = function() {
+  return /** @type {ol.CoordinateFormatType|undefined} */ (
+      this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT));
 };
+goog.exportProperty(
+    ol.control.MousePosition.prototype,
+    'getCoordinateFormat',
+    ol.control.MousePosition.prototype.getCoordinateFormat);
 
 
 /**
- * Disposes of this EventHandler and removes all listeners that it registered.
- * @override
- * @protected
+ * @return {ol.proj.Projection|undefined} The projection to report mouse
+ *     position in.
+ * @observable
+ * @api stable
  */
-goog.events.EventHandler.prototype.disposeInternal = function() {
-  goog.events.EventHandler.superClass_.disposeInternal.call(this);
-  this.removeAll();
+ol.control.MousePosition.prototype.getProjection = function() {
+  return /** @type {ol.proj.Projection|undefined} */ (
+      this.get(ol.control.MousePositionProperty.PROJECTION));
 };
+goog.exportProperty(
+    ol.control.MousePosition.prototype,
+    'getProjection',
+    ol.control.MousePosition.prototype.getProjection);
 
 
 /**
- * Default event handler
- * @param {goog.events.Event} e Event object.
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @protected
  */
-goog.events.EventHandler.prototype.handleEvent = function(e) {
-  throw Error('EventHandler.handleEvent not implemented');
+ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) {
+  var map = this.getMap();
+  this.lastMouseMovePixel_ = map.getEventPixel(browserEvent.getBrowserEvent());
+  this.updateHTML_(this.lastMouseMovePixel_);
 };
 
-// Copyright 2012 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Bidi utility functions.
- *
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @protected
  */
-
-goog.provide('goog.style.bidi');
-
-goog.require('goog.dom');
-goog.require('goog.style');
-goog.require('goog.userAgent');
+ol.control.MousePosition.prototype.handleMouseOut = function(browserEvent) {
+  this.updateHTML_(null);
+  this.lastMouseMovePixel_ = null;
+};
 
 
 /**
- * Returns the normalized scrollLeft position for a scrolled element.
- * @param {Element} element The scrolled element.
- * @return {number} The number of pixels the element is scrolled. 0 indicates
- *     that the element is not scrolled at all (which, in general, is the
- *     left-most position in ltr and the right-most position in rtl).
+ * @inheritDoc
+ * @api stable
  */
-goog.style.bidi.getScrollLeft = function(element) {
-  var isRtl = goog.style.isRightToLeft(element);
-  if (isRtl && goog.userAgent.GECKO) {
-    // ScrollLeft starts at 0 and then goes negative as the element is scrolled
-    // towards the left.
-    return -element.scrollLeft;
-  } else if (isRtl &&
-             !(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) {
-    // ScrollLeft starts at the maximum positive value and decreases towards
-    // 0 as the element is scrolled towards the left. However, for overflow
-    // visible, there is no scrollLeft and the value always stays correctly at 0
-    var overflowX = goog.style.getComputedOverflowX(element);
-    if (overflowX == 'visible') {
-      return element.scrollLeft;
-    } else {
-      return element.scrollWidth - element.clientWidth - element.scrollLeft;
-    }
+ol.control.MousePosition.prototype.setMap = function(map) {
+  goog.base(this, 'setMap', map);
+  if (!goog.isNull(map)) {
+    var viewport = map.getViewport();
+    this.listenerKeys.push(
+        goog.events.listen(viewport, goog.events.EventType.MOUSEMOVE,
+            this.handleMouseMove, false, this),
+        goog.events.listen(viewport, goog.events.EventType.MOUSEOUT,
+            this.handleMouseOut, false, this)
+    );
   }
-  // ScrollLeft behavior is identical in rtl and ltr, it starts at 0 and
-  // increases as the element is scrolled away from the start.
-  return element.scrollLeft;
 };
 
 
 /**
- * Returns the "offsetStart" of an element, analagous to offsetLeft but
- * normalized for right-to-left environments and various browser
- * inconsistencies. This value returned can always be passed to setScrollOffset
- * to scroll to an element's left edge in a left-to-right offsetParent or
- * right edge in a right-to-left offsetParent.
- *
- * For example, here offsetStart is 10px in an LTR environment and 5px in RTL:
- *
- * <pre>
- * |          xxxxxxxxxx     |
- *  ^^^^^^^^^^   ^^^^   ^^^^^
- *     10px      elem    5px
- * </pre>
- *
- * If an element is positioned before the start of its offsetParent, the
- * startOffset may be negative.  This can be used with setScrollOffset to
- * reliably scroll to an element:
- *
- * <pre>
- * var scrollOffset = goog.style.bidi.getOffsetStart(element);
- * goog.style.bidi.setScrollOffset(element.offsetParent, scrollOffset);
- * </pre>
- *
- * @see setScrollOffset
- *
- * @param {Element} element The element for which we need to determine the
- *     offsetStart position.
- * @return {number} The offsetStart for that element.
+ * @param {ol.CoordinateFormatType} format The format to render the current
+ *     position in.
+ * @observable
+ * @api stable
  */
-goog.style.bidi.getOffsetStart = function(element) {
-  var offsetLeftForReal = element.offsetLeft;
-
-  // The element might not have an offsetParent.
-  // For example, the node might not be attached to the DOM tree,
-  // and position:fixed children do not have an offset parent.
-  // Just try to do the best we can with what we have.
-  var bestParent = element.offsetParent;
-
-  if (!bestParent && goog.style.getComputedPosition(element) == 'fixed') {
-    bestParent = goog.dom.getOwnerDocument(element).documentElement;
-  }
-
-  // Just give up in this case.
-  if (!bestParent) {
-    return offsetLeftForReal;
-  }
-
-  if (goog.userAgent.GECKO) {
-    // When calculating an element's offsetLeft, Firefox erroneously subtracts
-    // the border width from the actual distance.  So we need to add it back.
-    var borderWidths = goog.style.getBorderBox(bestParent);
-    offsetLeftForReal += borderWidths.left;
-  } else if (goog.userAgent.isDocumentModeOrHigher(8) &&
-             !goog.userAgent.isDocumentModeOrHigher(9)) {
-    // When calculating an element's offsetLeft, IE8/9-Standards Mode
-    // erroneously adds the border width to the actual distance.  So we need to
-    // subtract it.
-    var borderWidths = goog.style.getBorderBox(bestParent);
-    offsetLeftForReal -= borderWidths.left;
-  }
-
-  if (goog.style.isRightToLeft(bestParent)) {
-    // Right edge of the element relative to the left edge of its parent.
-    var elementRightOffset = offsetLeftForReal + element.offsetWidth;
-
-    // Distance from the parent's right edge to the element's right edge.
-    return bestParent.clientWidth - elementRightOffset;
-  }
-
-  return offsetLeftForReal;
+ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
+  this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format);
 };
+goog.exportProperty(
+    ol.control.MousePosition.prototype,
+    'setCoordinateFormat',
+    ol.control.MousePosition.prototype.setCoordinateFormat);
 
 
 /**
- * Sets the element's scrollLeft attribute so it is correctly scrolled by
- * offsetStart pixels.  This takes into account whether the element is RTL and
- * the nuances of different browsers.  To scroll to the "beginning" of an
- * element use getOffsetStart to obtain the element's offsetStart value and then
- * pass the value to setScrollOffset.
- * @see getOffsetStart
- * @param {Element} element The element to set scrollLeft on.
- * @param {number} offsetStart The number of pixels to scroll the element.
- *     If this value is < 0, 0 is used.
+ * @param {ol.proj.Projection} projection The projection to report mouse
+ *     position in.
+ * @observable
+ * @api stable
  */
-goog.style.bidi.setScrollOffset = function(element, offsetStart) {
-  offsetStart = Math.max(offsetStart, 0);
-  // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to
-  // the number of pixels to scroll.
-  // Otherwise, in RTL, we need to account for different browser behavior.
-  if (!goog.style.isRightToLeft(element)) {
-    element.scrollLeft = offsetStart;
-  } else if (goog.userAgent.GECKO) {
-    // Negative scroll-left positions in RTL.
-    element.scrollLeft = -offsetStart;
-  } else if (!(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) {
-    // Take the current scrollLeft value and move to the right by the
-    // offsetStart to get to the left edge of the element, and then by
-    // the clientWidth of the element to get to the right edge.
-    element.scrollLeft =
-        element.scrollWidth - offsetStart - element.clientWidth;
-  } else {
-    element.scrollLeft = offsetStart;
-  }
+ol.control.MousePosition.prototype.setProjection = function(projection) {
+  this.set(ol.control.MousePositionProperty.PROJECTION, projection);
 };
+goog.exportProperty(
+    ol.control.MousePosition.prototype,
+    'setProjection',
+    ol.control.MousePosition.prototype.setProjection);
 
 
 /**
- * Sets the element's left style attribute in LTR or right style attribute in
- * RTL.  Also clears the left attribute in RTL and the right attribute in LTR.
- * @param {Element} elem The element to position.
- * @param {number} left The left position in LTR; will be set as right in RTL.
- * @param {?number} top The top position.  If null only the left/right is set.
- * @param {boolean} isRtl Whether we are in RTL mode.
+ * @param {?ol.Pixel} pixel Pixel.
+ * @private
  */
-goog.style.bidi.setPosition = function(elem, left, top, isRtl) {
-  if (!goog.isNull(top)) {
-    elem.style.top = top + 'px';
+ol.control.MousePosition.prototype.updateHTML_ = function(pixel) {
+  var html = this.undefinedHTML_;
+  if (!goog.isNull(pixel) && !goog.isNull(this.mapProjection_)) {
+    if (goog.isNull(this.transform_)) {
+      var projection = this.getProjection();
+      if (goog.isDef(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 (!goog.isNull(coordinate)) {
+      this.transform_(coordinate, coordinate);
+      var coordinateFormat = this.getCoordinateFormat();
+      if (goog.isDef(coordinateFormat)) {
+        html = coordinateFormat(coordinate);
+      } else {
+        html = coordinate.toString();
+      }
+    }
   }
-  if (isRtl) {
-    elem.style.right = left + 'px';
-    elem.style.left = '';
-  } else {
-    elem.style.left = left + 'px';
-    elem.style.right = '';
+  if (!goog.isDef(this.renderedHTML_) || html != this.renderedHTML_) {
+    this.element.innerHTML = html;
+    this.renderedHTML_ = html;
   }
 };
 
@@ -33502,9367 +34823,9446 @@ goog.style.bidi.setPosition = function(elem, left, top, isRtl) {
 // limitations under the License.
 
 /**
- * @fileoverview Drag Utilities.
+ * @fileoverview Generics method for collection-like classes and objects.
  *
- * Provides extensible functionality for drag & drop behaviour.
+ * @author arv@google.com (Erik Arvidsson)
  *
- * @see ../demos/drag.html
- * @see ../demos/dragger.html
+ * This file contains functions to work with collections. It supports using
+ * Map, Set, Array and Object and other classes that implement collection-like
+ * methods.
  */
 
 
-goog.provide('goog.fx.DragEvent');
-goog.provide('goog.fx.Dragger');
-goog.provide('goog.fx.Dragger.EventType');
+goog.provide('goog.structs');
+
+goog.require('goog.array');
+goog.require('goog.object');
 
-goog.require('goog.dom');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventHandler');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
-goog.require('goog.math.Coordinate');
-goog.require('goog.math.Rect');
-goog.require('goog.style');
-goog.require('goog.style.bidi');
-goog.require('goog.userAgent');
 
+// We treat an object as a dictionary if it has getKeys or it is an object that
+// isn't arrayLike.
 
 
 /**
- * A class that allows mouse or touch-based dragging (moving) of an element
- *
- * @param {Element} target The element that will be dragged.
- * @param {Element=} opt_handle An optional handle to control the drag, if null
- *     the target is used.
- * @param {goog.math.Rect=} opt_limits Object containing left, top, width,
- *     and height.
- *
- * @extends {goog.events.EventTarget}
- * @constructor
+ * Returns the number of values in the collection-like object.
+ * @param {Object} col The collection-like object.
+ * @return {number} The number of values in the collection-like object.
  */
-goog.fx.Dragger = function(target, opt_handle, opt_limits) {
-  goog.events.EventTarget.call(this);
-  this.target = target;
-  this.handle = opt_handle || target;
-  this.limits = opt_limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
-
-  this.document_ = goog.dom.getOwnerDocument(target);
-  this.eventHandler_ = new goog.events.EventHandler(this);
-  this.registerDisposable(this.eventHandler_);
-
-  // Add listener. Do not use the event handler here since the event handler is
-  // used for listeners added and removed during the drag operation.
-  goog.events.listen(this.handle,
-      [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN],
-      this.startDrag, false, this);
+goog.structs.getCount = function(col) {
+  if (typeof col.getCount == 'function') {
+    return col.getCount();
+  }
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    return col.length;
+  }
+  return goog.object.getCount(col);
 };
-goog.inherits(goog.fx.Dragger, goog.events.EventTarget);
 
 
 /**
- * Whether setCapture is supported by the browser.
- * @type {boolean}
- * @private
+ * Returns the values of the collection-like object.
+ * @param {Object} col The collection-like object.
+ * @return {!Array<?>} The values in the collection-like object.
  */
-goog.fx.Dragger.HAS_SET_CAPTURE_ =
-    // IE and Gecko after 1.9.3 has setCapture
-    // WebKit does not yet: https://bugs.webkit.org/show_bug.cgi?id=27330
-    goog.userAgent.IE ||
-    goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.3');
+goog.structs.getValues = function(col) {
+  if (typeof col.getValues == 'function') {
+    return col.getValues();
+  }
+  if (goog.isString(col)) {
+    return col.split('');
+  }
+  if (goog.isArrayLike(col)) {
+    var rv = [];
+    var l = col.length;
+    for (var i = 0; i < l; i++) {
+      rv.push(col[i]);
+    }
+    return rv;
+  }
+  return goog.object.getValues(col);
+};
 
 
 /**
- * Creates copy of node being dragged.  This is a utility function to be used
- * wherever it is inappropriate for the original source to follow the mouse
- * cursor itself.
- *
- * @param {Element} sourceEl Element to copy.
- * @return {!Element} The clone of {@code sourceEl}.
+ * Returns the keys of the collection. Some collections have no notion of
+ * keys/indexes and this function will return undefined in those cases.
+ * @param {Object} col The collection-like object.
+ * @return {!Array|undefined} The keys in the collection.
  */
-goog.fx.Dragger.cloneNode = function(sourceEl) {
-  var clonedEl = /** @type {Element} */ (sourceEl.cloneNode(true)),
-      origTexts = sourceEl.getElementsByTagName('textarea'),
-      dragTexts = clonedEl.getElementsByTagName('textarea');
-  // Cloning does not copy the current value of textarea elements, so correct
-  // this manually.
-  for (var i = 0; i < origTexts.length; i++) {
-    dragTexts[i].value = origTexts[i].value;
+goog.structs.getKeys = function(col) {
+  if (typeof col.getKeys == 'function') {
+    return col.getKeys();
   }
-  switch (sourceEl.tagName.toLowerCase()) {
-    case 'tr':
-      return goog.dom.createDom(
-          'table', null, goog.dom.createDom('tbody', null, clonedEl));
-    case 'td':
-    case 'th':
-      return goog.dom.createDom(
-          'table', null, goog.dom.createDom('tbody', null, goog.dom.createDom(
-          'tr', null, clonedEl)));
-    case 'textarea':
-      clonedEl.value = sourceEl.value;
-    default:
-      return clonedEl;
+  // if we have getValues but no getKeys we know this is a key-less collection
+  if (typeof col.getValues == 'function') {
+    return undefined;
+  }
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    var rv = [];
+    var l = col.length;
+    for (var i = 0; i < l; i++) {
+      rv.push(i);
+    }
+    return rv;
   }
-};
-
 
-/**
- * Constants for event names.
- * @enum {string}
- */
-goog.fx.Dragger.EventType = {
-  // The drag action was canceled before the START event. Possible reasons:
-  // disabled dragger, dragging with the right mouse button or releasing the
-  // button before reaching the hysteresis distance.
-  EARLY_CANCEL: 'earlycancel',
-  START: 'start',
-  BEFOREDRAG: 'beforedrag',
-  DRAG: 'drag',
-  END: 'end'
+  return goog.object.getKeys(col);
 };
 
 
 /**
- * Reference to drag target element.
- * @type {Element}
+ * Whether the collection contains the given value. This is O(n) and uses
+ * equals (==) to test the existence.
+ * @param {Object} col The collection-like object.
+ * @param {*} val The value to check for.
+ * @return {boolean} True if the map contains the value.
  */
-goog.fx.Dragger.prototype.target;
+goog.structs.contains = function(col, val) {
+  if (typeof col.contains == 'function') {
+    return col.contains(val);
+  }
+  if (typeof col.containsValue == 'function') {
+    return col.containsValue(val);
+  }
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    return goog.array.contains(/** @type {!Array<?>} */ (col), val);
+  }
+  return goog.object.containsValue(col, val);
+};
 
 
 /**
- * Reference to the handler that initiates the drag.
- * @type {Element}
+ * Whether the collection is empty.
+ * @param {Object} col The collection-like object.
+ * @return {boolean} True if empty.
  */
-goog.fx.Dragger.prototype.handle;
+goog.structs.isEmpty = function(col) {
+  if (typeof col.isEmpty == 'function') {
+    return col.isEmpty();
+  }
 
+  // We do not use goog.string.isEmpty because here we treat the string as
+  // collection and as such even whitespace matters
 
-/**
- * Object representing the limits of the drag region.
- * @type {goog.math.Rect}
- */
-goog.fx.Dragger.prototype.limits;
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    return goog.array.isEmpty(/** @type {!Array<?>} */ (col));
+  }
+  return goog.object.isEmpty(col);
+};
 
 
 /**
- * Whether the element is rendered right-to-left. We initialize this lazily.
- * @type {boolean|undefined}}
- * @private
+ * Removes all the elements from the collection.
+ * @param {Object} col The collection-like object.
  */
-goog.fx.Dragger.prototype.rightToLeft_;
+goog.structs.clear = function(col) {
+  // NOTE(arv): This should not contain strings because strings are immutable
+  if (typeof col.clear == 'function') {
+    col.clear();
+  } else if (goog.isArrayLike(col)) {
+    goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
+  } else {
+    goog.object.clear(col);
+  }
+};
 
 
 /**
- * Current x position of mouse or touch relative to viewport.
- * @type {number}
+ * Calls a function for each value in a collection. The function takes
+ * three arguments; the value, the key and the collection.
+ *
+ * NOTE: This will be deprecated soon! Please use a more specific method if
+ * possible, e.g. goog.array.forEach, goog.object.forEach, etc.
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):?} f The function to call for every value.
+ *     This function takes
+ *     3 arguments (the value, the key or undefined if the collection has no
+ *     notion of keys, and the collection) and the return value is irrelevant.
+ * @param {T=} opt_obj The object to be used as the value of 'this'
+ *     within {@code f}.
+ * @template T,S
  */
-goog.fx.Dragger.prototype.clientX = 0;
+goog.structs.forEach = function(col, f, opt_obj) {
+  if (typeof col.forEach == 'function') {
+    col.forEach(f, opt_obj);
+  } else if (goog.isArrayLike(col) || goog.isString(col)) {
+    goog.array.forEach(/** @type {!Array<?>} */ (col), f, opt_obj);
+  } else {
+    var keys = goog.structs.getKeys(col);
+    var values = goog.structs.getValues(col);
+    var l = values.length;
+    for (var i = 0; i < l; i++) {
+      f.call(opt_obj, values[i], keys && keys[i], col);
+    }
+  }
+};
 
 
 /**
- * Current y position of mouse or touch relative to viewport.
- * @type {number}
+ * Calls a function for every value in the collection. When a call returns true,
+ * adds the value to a new collection (Array is returned by default).
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):boolean} f The function to call for every
+ *     value. This function takes
+ *     3 arguments (the value, the key or undefined if the collection has no
+ *     notion of keys, and the collection) and should return a Boolean. If the
+ *     return value is true the value is added to the result collection. If it
+ *     is false the value is not included.
+ * @param {T=} opt_obj The object to be used as the value of 'this'
+ *     within {@code f}.
+ * @return {!Object|!Array<?>} A new collection where the passed values are
+ *     present. If col is a key-less collection an array is returned.  If col
+ *     has keys and values a plain old JS object is returned.
+ * @template T,S
  */
-goog.fx.Dragger.prototype.clientY = 0;
-
+goog.structs.filter = function(col, f, opt_obj) {
+  if (typeof col.filter == 'function') {
+    return col.filter(f, opt_obj);
+  }
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    return goog.array.filter(/** @type {!Array<?>} */ (col), f, opt_obj);
+  }
 
-/**
- * Current x position of mouse or touch relative to screen. Deprecated because
- * it doesn't take into affect zoom level or pixel density.
- * @type {number}
- * @deprecated Consider switching to clientX instead.
- */
-goog.fx.Dragger.prototype.screenX = 0;
+  var rv;
+  var keys = goog.structs.getKeys(col);
+  var values = goog.structs.getValues(col);
+  var l = values.length;
+  if (keys) {
+    rv = {};
+    for (var i = 0; i < l; i++) {
+      if (f.call(opt_obj, values[i], keys[i], col)) {
+        rv[keys[i]] = values[i];
+      }
+    }
+  } else {
+    // We should not use goog.array.filter here since we want to make sure that
+    // the index is undefined as well as make sure that col is passed to the
+    // function.
+    rv = [];
+    for (var i = 0; i < l; i++) {
+      if (f.call(opt_obj, values[i], undefined, col)) {
+        rv.push(values[i]);
+      }
+    }
+  }
+  return rv;
+};
 
 
 /**
- * Current y position of mouse or touch relative to screen. Deprecated because
- * it doesn't take into affect zoom level or pixel density.
- * @type {number}
- * @deprecated Consider switching to clientY instead.
+ * Calls a function for every value in the collection and adds the result into a
+ * new collection (defaults to creating a new Array).
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):V} f The function to call for every value.
+ *     This function takes 3 arguments (the value, the key or undefined if the
+ *     collection has no notion of keys, and the collection) and should return
+ *     something. The result will be used as the value in the new collection.
+ * @param {T=} opt_obj  The object to be used as the value of 'this'
+ *     within {@code f}.
+ * @return {!Object<V>|!Array<V>} A new collection with the new values.  If
+ *     col is a key-less collection an array is returned.  If col has keys and
+ *     values a plain old JS object is returned.
+ * @template T,S,V
  */
-goog.fx.Dragger.prototype.screenY = 0;
-
+goog.structs.map = function(col, f, opt_obj) {
+  if (typeof col.map == 'function') {
+    return col.map(f, opt_obj);
+  }
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    return goog.array.map(/** @type {!Array<?>} */ (col), f, opt_obj);
+  }
 
-/**
- * The x position where the first mousedown or touchstart occurred.
- * @type {number}
- */
-goog.fx.Dragger.prototype.startX = 0;
+  var rv;
+  var keys = goog.structs.getKeys(col);
+  var values = goog.structs.getValues(col);
+  var l = values.length;
+  if (keys) {
+    rv = {};
+    for (var i = 0; i < l; i++) {
+      rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
+    }
+  } else {
+    // We should not use goog.array.map here since we want to make sure that
+    // the index is undefined as well as make sure that col is passed to the
+    // function.
+    rv = [];
+    for (var i = 0; i < l; i++) {
+      rv[i] = f.call(opt_obj, values[i], undefined, col);
+    }
+  }
+  return rv;
+};
 
 
 /**
- * The y position where the first mousedown or touchstart occurred.
- * @type {number}
+ * Calls f for each value in a collection. If any call returns true this returns
+ * true (without checking the rest). If all returns false this returns false.
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):boolean} f The function to call for every
+ *     value. This function takes 3 arguments (the value, the key or undefined
+ *     if the collection has no notion of keys, and the collection) and should
+ *     return a boolean.
+ * @param {T=} opt_obj  The object to be used as the value of 'this'
+ *     within {@code f}.
+ * @return {boolean} True if any value passes the test.
+ * @template T,S
  */
-goog.fx.Dragger.prototype.startY = 0;
+goog.structs.some = function(col, f, opt_obj) {
+  if (typeof col.some == 'function') {
+    return col.some(f, opt_obj);
+  }
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    return goog.array.some(/** @type {!Array<?>} */ (col), f, opt_obj);
+  }
+  var keys = goog.structs.getKeys(col);
+  var values = goog.structs.getValues(col);
+  var l = values.length;
+  for (var i = 0; i < l; i++) {
+    if (f.call(opt_obj, values[i], keys && keys[i], col)) {
+      return true;
+    }
+  }
+  return false;
+};
 
 
 /**
- * Current x position of drag relative to target's parent.
- * @type {number}
+ * Calls f for each value in a collection. If all calls return true this return
+ * true this returns true. If any returns false this returns false at this point
+ *  and does not continue to check the remaining values.
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):boolean} f The function to call for every
+ *     value. This function takes 3 arguments (the value, the key or
+ *     undefined if the collection has no notion of keys, and the collection)
+ *     and should return a boolean.
+ * @param {T=} opt_obj  The object to be used as the value of 'this'
+ *     within {@code f}.
+ * @return {boolean} True if all key-value pairs pass the test.
+ * @template T,S
  */
-goog.fx.Dragger.prototype.deltaX = 0;
+goog.structs.every = function(col, f, opt_obj) {
+  if (typeof col.every == 'function') {
+    return col.every(f, opt_obj);
+  }
+  if (goog.isArrayLike(col) || goog.isString(col)) {
+    return goog.array.every(/** @type {!Array<?>} */ (col), f, opt_obj);
+  }
+  var keys = goog.structs.getKeys(col);
+  var values = goog.structs.getValues(col);
+  var l = values.length;
+  for (var i = 0; i < l; i++) {
+    if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
+      return false;
+    }
+  }
+  return true;
+};
 
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Current y position of drag relative to target's parent.
- * @type {number}
+ * @fileoverview Python style iteration utilities.
+ * @author arv@google.com (Erik Arvidsson)
  */
-goog.fx.Dragger.prototype.deltaY = 0;
 
 
-/**
- * The current page scroll value.
- * @type {goog.math.Coordinate}
- */
-goog.fx.Dragger.prototype.pageScroll;
-
+goog.provide('goog.iter');
+goog.provide('goog.iter.Iterable');
+goog.provide('goog.iter.Iterator');
+goog.provide('goog.iter.StopIteration');
 
-/**
- * Whether dragging is currently enabled.
- * @type {boolean}
- * @private
- */
-goog.fx.Dragger.prototype.enabled_ = true;
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('goog.math');
 
 
 /**
- * Whether object is currently being dragged.
- * @type {boolean}
- * @private
+ * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
  */
-goog.fx.Dragger.prototype.dragging_ = false;
+goog.iter.Iterable;
 
 
 /**
- * The amount of distance, in pixels, after which a mousedown or touchstart is
- * considered a drag.
- * @type {number}
- * @private
+ * Singleton Error object that is used to terminate iterations.
+ * @const {!Error}
  */
-goog.fx.Dragger.prototype.hysteresisDistanceSquared_ = 0;
-
+goog.iter.StopIteration = ('StopIteration' in goog.global) ?
+    // For script engines that support legacy iterators.
+    goog.global['StopIteration'] :
+    Error('StopIteration');
 
-/**
- * Timestamp of when the mousedown or touchstart occurred.
- * @type {number}
- * @private
- */
-goog.fx.Dragger.prototype.mouseDownTime_ = 0;
 
 
 /**
- * Reference to a document object to use for the events.
- * @type {Document}
- * @private
+ * Class/interface for iterators.  An iterator needs to implement a {@code next}
+ * method and it needs to throw a {@code goog.iter.StopIteration} when the
+ * iteration passes beyond the end.  Iterators have no {@code hasNext} method.
+ * It is recommended to always use the helper functions to iterate over the
+ * iterator or in case you are only targeting JavaScript 1.7 for in loops.
+ * @constructor
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.document_;
+goog.iter.Iterator = function() {};
 
 
 /**
- * The SCROLL event target used to make drag element follow scrolling.
- * @type {EventTarget}
- * @private
+ * Returns the next value of the iteration.  This will throw the object
+ * {@see goog.iter#StopIteration} when the iteration passes the end.
+ * @return {VALUE} Any object or value.
  */
-goog.fx.Dragger.prototype.scrollTarget_;
+goog.iter.Iterator.prototype.next = function() {
+  throw goog.iter.StopIteration;
+};
 
 
 /**
- * Whether IE drag events cancelling is on.
- * @type {boolean}
- * @private
+ * Returns the {@code Iterator} object itself.  This is used to implement
+ * the iterator protocol in JavaScript 1.7
+ * @param {boolean=} opt_keys  Whether to return the keys or values. Default is
+ *     to only return the values.  This is being used by the for-in loop (true)
+ *     and the for-each-in loop (false).  Even though the param gives a hint
+ *     about what the iterator will return there is no guarantee that it will
+ *     return the keys when true is passed.
+ * @return {!goog.iter.Iterator<VALUE>} The object itself.
  */
-goog.fx.Dragger.prototype.ieDragStartCancellingOn_ = false;
+goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
+  return this;
+};
 
 
 /**
- * Whether the dragger implements the changes described in http://b/6324964,
- * making it truly RTL.  This is a temporary flag to allow clients to transition
- * to the new behavior at their convenience.  At some point it will be the
- * default.
- * @type {boolean}
- * @private
+ * Returns an iterator that knows how to iterate over the values in the object.
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable  If the
+ *     object is an iterator it will be returned as is.  If the object has an
+ *     {@code __iterator__} method that will be called to get the value
+ *     iterator.  If the object is an array-like object we create an iterator
+ *     for that.
+ * @return {!goog.iter.Iterator<VALUE>} An iterator that knows how to iterate
+ *     over the values in {@code iterable}.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.useRightPositioningForRtl_ = false;
+goog.iter.toIterator = function(iterable) {
+  if (iterable instanceof goog.iter.Iterator) {
+    return iterable;
+  }
+  if (typeof iterable.__iterator__ == 'function') {
+    return iterable.__iterator__(false);
+  }
+  if (goog.isArrayLike(iterable)) {
+    var i = 0;
+    var newIter = new goog.iter.Iterator;
+    newIter.next = function() {
+      while (true) {
+        if (i >= iterable.length) {
+          throw goog.iter.StopIteration;
+        }
+        // Don't include deleted elements.
+        if (!(i in iterable)) {
+          i++;
+          continue;
+        }
+        return iterable[i++];
+      }
+    };
+    return newIter;
+  }
 
 
-/**
- * Turns on/off true RTL behavior.  This should be called immediately after
- * construction.  This is a temporary flag to allow clients to transition
- * to the new component at their convenience.  At some point true will be the
- * default.
- * @param {boolean} useRightPositioningForRtl True if "right" should be used for
- *     positioning, false if "left" should be used for positioning.
- */
-goog.fx.Dragger.prototype.enableRightPositioningForRtl =
-    function(useRightPositioningForRtl) {
-  this.useRightPositioningForRtl_ = useRightPositioningForRtl;
+  // TODO(arv): Should we fall back on goog.structs.getValues()?
+  throw Error('Not implemented');
 };
 
 
 /**
- * Returns the event handler, intended for subclass use.
- * @return {goog.events.EventHandler.<T>} The event handler.
- * @this T
- * @template T
+ * Calls a function for each element in the iterator with the element of the
+ * iterator passed as argument.
+ *
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable  The iterator
+ *     to iterate over. If the iterable is an object {@code toIterator} will be
+ *     called on it.
+ * @param {function(this:THIS,VALUE,?,!goog.iter.Iterator<VALUE>)} f
+ *     The function to call for every element.  This function takes 3 arguments
+ *     (the element, undefined, and the iterator) and the return value is
+ *     irrelevant.  The reason for passing undefined as the second argument is
+ *     so that the same function can be used in {@see goog.array#forEach} as
+ *     well as others.  The third parameter is of type "number" for
+ *     arraylike objects, undefined, otherwise.
+ * @param {THIS=} opt_obj  The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.getHandler = function() {
-  return this.eventHandler_;
+goog.iter.forEach = function(iterable, f, opt_obj) {
+  if (goog.isArrayLike(iterable)) {
+    /** @preserveTry */
+    try {
+      // NOTES: this passes the index number to the second parameter
+      // of the callback contrary to the documentation above.
+      goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
+                         opt_obj);
+    } catch (ex) {
+      if (ex !== goog.iter.StopIteration) {
+        throw ex;
+      }
+    }
+  } else {
+    iterable = goog.iter.toIterator(iterable);
+    /** @preserveTry */
+    try {
+      while (true) {
+        f.call(opt_obj, iterable.next(), undefined, iterable);
+      }
+    } catch (ex) {
+      if (ex !== goog.iter.StopIteration) {
+        throw ex;
+      }
+    }
+  }
 };
 
 
 /**
- * Sets (or reset) the Drag limits after a Dragger is created.
- * @param {goog.math.Rect?} limits Object containing left, top, width,
- *     height for new Dragger limits. If target is right-to-left and
- *     enableRightPositioningForRtl(true) is called, then rect is interpreted as
- *     right, top, width, and height.
- */
-goog.fx.Dragger.prototype.setLimits = function(limits) {
-  this.limits = limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
-};
-
-
-/**
- * Sets the distance the user has to drag the element before a drag operation is
- * started.
- * @param {number} distance The number of pixels after which a mousedown and
- *     move is considered a drag.
+ * Calls a function for every element in the iterator, and if the function
+ * returns true adds the element to a new iterator.
+ *
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     to iterate over.
+ * @param {
+ *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
+ *     The function to call for every element. This function takes 3 arguments
+ *     (the element, undefined, and the iterator) and should return a boolean.
+ *     If the return value is true the element will be included in the returned
+ *     iterator.  If it is false the element is not included.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements
+ *     that passed the test are present.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.setHysteresis = function(distance) {
-  this.hysteresisDistanceSquared_ = Math.pow(distance, 2);
+goog.iter.filter = function(iterable, f, opt_obj) {
+  var iterator = goog.iter.toIterator(iterable);
+  var newIter = new goog.iter.Iterator;
+  newIter.next = function() {
+    while (true) {
+      var val = iterator.next();
+      if (f.call(opt_obj, val, undefined, iterator)) {
+        return val;
+      }
+    }
+  };
+  return newIter;
 };
 
 
 /**
- * Gets the distance the user has to drag the element before a drag operation is
- * started.
- * @return {number} distance The number of pixels after which a mousedown and
- *     move is considered a drag.
+ * Calls a function for every element in the iterator, and if the function
+ * returns false adds the element to a new iterator.
+ *
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     to iterate over.
+ * @param {
+ *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
+ *     The function to call for every element. This function takes 3 arguments
+ *     (the element, undefined, and the iterator) and should return a boolean.
+ *     If the return value is false the element will be included in the returned
+ *     iterator.  If it is true the element is not included.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements
+ *     that did not pass the test are present.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.getHysteresis = function() {
-  return Math.sqrt(this.hysteresisDistanceSquared_);
+goog.iter.filterFalse = function(iterable, f, opt_obj) {
+  return goog.iter.filter(iterable, goog.functions.not(f), opt_obj);
 };
 
 
 /**
- * Sets the SCROLL event target to make drag element follow scrolling.
+ * Creates a new iterator that returns the values in a range.  This function
+ * can take 1, 2 or 3 arguments:
+ * <pre>
+ * range(5) same as range(0, 5, 1)
+ * range(2, 5) same as range(2, 5, 1)
+ * </pre>
  *
- * @param {EventTarget} scrollTarget The event target that dispatches SCROLL
- *     events.
+ * @param {number} startOrStop  The stop value if only one argument is provided.
+ *     The start value if 2 or more arguments are provided.  If only one
+ *     argument is used the start value is 0.
+ * @param {number=} opt_stop  The stop value.  If left out then the first
+ *     argument is used as the stop value.
+ * @param {number=} opt_step  The number to increment with between each call to
+ *     next.  This can be negative.
+ * @return {!goog.iter.Iterator<number>} A new iterator that returns the values
+ *     in the range.
  */
-goog.fx.Dragger.prototype.setScrollTarget = function(scrollTarget) {
-  this.scrollTarget_ = scrollTarget;
+goog.iter.range = function(startOrStop, opt_stop, opt_step) {
+  var start = 0;
+  var stop = startOrStop;
+  var step = opt_step || 1;
+  if (arguments.length > 1) {
+    start = startOrStop;
+    stop = opt_stop;
+  }
+  if (step == 0) {
+    throw Error('Range step argument must not be zero');
+  }
+
+  var newIter = new goog.iter.Iterator;
+  newIter.next = function() {
+    if (step > 0 && start >= stop || step < 0 && start <= stop) {
+      throw goog.iter.StopIteration;
+    }
+    var rv = start;
+    start += step;
+    return rv;
+  };
+  return newIter;
 };
 
 
 /**
- * Enables cancelling of built-in IE drag events.
- * @param {boolean} cancelIeDragStart Whether to enable cancelling of IE
- *     dragstart event.
+ * Joins the values in a iterator with a delimiter.
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     to get the values from.
+ * @param {string} deliminator  The text to put between the values.
+ * @return {string} The joined value string.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.setCancelIeDragStart = function(cancelIeDragStart) {
-  this.ieDragStartCancellingOn_ = cancelIeDragStart;
+goog.iter.join = function(iterable, deliminator) {
+  return goog.iter.toArray(iterable).join(deliminator);
 };
 
 
 /**
- * @return {boolean} Whether the dragger is enabled.
+ * For every element in the iterator call a function and return a new iterator
+ * with that value.
+ *
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterator to iterate over.
+ * @param {
+ *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):RESULT} f
+ *     The function to call for every element.  This function takes 3 arguments
+ *     (the element, undefined, and the iterator) and should return a new value.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the
+ *     results of applying the function to each element in the original
+ *     iterator.
+ * @template THIS, VALUE, RESULT
  */
-goog.fx.Dragger.prototype.getEnabled = function() {
-  return this.enabled_;
+goog.iter.map = function(iterable, f, opt_obj) {
+  var iterator = goog.iter.toIterator(iterable);
+  var newIter = new goog.iter.Iterator;
+  newIter.next = function() {
+    var val = iterator.next();
+    return f.call(opt_obj, val, undefined, iterator);
+  };
+  return newIter;
 };
 
 
 /**
- * Set whether dragger is enabled
- * @param {boolean} enabled Whether dragger is enabled.
+ * Passes every element of an iterator into a function and accumulates the
+ * result.
+ *
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     to iterate over.
+ * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for
+ *     every element. This function takes 2 arguments (the function's previous
+ *     result or the initial value, and the value of the current element).
+ *     function(previousValue, currentElement) : newValue.
+ * @param {VALUE} val The initial value to pass into the function on the first
+ *     call.
+ * @param {THIS=} opt_obj  The object to be used as the value of 'this' within
+ *     f.
+ * @return {VALUE} Result of evaluating f repeatedly across the values of
+ *     the iterator.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.setEnabled = function(enabled) {
-  this.enabled_ = enabled;
-};
-
-
-/** @override */
-goog.fx.Dragger.prototype.disposeInternal = function() {
-  goog.fx.Dragger.superClass_.disposeInternal.call(this);
-  goog.events.unlisten(this.handle,
-      [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN],
-      this.startDrag, false, this);
-  this.cleanUpAfterDragging_();
-
-  this.target = null;
-  this.handle = null;
+goog.iter.reduce = function(iterable, f, val, opt_obj) {
+  var rval = val;
+  goog.iter.forEach(iterable, function(val) {
+    rval = f.call(opt_obj, rval, val);
+  });
+  return rval;
 };
 
 
 /**
- * Whether the DOM element being manipulated is rendered right-to-left.
- * @return {boolean} True if the DOM element is rendered right-to-left, false
- *     otherwise.
- * @private
+ * Goes through the values in the iterator. Calls f for each of these, and if
+ * any of them returns true, this returns true (without checking the rest). If
+ * all return false this will return false.
+ *
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     object.
+ * @param {
+ *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
+ *     The function to call for every value. This function takes 3 arguments
+ *     (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @return {boolean} true if any value passes the test.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.isRightToLeft_ = function() {
-  if (!goog.isDef(this.rightToLeft_)) {
-    this.rightToLeft_ = goog.style.isRightToLeft(this.target);
+goog.iter.some = function(iterable, f, opt_obj) {
+  iterable = goog.iter.toIterator(iterable);
+  /** @preserveTry */
+  try {
+    while (true) {
+      if (f.call(opt_obj, iterable.next(), undefined, iterable)) {
+        return true;
+      }
+    }
+  } catch (ex) {
+    if (ex !== goog.iter.StopIteration) {
+      throw ex;
+    }
   }
-  return this.rightToLeft_;
+  return false;
 };
 
 
 /**
- * Event handler that is used to start the drag
- * @param {goog.events.BrowserEvent} e Event object.
+ * Goes through the values in the iterator. Calls f for each of these and if any
+ * of them returns false this returns false (without checking the rest). If all
+ * return true this will return true.
+ *
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     object.
+ * @param {
+ *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
+ *     The function to call for every value. This function takes 3 arguments
+ *     (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @return {boolean} true if every value passes the test.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.startDrag = function(e) {
-  var isMouseDown = e.type == goog.events.EventType.MOUSEDOWN;
-
-  // Dragger.startDrag() can be called by AbstractDragDrop with a mousemove
-  // event and IE does not report pressed mouse buttons on mousemove. Also,
-  // it does not make sense to check for the button if the user is already
-  // dragging.
-
-  if (this.enabled_ && !this.dragging_ &&
-      (!isMouseDown || e.isMouseActionButton())) {
-    this.maybeReinitTouchEvent_(e);
-    if (this.hysteresisDistanceSquared_ == 0) {
-      if (this.fireDragStart_(e)) {
-        this.dragging_ = true;
-        e.preventDefault();
-      } else {
-        // If the start drag is cancelled, don't setup for a drag.
-        return;
+goog.iter.every = function(iterable, f, opt_obj) {
+  iterable = goog.iter.toIterator(iterable);
+  /** @preserveTry */
+  try {
+    while (true) {
+      if (!f.call(opt_obj, iterable.next(), undefined, iterable)) {
+        return false;
       }
-    } else {
-      // Need to preventDefault for hysteresis to prevent page getting selected.
-      e.preventDefault();
     }
-    this.setupDragHandlers();
-
-    this.clientX = this.startX = e.clientX;
-    this.clientY = this.startY = e.clientY;
-    this.screenX = e.screenX;
-    this.screenY = e.screenY;
-    this.computeInitialPosition();
-    this.pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll();
-
-    this.mouseDownTime_ = goog.now();
-  } else {
-    this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL);
+  } catch (ex) {
+    if (ex !== goog.iter.StopIteration) {
+      throw ex;
+    }
   }
+  return true;
 };
 
 
 /**
- * Sets up event handlers when dragging starts.
- * @protected
+ * Takes zero or more iterables and returns one iterator that will iterate over
+ * them in the order chained.
+ * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any
+ *     number of iterable objects.
+ * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will
+ *     iterate over all the given iterables' contents.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.setupDragHandlers = function() {
-  var doc = this.document_;
-  var docEl = doc.documentElement;
-  // Use bubbling when we have setCapture since we got reports that IE has
-  // problems with the capturing events in combination with setCapture.
-  var useCapture = !goog.fx.Dragger.HAS_SET_CAPTURE_;
+goog.iter.chain = function(var_args) {
+  return goog.iter.chainFromIterable(arguments);
+};
 
-  this.eventHandler_.listen(doc,
-      [goog.events.EventType.TOUCHMOVE, goog.events.EventType.MOUSEMOVE],
-      this.handleMove_, useCapture);
-  this.eventHandler_.listen(doc,
-      [goog.events.EventType.TOUCHEND, goog.events.EventType.MOUSEUP],
-      this.endDrag, useCapture);
 
-  if (goog.fx.Dragger.HAS_SET_CAPTURE_) {
-    docEl.setCapture(false);
-    this.eventHandler_.listen(docEl,
-                              goog.events.EventType.LOSECAPTURE,
-                              this.endDrag);
-  } else {
-    // Make sure we stop the dragging if the window loses focus.
-    // Don't use capture in this listener because we only want to end the drag
-    // if the actual window loses focus. Since blur events do not bubble we use
-    // a bubbling listener on the window.
-    this.eventHandler_.listen(goog.dom.getWindow(doc),
-                              goog.events.EventType.BLUR,
-                              this.endDrag);
-  }
+/**
+ * Takes a single iterable containing zero or more iterables and returns one
+ * iterator that will iterate over each one in the order given.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.chain.from_iterable
+ * @param {goog.iter.Iterable} iterable The iterable of iterables to chain.
+ * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will
+ *     iterate over all the contents of the iterables contained within
+ *     {@code iterable}.
+ * @template VALUE
+ */
+goog.iter.chainFromIterable = function(iterable) {
+  var iterator = goog.iter.toIterator(iterable);
+  var iter = new goog.iter.Iterator();
+  var current = null;
 
-  if (goog.userAgent.IE && this.ieDragStartCancellingOn_) {
-    // Cancel IE's 'ondragstart' event.
-    this.eventHandler_.listen(doc, goog.events.EventType.DRAGSTART,
-                              goog.events.Event.preventDefault);
-  }
+  iter.next = function() {
+    while (true) {
+      if (current == null) {
+        var it = iterator.next();
+        current = goog.iter.toIterator(it);
+      }
+      try {
+        return current.next();
+      } catch (ex) {
+        if (ex !== goog.iter.StopIteration) {
+          throw ex;
+        }
+        current = null;
+      }
+    }
+  };
 
-  if (this.scrollTarget_) {
-    this.eventHandler_.listen(this.scrollTarget_, goog.events.EventType.SCROLL,
-                              this.onScroll_, useCapture);
-  }
+  return iter;
 };
 
 
 /**
- * Fires a goog.fx.Dragger.EventType.START event.
- * @param {goog.events.BrowserEvent} e Browser event that triggered the drag.
- * @return {boolean} False iff preventDefault was called on the DragEvent.
- * @private
+ * Builds a new iterator that iterates over the original, but skips elements as
+ * long as a supplied function returns true.
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     object.
+ * @param {
+ *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
+ *     The function to call for every value. This function takes 3 arguments
+ *     (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator that drops elements from
+ *     the original iterator as long as {@code f} is true.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.fireDragStart_ = function(e) {
-  return this.dispatchEvent(new goog.fx.DragEvent(
-      goog.fx.Dragger.EventType.START, this, e.clientX, e.clientY, e));
+goog.iter.dropWhile = function(iterable, f, opt_obj) {
+  var iterator = goog.iter.toIterator(iterable);
+  var newIter = new goog.iter.Iterator;
+  var dropping = true;
+  newIter.next = function() {
+    while (true) {
+      var val = iterator.next();
+      if (dropping && f.call(opt_obj, val, undefined, iterator)) {
+        continue;
+      } else {
+        dropping = false;
+      }
+      return val;
+    }
+  };
+  return newIter;
 };
 
 
 /**
- * Unregisters the event handlers that are only active during dragging, and
- * releases mouse capture.
- * @private
+ * Builds a new iterator that iterates over the original, but only as long as a
+ * supplied function returns true.
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     object.
+ * @param {
+ *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
+ *     The function to call for every value. This function takes 3 arguments
+ *     (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj This is used as the 'this' object in f when called.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator that keeps elements in
+ *     the original iterator as long as the function is true.
+ * @template THIS, VALUE
  */
-goog.fx.Dragger.prototype.cleanUpAfterDragging_ = function() {
-  this.eventHandler_.removeAll();
-  if (goog.fx.Dragger.HAS_SET_CAPTURE_) {
-    this.document_.releaseCapture();
-  }
+goog.iter.takeWhile = function(iterable, f, opt_obj) {
+  var iterator = goog.iter.toIterator(iterable);
+  var iter = new goog.iter.Iterator();
+  iter.next = function() {
+    var val = iterator.next();
+    if (f.call(opt_obj, val, undefined, iterator)) {
+      return val;
+    }
+    throw goog.iter.StopIteration;
+  };
+  return iter;
 };
 
 
 /**
- * Event handler that is used to end the drag.
- * @param {goog.events.BrowserEvent} e Event object.
- * @param {boolean=} opt_dragCanceled Whether the drag has been canceled.
+ * Converts the iterator to an array
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
+ *     to convert to an array.
+ * @return {!Array<VALUE>} An array of the elements the iterator iterates over.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.endDrag = function(e, opt_dragCanceled) {
-  this.cleanUpAfterDragging_();
-
-  if (this.dragging_) {
-    this.maybeReinitTouchEvent_(e);
-    this.dragging_ = false;
-
-    var x = this.limitX(this.deltaX);
-    var y = this.limitY(this.deltaY);
-    var dragCanceled = opt_dragCanceled ||
-        e.type == goog.events.EventType.TOUCHCANCEL;
-    this.dispatchEvent(new goog.fx.DragEvent(
-        goog.fx.Dragger.EventType.END, this, e.clientX, e.clientY, e, x, y,
-        dragCanceled));
-  } else {
-    this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL);
+goog.iter.toArray = function(iterable) {
+  // Fast path for array-like.
+  if (goog.isArrayLike(iterable)) {
+    return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable));
   }
+  iterable = goog.iter.toIterator(iterable);
+  var array = [];
+  goog.iter.forEach(iterable, function(val) {
+    array.push(val);
+  });
+  return array;
 };
 
 
 /**
- * Event handler that is used to end the drag by cancelling it.
- * @param {goog.events.BrowserEvent} e Event object.
+ * Iterates over two iterables and returns true if they contain the same
+ * sequence of elements and have the same length.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable1 The first
+ *     iterable object.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable2 The second
+ *     iterable object.
+ * @param {function(VALUE,VALUE):boolean=} opt_equalsFn Optional comparison
+ *     function.
+ *     Should take two arguments to compare, and return true if the arguments
+ *     are equal. Defaults to {@link goog.array.defaultCompareEquality} which
+ *     compares the elements using the built-in '===' operator.
+ * @return {boolean} true if the iterables contain the same sequence of elements
+ *     and have the same length.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.endDragCancel = function(e) {
-  this.endDrag(e, true);
+goog.iter.equals = function(iterable1, iterable2, opt_equalsFn) {
+  var fillValue = {};
+  var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2);
+  var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
+  return goog.iter.every(pairs, function(pair) {
+    return equalsFn(pair[0], pair[1]);
+  });
 };
 
 
 /**
- * Re-initializes the event with the first target touch event or, in the case
- * of a stop event, the last changed touch.
- * @param {goog.events.BrowserEvent} e A TOUCH... event.
- * @private
+ * Advances the iterator to the next position, returning the given default value
+ * instead of throwing an exception if the iterator has no more entries.
+ * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterable
+ *     object.
+ * @param {VALUE} defaultValue The value to return if the iterator is empty.
+ * @return {VALUE} The next item in the iteration, or defaultValue if the
+ *     iterator was empty.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.maybeReinitTouchEvent_ = function(e) {
-  var type = e.type;
-
-  if (type == goog.events.EventType.TOUCHSTART ||
-      type == goog.events.EventType.TOUCHMOVE) {
-    e.init(e.getBrowserEvent().targetTouches[0], e.currentTarget);
-  } else if (type == goog.events.EventType.TOUCHEND ||
-             type == goog.events.EventType.TOUCHCANCEL) {
-    e.init(e.getBrowserEvent().changedTouches[0], e.currentTarget);
+goog.iter.nextOrValue = function(iterable, defaultValue) {
+  try {
+    return goog.iter.toIterator(iterable).next();
+  } catch (e) {
+    if (e != goog.iter.StopIteration) {
+      throw e;
+    }
+    return defaultValue;
   }
 };
 
 
 /**
- * Event handler that is used on mouse / touch move to update the drag
- * @param {goog.events.BrowserEvent} e Event object.
- * @private
+ * Cartesian product of zero or more sets.  Gives an iterator that gives every
+ * combination of one element chosen from each set.  For example,
+ * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).
+ * @see http://docs.python.org/library/itertools.html#itertools.product
+ * @param {...!goog.array.ArrayLike<VALUE>} var_args Zero or more sets, as
+ *     arrays.
+ * @return {!goog.iter.Iterator<!Array<VALUE>>} An iterator that gives each
+ *     n-tuple (as an array).
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.handleMove_ = function(e) {
-  if (this.enabled_) {
-    this.maybeReinitTouchEvent_(e);
-    // dx in right-to-left cases is relative to the right.
-    var sign = this.useRightPositioningForRtl_ &&
-        this.isRightToLeft_() ? -1 : 1;
-    var dx = sign * (e.clientX - this.clientX);
-    var dy = e.clientY - this.clientY;
-    this.clientX = e.clientX;
-    this.clientY = e.clientY;
-    this.screenX = e.screenX;
-    this.screenY = e.screenY;
+goog.iter.product = function(var_args) {
+  var someArrayEmpty = goog.array.some(arguments, function(arr) {
+    return !arr.length;
+  });
 
-    if (!this.dragging_) {
-      var diffX = this.startX - this.clientX;
-      var diffY = this.startY - this.clientY;
-      var distance = diffX * diffX + diffY * diffY;
-      if (distance > this.hysteresisDistanceSquared_) {
-        if (this.fireDragStart_(e)) {
-          this.dragging_ = true;
-        } else {
-          // DragListGroup disposes of the dragger if BEFOREDRAGSTART is
-          // canceled.
-          if (!this.isDisposed()) {
-            this.endDrag(e);
-          }
-          return;
-        }
-      }
-    }
+  // An empty set in a cartesian product gives an empty set.
+  if (someArrayEmpty || !arguments.length) {
+    return new goog.iter.Iterator();
+  }
 
-    var pos = this.calculatePosition_(dx, dy);
-    var x = pos.x;
-    var y = pos.y;
+  var iter = new goog.iter.Iterator();
+  var arrays = arguments;
 
-    if (this.dragging_) {
+  // The first indices are [0, 0, ...]
+  var indicies = goog.array.repeat(0, arrays.length);
 
-      var rv = this.dispatchEvent(new goog.fx.DragEvent(
-          goog.fx.Dragger.EventType.BEFOREDRAG, this, e.clientX, e.clientY,
-          e, x, y));
+  iter.next = function() {
 
-      // Only do the defaultAction and dispatch drag event if predrag didn't
-      // prevent default
-      if (rv) {
-        this.doDrag(e, x, y, false);
-        e.preventDefault();
+    if (indicies) {
+      var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) {
+        return arrays[arrayIndex][valueIndex];
+      });
+
+      // Generate the next-largest indices for the next call.
+      // Increase the rightmost index. If it goes over, increase the next
+      // rightmost (like carry-over addition).
+      for (var i = indicies.length - 1; i >= 0; i--) {
+        // Assertion prevents compiler warning below.
+        goog.asserts.assert(indicies);
+        if (indicies[i] < arrays[i].length - 1) {
+          indicies[i]++;
+          break;
+        }
+
+        // We're at the last indices (the last element of every array), so
+        // the iteration is over on the next call.
+        if (i == 0) {
+          indicies = null;
+          break;
+        }
+        // Reset the index in this column and loop back to increment the
+        // next one.
+        indicies[i] = 0;
       }
+      return retVal;
     }
-  }
+
+    throw goog.iter.StopIteration;
+  };
+
+  return iter;
 };
 
 
 /**
- * Calculates the drag position.
- *
- * @param {number} dx The horizontal movement delta.
- * @param {number} dy The vertical movement delta.
- * @return {!goog.math.Coordinate} The newly calculated drag element position.
- * @private
+ * Create an iterator to cycle over the iterable's elements indefinitely.
+ * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...
+ * @see: http://docs.python.org/library/itertools.html#itertools.cycle.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable object.
+ * @return {!goog.iter.Iterator<VALUE>} An iterator that iterates indefinitely
+ *     over the values in {@code iterable}.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.calculatePosition_ = function(dx, dy) {
-  // Update the position for any change in body scrolling
-  var pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll();
-  dx += pageScroll.x - this.pageScroll.x;
-  dy += pageScroll.y - this.pageScroll.y;
-  this.pageScroll = pageScroll;
+goog.iter.cycle = function(iterable) {
+  var baseIterator = goog.iter.toIterator(iterable);
 
-  this.deltaX += dx;
-  this.deltaY += dy;
+  // We maintain a cache to store the iterable elements as we iterate
+  // over them. The cache is used to return elements once we have
+  // iterated over the iterable once.
+  var cache = [];
+  var cacheIndex = 0;
 
-  var x = this.limitX(this.deltaX);
-  var y = this.limitY(this.deltaY);
-  return new goog.math.Coordinate(x, y);
-};
+  var iter = new goog.iter.Iterator();
 
+  // This flag is set after the iterable is iterated over once
+  var useCache = false;
 
-/**
- * Event handler for scroll target scrolling.
- * @param {goog.events.BrowserEvent} e The event.
- * @private
- */
-goog.fx.Dragger.prototype.onScroll_ = function(e) {
-  var pos = this.calculatePosition_(0, 0);
-  e.clientX = this.clientX;
-  e.clientY = this.clientY;
-  this.doDrag(e, pos.x, pos.y, true);
+  iter.next = function() {
+    var returnElement = null;
+
+    // Pull elements off the original iterator if not using cache
+    if (!useCache) {
+      try {
+        // Return the element from the iterable
+        returnElement = baseIterator.next();
+        cache.push(returnElement);
+        return returnElement;
+      } catch (e) {
+        // If an exception other than StopIteration is thrown
+        // or if there are no elements to iterate over (the iterable was empty)
+        // throw an exception
+        if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) {
+          throw e;
+        }
+        // set useCache to true after we know that a 'StopIteration' exception
+        // was thrown and the cache is not empty (to handle the 'empty iterable'
+        // use case)
+        useCache = true;
+      }
+    }
+
+    returnElement = cache[cacheIndex];
+    cacheIndex = (cacheIndex + 1) % cache.length;
+
+    return returnElement;
+  };
+
+  return iter;
 };
 
 
 /**
- * @param {goog.events.BrowserEvent} e The closure object
- *     representing the browser event that caused a drag event.
- * @param {number} x The new horizontal position for the drag element.
- * @param {number} y The new vertical position for the drag element.
- * @param {boolean} dragFromScroll Whether dragging was caused by scrolling
- *     the associated scroll target.
- * @protected
+ * Creates an iterator that counts indefinitely from a starting value.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.count
+ * @param {number=} opt_start The starting value. Default is 0.
+ * @param {number=} opt_step The number to increment with between each call to
+ *     next. Negative and floating point numbers are allowed. Default is 1.
+ * @return {!goog.iter.Iterator<number>} A new iterator that returns the values
+ *     in the series.
  */
-goog.fx.Dragger.prototype.doDrag = function(e, x, y, dragFromScroll) {
-  this.defaultAction(x, y);
-  this.dispatchEvent(new goog.fx.DragEvent(
-      goog.fx.Dragger.EventType.DRAG, this, e.clientX, e.clientY, e, x, y));
+goog.iter.count = function(opt_start, opt_step) {
+  var counter = opt_start || 0;
+  var step = goog.isDef(opt_step) ? opt_step : 1;
+  var iter = new goog.iter.Iterator();
+
+  iter.next = function() {
+    var returnValue = counter;
+    counter += step;
+    return returnValue;
+  };
+
+  return iter;
 };
 
 
 /**
- * Returns the 'real' x after limits are applied (allows for some
- * limits to be undefined).
- * @param {number} x X-coordinate to limit.
- * @return {number} The 'real' X-coordinate after limits are applied.
+ * Creates an iterator that returns the same object or value repeatedly.
+ * @param {VALUE} value Any object or value to repeat.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the
+ *     repeated value.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.limitX = function(x) {
-  var rect = this.limits;
-  var left = !isNaN(rect.left) ? rect.left : null;
-  var width = !isNaN(rect.width) ? rect.width : 0;
-  var maxX = left != null ? left + width : Infinity;
-  var minX = left != null ? left : -Infinity;
-  return Math.min(maxX, Math.max(minX, x));
+goog.iter.repeat = function(value) {
+  var iter = new goog.iter.Iterator();
+
+  iter.next = goog.functions.constant(value);
+
+  return iter;
 };
 
 
 /**
- * Returns the 'real' y after limits are applied (allows for some
- * limits to be undefined).
- * @param {number} y Y-coordinate to limit.
- * @return {number} The 'real' Y-coordinate after limits are applied.
+ * Creates an iterator that returns running totals from the numbers in
+ * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields
+ * {@code 1 -> 3 -> 6 -> 10 -> 15}.
+ * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate
+ * @param {!goog.iter.Iterable<number>} iterable The iterable of numbers to
+ *     accumulate.
+ * @return {!goog.iter.Iterator<number>} A new iterator that returns the
+ *     numbers in the series.
  */
-goog.fx.Dragger.prototype.limitY = function(y) {
-  var rect = this.limits;
-  var top = !isNaN(rect.top) ? rect.top : null;
-  var height = !isNaN(rect.height) ? rect.height : 0;
-  var maxY = top != null ? top + height : Infinity;
-  var minY = top != null ? top : -Infinity;
-  return Math.min(maxY, Math.max(minY, y));
+goog.iter.accumulate = function(iterable) {
+  var iterator = goog.iter.toIterator(iterable);
+  var total = 0;
+  var iter = new goog.iter.Iterator();
+
+  iter.next = function() {
+    total += iterator.next();
+    return total;
+  };
+
+  return iter;
 };
 
 
 /**
- * Overridable function for computing the initial position of the target
- * before dragging begins.
- * @protected
+ * Creates an iterator that returns arrays containing the ith elements from the
+ * provided iterables. The returned arrays will be the same size as the number
+ * of iterables given in {@code var_args}. Once the shortest iterable is
+ * exhausted, subsequent calls to {@code next()} will throw
+ * {@code goog.iter.StopIteration}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.izip
+ * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any
+ *     number of iterable objects.
+ * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns
+ *     arrays of elements from the provided iterables.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.computeInitialPosition = function() {
-  this.deltaX = this.useRightPositioningForRtl_ ?
-      goog.style.bidi.getOffsetStart(this.target) : this.target.offsetLeft;
-  this.deltaY = this.target.offsetTop;
+goog.iter.zip = function(var_args) {
+  var args = arguments;
+  var iter = new goog.iter.Iterator();
+
+  if (args.length > 0) {
+    var iterators = goog.array.map(args, goog.iter.toIterator);
+    iter.next = function() {
+      var arr = goog.array.map(iterators, function(it) {
+        return it.next();
+      });
+      return arr;
+    };
+  }
+
+  return iter;
 };
 
 
 /**
- * Overridable function for handling the default action of the drag behaviour.
- * Normally this is simply moving the element to x,y though in some cases it
- * might be used to resize the layer.  This is basically a shortcut to
- * implementing a default ondrag event handler.
- * @param {number} x X-coordinate for target element. In right-to-left, x this
- *     is the number of pixels the target should be moved to from the right.
- * @param {number} y Y-coordinate for target element.
+ * Creates an iterator that returns arrays containing the ith elements from the
+ * provided iterables. The returned arrays will be the same size as the number
+ * of iterables given in {@code var_args}. Shorter iterables will be extended
+ * with {@code fillValue}. Once the longest iterable is exhausted, subsequent
+ * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest
+ * @param {VALUE} fillValue The object or value used to fill shorter iterables.
+ * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any
+ *     number of iterable objects.
+ * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns
+ *     arrays of elements from the provided iterables.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.defaultAction = function(x, y) {
-  if (this.useRightPositioningForRtl_ && this.isRightToLeft_()) {
-    this.target.style.right = x + 'px';
-  } else {
-    this.target.style.left = x + 'px';
+goog.iter.zipLongest = function(fillValue, var_args) {
+  var args = goog.array.slice(arguments, 1);
+  var iter = new goog.iter.Iterator();
+
+  if (args.length > 0) {
+    var iterators = goog.array.map(args, goog.iter.toIterator);
+
+    iter.next = function() {
+      var iteratorsHaveValues = false;  // false when all iterators are empty.
+      var arr = goog.array.map(iterators, function(it) {
+        var returnValue;
+        try {
+          returnValue = it.next();
+          // Iterator had a value, so we've not exhausted the iterators.
+          // Set flag accordingly.
+          iteratorsHaveValues = true;
+        } catch (ex) {
+          if (ex !== goog.iter.StopIteration) {
+            throw ex;
+          }
+          returnValue = fillValue;
+        }
+        return returnValue;
+      });
+
+      if (!iteratorsHaveValues) {
+        throw goog.iter.StopIteration;
+      }
+      return arr;
+    };
   }
-  this.target.style.top = y + 'px';
+
+  return iter;
 };
 
 
 /**
- * @return {boolean} Whether the dragger is currently in the midst of a drag.
+ * Creates an iterator that filters {@code iterable} based on a series of
+ * {@code selectors}. On each call to {@code next()}, one item is taken from
+ * both the {@code iterable} and {@code selectors} iterators. If the item from
+ * {@code selectors} evaluates to true, the item from {@code iterable} is given.
+ * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors}
+ * is exhausted, subsequent calls to {@code next()} will throw
+ * {@code goog.iter.StopIteration}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.compress
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to filter.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} selectors An
+ *     iterable of items to be evaluated in a boolean context to determine if
+ *     the corresponding element in {@code iterable} should be included in the
+ *     result.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the
+ *     filtered values.
+ * @template VALUE
  */
-goog.fx.Dragger.prototype.isDragging = function() {
-  return this.dragging_;
+goog.iter.compress = function(iterable, selectors) {
+  var selectorIterator = goog.iter.toIterator(selectors);
+
+  return goog.iter.filter(iterable, function() {
+    return !!selectorIterator.next();
+  });
 };
 
 
 
 /**
- * Object representing a drag event
- * @param {string} type Event type.
- * @param {goog.fx.Dragger} dragobj Drag object initiating event.
- * @param {number} clientX X-coordinate relative to the viewport.
- * @param {number} clientY Y-coordinate relative to the viewport.
- * @param {goog.events.BrowserEvent} browserEvent The closure object
- *   representing the browser event that caused this drag event.
- * @param {number=} opt_actX Optional actual x for drag if it has been limited.
- * @param {number=} opt_actY Optional actual y for drag if it has been limited.
- * @param {boolean=} opt_dragCanceled Whether the drag has been canceled.
+ * Implements the {@code goog.iter.groupBy} iterator.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to group.
+ * @param {function(...[VALUE]): KEY=} opt_keyFunc  Optional function for
+ *     determining the key value for each group in the {@code iterable}. Default
+ *     is the identity function.
  * @constructor
- * @extends {goog.events.Event}
+ * @extends {goog.iter.Iterator<!Array<?>>}
+ * @template KEY, VALUE
+ * @private
  */
-goog.fx.DragEvent = function(type, dragobj, clientX, clientY, browserEvent,
-                             opt_actX, opt_actY, opt_dragCanceled) {
-  goog.events.Event.call(this, type);
+goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) {
 
   /**
-   * X-coordinate relative to the viewport
-   * @type {number}
+   * The iterable to group, coerced to an iterator.
+   * @type {!goog.iter.Iterator}
    */
-  this.clientX = clientX;
+  this.iterator = goog.iter.toIterator(iterable);
 
   /**
-   * Y-coordinate relative to the viewport
-   * @type {number}
+   * A function for determining the key value for each element in the iterable.
+   * If no function is provided, the identity function is used and returns the
+   * element unchanged.
+   * @type {function(...[VALUE]): KEY}
    */
-  this.clientY = clientY;
+  this.keyFunc = opt_keyFunc || goog.functions.identity;
 
   /**
-   * The closure object representing the browser event that caused this drag
-   * event.
-   * @type {goog.events.BrowserEvent}
+   * The target key for determining the start of a group.
+   * @type {KEY}
    */
-  this.browserEvent = browserEvent;
+  this.targetKey;
 
   /**
-   * The real x-position of the drag if it has been limited
-   * @type {number}
+   * The current key visited during iteration.
+   * @type {KEY}
    */
-  this.left = goog.isDef(opt_actX) ? opt_actX : dragobj.deltaX;
+  this.currentKey;
 
   /**
-   * The real y-position of the drag if it has been limited
-   * @type {number}
+   * The current value being added to the group.
+   * @type {VALUE}
    */
-  this.top = goog.isDef(opt_actY) ? opt_actY : dragobj.deltaY;
+  this.currentValue;
+};
+goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator);
 
-  /**
-   * Reference to the drag object for this event
-   * @type {goog.fx.Dragger}
-   */
-  this.dragger = dragobj;
 
-  /**
-   * Whether drag was canceled with this event. Used to differentiate between
-   * a legitimate drag END that can result in an action and a drag END which is
-   * a result of a drag cancelation. For now it can happen 1) with drag END
-   * event on FireFox when user drags the mouse out of the window, 2) with
-   * drag END event on IE7 which is generated on MOUSEMOVE event when user
-   * moves the mouse into the document after the mouse button has been
-   * released, 3) when TOUCHCANCEL is raised instead of TOUCHEND (on touch
-   * events).
-   * @type {boolean}
-   */
-  this.dragCanceled = !!opt_dragCanceled;
+/** @override */
+goog.iter.GroupByIterator_.prototype.next = function() {
+  while (this.currentKey == this.targetKey) {
+    this.currentValue = this.iterator.next();  // Exits on StopIteration
+    this.currentKey = this.keyFunc(this.currentValue);
+  }
+  this.targetKey = this.currentKey;
+  return [this.currentKey, this.groupItems_(this.targetKey)];
 };
-goog.inherits(goog.fx.DragEvent, goog.events.Event);
 
-// FIXME should possibly show tooltip when dragging?
 
-goog.provide('ol.control.ZoomSlider');
+/**
+ * Performs the grouping of objects using the given key.
+ * @param {KEY} targetKey  The target key object for the group.
+ * @return {!Array<VALUE>} An array of grouped objects.
+ * @private
+ */
+goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) {
+  var arr = [];
+  while (this.currentKey == targetKey) {
+    arr.push(this.currentValue);
+    try {
+      this.currentValue = this.iterator.next();
+    } catch (ex) {
+      if (ex !== goog.iter.StopIteration) {
+        throw ex;
+      }
+      break;
+    }
+    this.currentKey = this.keyFunc(this.currentValue);
+  }
+  return arr;
+};
 
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventType');
-goog.require('goog.fx.Dragger');
-goog.require('goog.fx.Dragger.EventType');
-goog.require('goog.math');
-goog.require('goog.math.Rect');
-goog.require('goog.style');
-goog.require('ol');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
 
+/**
+ * Creates an iterator that returns arrays containing elements from the
+ * {@code iterable} grouped by a key value. For iterables with repeated
+ * elements (i.e. sorted according to a particular key function), this function
+ * has a {@code uniq}-like effect. For example, grouping the array:
+ * {@code [A, B, B, C, C, A]} produces
+ * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.groupby
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to group.
+ * @param {function(...[VALUE]): KEY=} opt_keyFunc  Optional function for
+ *     determining the key value for each group in the {@code iterable}. Default
+ *     is the identity function.
+ * @return {!goog.iter.Iterator<!Array<?>>} A new iterator that returns
+ *     arrays of consecutive key and groups.
+ * @template KEY, VALUE
+ */
+goog.iter.groupBy = function(iterable, opt_keyFunc) {
+  return new goog.iter.GroupByIterator_(iterable, opt_keyFunc);
+};
 
 
 /**
- * @classdesc
- * A slider type of control for zooming.
- *
- * Example:
+ * Gives an iterator that gives the result of calling the given function
+ * <code>f</code> with the arguments taken from the next element from
+ * <code>iterable</code> (the elements are expected to also be iterables).
  *
- *     map.addControl(new ol.control.ZoomSlider());
+ * Similar to {@see goog.iter#map} but allows the function to accept multiple
+ * arguments from the iterable.
  *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options.
- * @api stable
+ * @param {!goog.iter.Iterable<!goog.iter.Iterable>} iterable The iterable of
+ *     iterables to iterate over.
+ * @param {function(this:THIS,...[*]):RESULT} f The function to call for every
+ *     element.  This function takes N+2 arguments, where N represents the
+ *     number of items from the next element of the iterable. The two
+ *     additional arguments passed to the function are undefined and the
+ *     iterator itself. The function should return a new value.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ *     {@code f}.
+ * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the
+ *     results of applying the function to each element in the original
+ *     iterator.
+ * @template THIS, RESULT
  */
-ol.control.ZoomSlider = function(opt_options) {
+goog.iter.starMap = function(iterable, f, opt_obj) {
+  var iterator = goog.iter.toIterator(iterable);
+  var iter = new goog.iter.Iterator();
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  iter.next = function() {
+    var args = goog.iter.toArray(iterator.next());
+    return f.apply(opt_obj, goog.array.concat(args, undefined, iterator));
+  };
 
-  /**
-   * Will hold the current resolution of the view.
-   *
-   * @type {number|undefined}
-   * @private
-   */
-  this.currentResolution_ = undefined;
+  return iter;
+};
 
-  /**
-   * 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;
 
-  /**
-   * Whether the slider is initialized.
-   * @type {boolean}
-   * @private
-   */
-  this.sliderInitialized_ = false;
+/**
+ * Returns an array of iterators each of which can iterate over the values in
+ * {@code iterable} without advancing the others.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.tee
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to tee.
+ * @param {number=} opt_num  The number of iterators to create. Default is 2.
+ * @return {!Array<goog.iter.Iterator<VALUE>>} An array of iterators.
+ * @template VALUE
+ */
+goog.iter.tee = function(iterable, opt_num) {
+  var iterator = goog.iter.toIterator(iterable);
+  var num = goog.isNumber(opt_num) ? opt_num : 2;
+  var buffers = goog.array.map(goog.array.range(num), function() {
+    return [];
+  });
 
-  var className = goog.isDef(options.className) ?
-      options.className : 'ol-zoomslider';
-  var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV,
-      [className + '-thumb', ol.css.CLASS_UNSELECTABLE]);
-  var sliderElement = goog.dom.createDom(goog.dom.TagName.DIV,
-      [className, ol.css.CLASS_UNSELECTABLE], thumbElement);
+  var addNextIteratorValueToBuffers = function() {
+    var val = iterator.next();
+    goog.array.forEach(buffers, function(buffer) {
+      buffer.push(val);
+    });
+  };
 
-  /**
-   * @type {goog.fx.Dragger}
-   * @private
-   */
-  this.dragger_ = new goog.fx.Dragger(thumbElement);
-  this.registerDisposable(this.dragger_);
+  var createIterator = function(buffer) {
+    // Each tee'd iterator has an associated buffer (initially empty). When a
+    // tee'd iterator's buffer is empty, it calls
+    // addNextIteratorValueToBuffers(), adding the next value to all tee'd
+    // iterators' buffers, and then returns that value. This allows each
+    // iterator to be advanced independently.
+    var iter = new goog.iter.Iterator();
 
-  goog.events.listen(this.dragger_, [
-    goog.fx.Dragger.EventType.DRAG,
-    goog.fx.Dragger.EventType.END
-  ], this.handleSliderChange_, undefined, this);
+    iter.next = function() {
+      if (goog.array.isEmpty(buffer)) {
+        addNextIteratorValueToBuffers();
+      }
+      goog.asserts.assert(!goog.array.isEmpty(buffer));
+      return buffer.shift();
+    };
 
-  goog.events.listen(sliderElement, goog.events.EventType.CLICK,
-      this.handleContainerClick_, false, this);
-  goog.events.listen(thumbElement, goog.events.EventType.CLICK,
-      goog.events.Event.stopPropagation);
+    return iter;
+  };
 
-  goog.base(this, {
-    element: sliderElement
-  });
+  return goog.array.map(buffers, createIterator);
 };
-goog.inherits(ol.control.ZoomSlider, ol.control.Control);
 
 
 /**
- * The enum for available directions.
- *
- * @enum {number}
+ * Creates an iterator that returns arrays containing a count and an element
+ * obtained from the given {@code iterable}.
+ * @see http://docs.python.org/2/library/functions.html#enumerate
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to enumerate.
+ * @param {number=} opt_start  Optional starting value. Default is 0.
+ * @return {!goog.iter.Iterator<!Array<?>>} A new iterator containing
+ *     count/item pairs.
+ * @template VALUE
  */
-ol.control.ZoomSlider.direction = {
-  VERTICAL: 0,
-  HORIZONTAL: 1
+goog.iter.enumerate = function(iterable, opt_start) {
+  return goog.iter.zip(goog.iter.count(opt_start), iterable);
 };
 
 
 /**
- * @inheritDoc
+ * Creates an iterator that returns the first {@code limitSize} elements from an
+ * iterable. If this number is greater than the number of elements in the
+ * iterable, all the elements are returned.
+ * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to limit.
+ * @param {number} limitSize  The maximum number of elements to return.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator containing
+ *     {@code limitSize} elements.
+ * @template VALUE
  */
-ol.control.ZoomSlider.prototype.setMap = function(map) {
-  goog.base(this, 'setMap', map);
-  if (!goog.isNull(map)) {
-    map.render();
-  }
+goog.iter.limit = function(iterable, limitSize) {
+  goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0);
+
+  var iterator = goog.iter.toIterator(iterable);
+
+  var iter = new goog.iter.Iterator();
+  var remaining = limitSize;
+
+  iter.next = function() {
+    if (remaining-- > 0) {
+      return iterator.next();
+    }
+    throw goog.iter.StopIteration;
+  };
+
+  return iter;
 };
 
 
 /**
- * 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
+ * Creates an iterator that is advanced {@code count} steps ahead. Consumed
+ * values are silently discarded. If {@code count} is greater than the number
+ * of elements in {@code iterable}, an empty iterator is returned. Subsequent
+ * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to consume.
+ * @param {number} count  The number of elements to consume from the iterator.
+ * @return {!goog.iter.Iterator<VALUE>} An iterator advanced zero or more steps
+ *     ahead.
+ * @template VALUE
  */
-ol.control.ZoomSlider.prototype.initSlider_ = function() {
-  var container = this.element,
-      thumb = goog.dom.getFirstElementChild(container),
-      elemSize = goog.style.getContentBoxSize(container),
-      thumbBounds = goog.style.getBounds(thumb),
-      thumbMargins = goog.style.getMarginBox(thumb),
-      thumbBorderBox = goog.style.getBorderBox(thumb),
-      w = elemSize.width -
-          thumbMargins.left - thumbMargins.right -
-          thumbBorderBox.left - thumbBorderBox.right -
-          thumbBounds.width,
-      h = elemSize.height -
-          thumbMargins.top - thumbMargins.bottom -
-          thumbBorderBox.top - thumbBorderBox.bottom -
-          thumbBounds.height,
-      limits;
-  if (elemSize.width > elemSize.height) {
-    this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL;
-    limits = new goog.math.Rect(0, 0, w, 0);
-  } else {
-    this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;
-    limits = new goog.math.Rect(0, 0, 0, h);
+goog.iter.consume = function(iterable, count) {
+  goog.asserts.assert(goog.math.isInt(count) && count >= 0);
+
+  var iterator = goog.iter.toIterator(iterable);
+
+  while (count-- > 0) {
+    goog.iter.nextOrValue(iterator, null);
   }
-  this.dragger_.setLimits(limits);
-  this.sliderInitialized_ = true;
+
+  return iterator;
 };
 
 
 /**
- * @inheritDoc
+ * Creates an iterator that returns a range of elements from an iterable.
+ * Similar to {@see goog.array#slice} but does not support negative indexes.
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to slice.
+ * @param {number} start  The index of the first element to return.
+ * @param {number=} opt_end  The index after the last element to return. If
+ *     defined, must be greater than or equal to {@code start}.
+ * @return {!goog.iter.Iterator<VALUE>} A new iterator containing a slice of
+ *     the original.
+ * @template VALUE
  */
-ol.control.ZoomSlider.prototype.handleMapPostrender = function(mapEvent) {
-  if (goog.isNull(mapEvent.frameState)) {
-    return;
-  }
-  goog.asserts.assert(
-      goog.isDefAndNotNull(mapEvent.frameState.viewState));
-  if (!this.sliderInitialized_) {
-    this.initSlider_();
-  }
-  var res = mapEvent.frameState.viewState.resolution;
-  if (res !== this.currentResolution_) {
-    this.currentResolution_ = res;
-    this.positionThumbForResolution_(res);
+goog.iter.slice = function(iterable, start, opt_end) {
+  goog.asserts.assert(goog.math.isInt(start) && start >= 0);
+
+  var iterator = goog.iter.consume(iterable, start);
+
+  if (goog.isNumber(opt_end)) {
+    goog.asserts.assert(
+        goog.math.isInt(/** @type {number} */ (opt_end)) && opt_end >= start);
+    iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */);
   }
+
+  return iterator;
 };
 
 
 /**
- * @param {goog.events.BrowserEvent} browserEvent The browser event to handle.
+ * Checks an array for duplicate elements.
+ * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to check for
+ *     duplicates.
+ * @return {boolean} True, if the array contains duplicates, false otherwise.
  * @private
+ * @template VALUE
  */
-ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) {
-  var map = this.getMap();
-  var view = map.getView();
-  var resolution;
-  var amountDragged = this.amountDragged_(browserEvent.offsetX,
-      browserEvent.offsetY);
-  resolution = this.resolutionForAmount_(amountDragged);
-  goog.asserts.assert(goog.isDef(resolution));
-  map.beforeRender(ol.animation.zoom({
-    resolution: resolution,
-    duration: ol.ZOOMSLIDER_ANIMATION_DURATION,
-    easing: ol.easing.easeOut
-  }));
-  resolution = view.constrainResolution(resolution);
-  view.setResolution(resolution);
+// TODO(user): Consider moving this into goog.array as a public function.
+goog.iter.hasDuplicates_ = function(arr) {
+  var deduped = [];
+  goog.array.removeDuplicates(arr, deduped);
+  return arr.length != deduped.length;
 };
 
 
 /**
- * Positions the thumb inside its container according to the given resolution.
+ * Creates an iterator that returns permutations of elements in
+ * {@code iterable}.
  *
- * @param {number} res The res.
- * @private
+ * Permutations are obtained by taking the Cartesian product of
+ * {@code opt_length} iterables and filtering out those with repeated
+ * elements. For example, the permutations of {@code [1,2,3]} are
+ * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.permutations
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable from which to generate permutations.
+ * @param {number=} opt_length Length of each permutation. If omitted, defaults
+ *     to the length of {@code iterable}.
+ * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing the
+ *     permutations of {@code iterable}.
+ * @template VALUE
  */
-ol.control.ZoomSlider.prototype.positionThumbForResolution_ = function(res) {
-  var amount = this.amountForResolution_(res),
-      dragger = this.dragger_,
-      thumb = goog.dom.getFirstElementChild(this.element);
+goog.iter.permutations = function(iterable, opt_length) {
+  var elements = goog.iter.toArray(iterable);
+  var length = goog.isNumber(opt_length) ? opt_length : elements.length;
 
-  if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) {
-    var left = dragger.limits.left + dragger.limits.width * amount;
-    goog.style.setPosition(thumb, left);
-  } else {
-    var top = dragger.limits.top + dragger.limits.height * amount;
-    goog.style.setPosition(thumb, dragger.limits.left, top);
-  }
+  var sets = goog.array.repeat(elements, length);
+  var product = goog.iter.product.apply(undefined, sets);
+
+  return goog.iter.filter(product, function(arr) {
+    return !goog.iter.hasDuplicates_(arr);
+  });
 };
 
 
 /**
- * Calculates the amount the thumb has been dragged to allow for calculation
- * of the corresponding resolution.
+ * Creates an iterator that returns combinations of elements from
+ * {@code iterable}.
  *
- * @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 amount the thumb has been dragged.
- * @private
+ * Combinations are obtained by taking the {@see goog.iter#permutations} of
+ * {@code iterable} and filtering those whose elements appear in the order they
+ * are encountered in {@code iterable}. For example, the 3-length combinations
+ * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.combinations
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable from which to generate combinations.
+ * @param {number} length The length of each combination.
+ * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing
+ *     combinations from the {@code iterable}.
+ * @template VALUE
  */
-ol.control.ZoomSlider.prototype.amountDragged_ = function(x, y) {
-  var draggerLimits = this.dragger_.limits,
-      amount = 0;
-  if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) {
-    amount = (x - draggerLimits.left) / draggerLimits.width;
-  } else {
-    amount = (y - draggerLimits.top) / draggerLimits.height;
+goog.iter.combinations = function(iterable, length) {
+  var elements = goog.iter.toArray(iterable);
+  var indexes = goog.iter.range(elements.length);
+  var indexIterator = goog.iter.permutations(indexes, length);
+  // sortedIndexIterator will now give arrays of with the given length that
+  // indicate what indexes into "elements" should be returned on each iteration.
+  var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
+    return goog.array.isSorted(arr);
+  });
+
+  var iter = new goog.iter.Iterator();
+
+  function getIndexFromElements(index) {
+    return elements[index];
   }
-  return amount;
-};
 
+  iter.next = function() {
+    return goog.array.map(
+        /** @type {!Array<number>} */
+        (sortedIndexIterator.next()), getIndexFromElements);
+  };
 
-/**
- * Calculates the corresponding resolution of the thumb by the amount it has
- * been dragged from its minimum.
- *
- * @param {number} amount The amount the thumb has been dragged.
- * @return {number} The corresponding resolution.
- * @private
- */
-ol.control.ZoomSlider.prototype.resolutionForAmount_ = function(amount) {
-  // FIXME do we really need this affine transform?
-  amount = (goog.math.clamp(amount, 0, 1) - 1) * -1;
-  var fn = this.getMap().getView().getResolutionForValueFunction();
-  return fn(amount);
+  return iter;
 };
 
 
 /**
- * Determines an amount of dragging relative to this minimum position by the
- * given resolution.
+ * Creates an iterator that returns combinations of elements from
+ * {@code iterable}, with repeated elements possible.
  *
- * @param {number} res The resolution to get the amount for.
- * @return {number} The corresponding value (between 0 and 1).
- * @private
+ * Combinations are obtained by taking the Cartesian product of {@code length}
+ * iterables and filtering those whose elements appear in the order they are
+ * encountered in {@code iterable}. For example, the 2-length combinations of
+ * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.combinations_with_replacement
+ * @see http://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition
+ * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
+ *     iterable to combine.
+ * @param {number} length The length of each combination.
+ * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing
+ *     combinations from the {@code iterable}.
+ * @template VALUE
  */
-ol.control.ZoomSlider.prototype.amountForResolution_ = function(res) {
-  var fn = this.getMap().getView().getValueForResolutionFunction();
-  var value = fn(res);
-  // FIXME do we really need this affine transform?
-  return (value - 1) * -1;
+goog.iter.combinationsWithReplacement = function(iterable, length) {
+  var elements = goog.iter.toArray(iterable);
+  var indexes = goog.array.range(elements.length);
+  var sets = goog.array.repeat(indexes, length);
+  var indexIterator = goog.iter.product.apply(undefined, sets);
+  // sortedIndexIterator will now give arrays of with the given length that
+  // indicate what indexes into "elements" should be returned on each iteration.
+  var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
+    return goog.array.isSorted(arr);
+  });
+
+  var iter = new goog.iter.Iterator();
+
+  function getIndexFromElements(index) {
+    return elements[index];
+  }
+
+  iter.next = function() {
+    return goog.array.map(
+        /** @type {!Array<number>} */
+        (sortedIndexIterator.next()), getIndexFromElements);
+  };
+
+  return iter;
 };
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Handles the user caused changes of the slider thumb and adjusts the
- * resolution of our map accordingly. Will be called both while dragging and
- * when dragging ends.
+ * @fileoverview Datastructure: Hash Map.
  *
- * @param {goog.fx.DragDropEvent} e The dragdropevent.
- * @private
+ * @author arv@google.com (Erik Arvidsson)
+ *
+ * This file contains an implementation of a Map structure. It implements a lot
+ * of the methods used in goog.structs so those functions work on hashes. This
+ * is best suited for complex key types. For simple keys such as numbers and
+ * strings, and where special names like __proto__ are not a concern, consider
+ * using the lighter-weight utilities in goog.object.
  */
-ol.control.ZoomSlider.prototype.handleSliderChange_ = function(e) {
-  var map = this.getMap();
-  var view = map.getView();
-  var resolution;
-  if (e.type === goog.fx.Dragger.EventType.DRAG) {
-    var amountDragged = this.amountDragged_(e.left, e.top);
-    resolution = this.resolutionForAmount_(amountDragged);
-    if (resolution !== this.currentResolution_) {
-      this.currentResolution_ = resolution;
-      view.setResolution(resolution);
-    }
-  } else {
-    goog.asserts.assert(goog.isDef(this.currentResolution_));
-    map.beforeRender(ol.animation.zoom({
-      resolution: this.currentResolution_,
-      duration: ol.ZOOMSLIDER_ANIMATION_DURATION,
-      easing: ol.easing.easeOut
-    }));
-    resolution = view.constrainResolution(this.currentResolution_);
-    view.setResolution(resolution);
-  }
-};
 
-goog.provide('ol.control.ZoomToExtent');
 
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.pointer.PointerEventHandler');
+goog.provide('goog.structs.Map');
+
+goog.require('goog.iter.Iterator');
+goog.require('goog.iter.StopIteration');
+goog.require('goog.object');
 
 
 
 /**
- * @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`.
- *
+ * Class for Hash Map datastructure.
+ * @param {*=} opt_map Map or Object to initialize the map with.
+ * @param {...*} var_args If 2 or more arguments are present then they
+ *     will be used as key-value pairs.
  * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomToExtentOptions=} opt_options Options.
- * @api stable
+ * @template K, V
  */
-ol.control.ZoomToExtent = function(opt_options) {
-  var options = goog.isDef(opt_options) ? opt_options : {};
+goog.structs.Map = function(opt_map, var_args) {
 
   /**
-   * @type {ol.Extent}
-   * @private
+   * Underlying JS object used to implement the map.
+   * @private {!Object}
    */
-  this.extent_ = goog.isDef(options.extent) ? options.extent : null;
+  this.map_ = {};
 
-  var className = goog.isDef(options.className) ? options.className :
-      'ol-zoom-extent';
+  /**
+   * An array of keys. This is necessary for two reasons:
+   *   1. Iterating the keys using for (var key in this.map_) allocates an
+   *      object for every key in IE which is really bad for IE6 GC perf.
+   *   2. Without a side data structure, we would need to escape all the keys
+   *      as that would be the only way we could tell during iteration if the
+   *      key was an internal key or a property of the object.
+   *
+   * This array can contain deleted keys so it's necessary to check the map
+   * as well to see if the key is still in the map (this doesn't require a
+   * memory allocation in IE).
+   * @private {!Array<string>}
+   */
+  this.keys_ = [];
 
-  var tipLabel = goog.isDef(options.tipLabel) ?
-      options.tipLabel : 'Fit to extent';
-  var tip = goog.dom.createDom(goog.dom.TagName.SPAN, {
-    'role' : 'tooltip'
-  }, tipLabel);
-  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
-    'class': 'ol-has-tooltip'
-  });
-  goog.dom.appendChild(button, tip);
+  /**
+   * The number of key value pairs in the map.
+   * @private {number}
+   */
+  this.count_ = 0;
 
-  var buttonHandler = new ol.pointer.PointerEventHandler(button);
-  this.registerDisposable(buttonHandler);
-  goog.events.listen(buttonHandler, ol.pointer.EventType.POINTERUP,
-      this.handlePointerUp_, false, this);
-  goog.events.listen(button, goog.events.EventType.CLICK,
-      this.handleClick_, false, this);
+  /**
+   * Version used to detect changes while iterating.
+   * @private {number}
+   */
+  this.version_ = 0;
 
-  goog.events.listen(button, [
-    goog.events.EventType.MOUSEOUT,
-    goog.events.EventType.FOCUSOUT
-  ], function() {
-    this.blur();
-  }, false);
+  var argLength = arguments.length;
 
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
+  if (argLength > 1) {
+    if (argLength % 2) {
+      throw Error('Uneven number of arguments');
+    }
+    for (var i = 0; i < argLength; i += 2) {
+      this.set(arguments[i], arguments[i + 1]);
+    }
+  } else if (opt_map) {
+    this.addAll(/** @type {Object} */ (opt_map));
+  }
+};
 
-  goog.base(this, {
-    element: element,
-    target: options.target
-  });
+
+/**
+ * @return {number} The number of key-value pairs in the map.
+ */
+goog.structs.Map.prototype.getCount = function() {
+  return this.count_;
 };
-goog.inherits(ol.control.ZoomToExtent, ol.control.Control);
 
 
 /**
- * @param {goog.events.BrowserEvent} event The event to handle
- * @private
+ * Returns the values of the map.
+ * @return {!Array<V>} The values in the map.
  */
-ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
-  if (event.screenX !== 0 && event.screenY !== 0) {
-    return;
+goog.structs.Map.prototype.getValues = function() {
+  this.cleanupKeysArray_();
+
+  var rv = [];
+  for (var i = 0; i < this.keys_.length; i++) {
+    var key = this.keys_[i];
+    rv.push(this.map_[key]);
   }
-  this.handleZoomToExtent_();
+  return rv;
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent The event to handle
- * @private
+ * Returns the keys of the map.
+ * @return {!Array<string>} Array of string values.
  */
-ol.control.ZoomToExtent.prototype.handlePointerUp_ = function(pointerEvent) {
-  pointerEvent.browserEvent.preventDefault();
-  this.handleZoomToExtent_();
+goog.structs.Map.prototype.getKeys = function() {
+  this.cleanupKeysArray_();
+  return /** @type {!Array<string>} */ (this.keys_.concat());
 };
 
 
 /**
- * @private
+ * Whether the map contains the given key.
+ * @param {*} key The key to check for.
+ * @return {boolean} Whether the map contains the key.
  */
-ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() {
-  var map = this.getMap();
-  var view = map.getView();
-  var extent = goog.isNull(this.extent_) ?
-      view.getProjection().getExtent() : this.extent_;
-  var size = map.getSize();
-  goog.asserts.assert(goog.isDef(size));
-  view.fitExtent(extent, size);
+goog.structs.Map.prototype.containsKey = function(key) {
+  return goog.structs.Map.hasKey_(this.map_, key);
 };
 
-goog.provide('ol.DeviceOrientation');
-goog.provide('ol.DeviceOrientationProperty');
 
-goog.require('goog.events');
-goog.require('goog.math');
-goog.require('ol.Object');
-goog.require('ol.has');
+/**
+ * Whether the map contains the given value. This is O(n).
+ * @param {V} val The value to check for.
+ * @return {boolean} Whether the map contains the value.
+ */
+goog.structs.Map.prototype.containsValue = function(val) {
+  for (var i = 0; i < this.keys_.length; i++) {
+    var key = this.keys_[i];
+    if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
+      return true;
+    }
+  }
+  return false;
+};
 
 
 /**
- * @enum {string}
+ * Whether this map is equal to the argument map.
+ * @param {goog.structs.Map} otherMap The map against which to test equality.
+ * @param {function(V, V): boolean=} opt_equalityFn Optional equality function
+ *     to test equality of values. If not specified, this will test whether
+ *     the values contained in each map are identical objects.
+ * @return {boolean} Whether the maps are equal.
  */
-ol.DeviceOrientationProperty = {
-  ALPHA: 'alpha',
-  BETA: 'beta',
-  GAMMA: 'gamma',
-  HEADING: 'heading',
-  TRACKING: 'tracking'
+goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
+  if (this === otherMap) {
+    return true;
+  }
+
+  if (this.count_ != otherMap.getCount()) {
+    return false;
+  }
+
+  var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
+
+  this.cleanupKeysArray_();
+  for (var key, i = 0; key = this.keys_[i]; i++) {
+    if (!equalityFn(this.get(key), otherMap.get(key))) {
+      return false;
+    }
+  }
+
+  return true;
 };
 
 
+/**
+ * Default equality test for values.
+ * @param {*} a The first value.
+ * @param {*} b The second value.
+ * @return {boolean} Whether a and b reference the same object.
+ */
+goog.structs.Map.defaultEquals = function(a, b) {
+  return a === b;
+};
+
 
 /**
- * @classdesc
- * The ol.DeviceOrientation class provides access to DeviceOrientation
- * information and 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 targetting mobile devices will be especially interested in this
- * class.
- *
- * Device orientation data are relative to a common starting point. For mobile
- * devices, the starting point is to lay your phone face up on a table with the
- * top of the phone pointing north. This represents the zero state. All
- * angles are then relative to this state. For computers, it is the same except
- * the screen is open at 90 degrees.
- *
- * Device orientation is reported as three angles - `alpha`, `beta`, and
- * `gamma` - relative to the starting position along the three planar axes X, Y
- * and Z. The X axis runs from the left edge to the right edge through the
- * middle of the device. Similarly, the Y axis runs from the bottom to the top
- * of the device through the middle. The Z axis runs from the back to the front
- * through the middle. In the starting position, the X axis points to the
- * right, the Y axis points away from you and the Z axis points straight up
- * from the device lying flat.
- *
- * The three angles representing the device orientation are relative to the
- * three axes. `alpha` indicates how much the device has been rotated around the
- * Z axis, which is commonly interpreted as the compass heading (see note
- * below). `beta` indicates how much the device has been rotated around the X
- * axis, or how much it is tilted from front to back.  `gamma` indicates how
- * much the device has been rotated around the Y axis, or how much it is tilted
- * from left to right.
- *
- * For most browsers, the `alpha` value returns the compass heading so if the
- * device points north, it will be 0.  With Safari on iOS, the 0 value of
- * `alpha` is calculated from when device orientation was first requested.
- * ol.DeviceOrientation provides the `heading` property which normalizes this
- * behavior across all browsers for you.
- *
- * It is important to note that the HTML 5 DeviceOrientation specification
- * indicates that `alpha`, `beta` and `gamma` are in degrees while the
- * equivalent properties in ol.DeviceOrientation are in radians for consistency
- * with all other uses of angles throughout OpenLayers.
- *
- * @see http://www.w3.org/TR/orientation-event/
- *
- * @constructor
- * @extends {ol.Object}
- * @fires change Triggered when the device orientation changes.
- * @param {olx.DeviceOrientationOptions=} opt_options Options.
- * @api
+ * @return {boolean} Whether the map is empty.
  */
-ol.DeviceOrientation = function(opt_options) {
-
-  goog.base(this);
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {goog.events.Key}
-   */
-  this.listenerKey_ = null;
-
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.DeviceOrientationProperty.TRACKING),
-      this.handleTrackingChanged_, false, this);
-
-  this.setTracking(goog.isDef(options.tracking) ? options.tracking : false);
-
+goog.structs.Map.prototype.isEmpty = function() {
+  return this.count_ == 0;
 };
-goog.inherits(ol.DeviceOrientation, ol.Object);
 
 
 /**
- * @inheritDoc
+ * Removes all key-value pairs from the map.
  */
-ol.DeviceOrientation.prototype.disposeInternal = function() {
-  this.setTracking(false);
-  goog.base(this, 'disposeInternal');
+goog.structs.Map.prototype.clear = function() {
+  this.map_ = {};
+  this.keys_.length = 0;
+  this.count_ = 0;
+  this.version_ = 0;
 };
 
 
 /**
- * @private
- * @param {goog.events.BrowserEvent} browserEvent Event.
+ * Removes a key-value pair based on the key. This is O(logN) amortized due to
+ * updating the keys array whenever the count becomes half the size of the keys
+ * in the keys array.
+ * @param {*} key  The key to remove.
+ * @return {boolean} Whether object was removed.
  */
-ol.DeviceOrientation.prototype.orientationChange_ = function(browserEvent) {
-  var event = /** @type {DeviceOrientationEvent} */
-      (browserEvent.getBrowserEvent());
-  if (goog.isDefAndNotNull(event.alpha)) {
-    var alpha = goog.math.toRadians(event.alpha);
-    this.set(ol.DeviceOrientationProperty.ALPHA, alpha);
-    // event.absolute is undefined in iOS.
-    if (goog.isBoolean(event.absolute) && event.absolute) {
-      this.set(ol.DeviceOrientationProperty.HEADING, alpha);
-    } else if (goog.isDefAndNotNull(event.webkitCompassHeading) &&
-               goog.isDefAndNotNull(event.webkitCompassAccuracy) &&
-               event.webkitCompassAccuracy != -1) {
-      var heading = goog.math.toRadians(event.webkitCompassHeading);
-      this.set(ol.DeviceOrientationProperty.HEADING, heading);
-    }
-  }
-  if (goog.isDefAndNotNull(event.beta)) {
-    this.set(ol.DeviceOrientationProperty.BETA,
-        goog.math.toRadians(event.beta));
-  }
-  if (goog.isDefAndNotNull(event.gamma)) {
-    this.set(ol.DeviceOrientationProperty.GAMMA,
-        goog.math.toRadians(event.gamma));
-  }
-  this.dispatchChangeEvent();
-};
+goog.structs.Map.prototype.remove = function(key) {
+  if (goog.structs.Map.hasKey_(this.map_, key)) {
+    delete this.map_[key];
+    this.count_--;
+    this.version_++;
 
+    // clean up the keys array if the threshhold is hit
+    if (this.keys_.length > 2 * this.count_) {
+      this.cleanupKeysArray_();
+    }
 
-/**
- * @return {number|undefined} The euler angle in radians of the device from the
- *     standard Z axis.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.getAlpha = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.ALPHA));
+    return true;
+  }
+  return false;
 };
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getAlpha',
-    ol.DeviceOrientation.prototype.getAlpha);
 
 
 /**
- * @return {number|undefined} The euler angle in radians of the device from the
- *     planar X axis.
- * @observable
- * @api
+ * Cleans up the temp keys array by removing entries that are no longer in the
+ * map.
+ * @private
  */
-ol.DeviceOrientation.prototype.getBeta = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.BETA));
-};
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getBeta',
-    ol.DeviceOrientation.prototype.getBeta);
-
+goog.structs.Map.prototype.cleanupKeysArray_ = function() {
+  if (this.count_ != this.keys_.length) {
+    // First remove keys that are no longer in the map.
+    var srcIndex = 0;
+    var destIndex = 0;
+    while (srcIndex < this.keys_.length) {
+      var key = this.keys_[srcIndex];
+      if (goog.structs.Map.hasKey_(this.map_, key)) {
+        this.keys_[destIndex++] = key;
+      }
+      srcIndex++;
+    }
+    this.keys_.length = destIndex;
+  }
 
-/**
- * @return {number|undefined} The euler angle in radians of the device from the
- *     planar Y axis.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.getGamma = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.GAMMA));
+  if (this.count_ != this.keys_.length) {
+    // If the count still isn't correct, that means we have duplicates. This can
+    // happen when the same key is added and removed multiple times. Now we have
+    // to allocate one extra Object to remove the duplicates. This could have
+    // been done in the first pass, but in the common case, we can avoid
+    // allocating an extra object by only doing this when necessary.
+    var seen = {};
+    var srcIndex = 0;
+    var destIndex = 0;
+    while (srcIndex < this.keys_.length) {
+      var key = this.keys_[srcIndex];
+      if (!(goog.structs.Map.hasKey_(seen, key))) {
+        this.keys_[destIndex++] = key;
+        seen[key] = 1;
+      }
+      srcIndex++;
+    }
+    this.keys_.length = destIndex;
+  }
 };
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getGamma',
-    ol.DeviceOrientation.prototype.getGamma);
 
 
 /**
- * @return {number|undefined} The heading of the device relative to north, in
- *     radians, normalizing for different browser behavior.
- * @observable
- * @api
+ * Returns the value for the given key.  If the key is not found and the default
+ * value is not given this will return {@code undefined}.
+ * @param {*} key The key to get the value for.
+ * @param {DEFAULT=} opt_val The value to return if no item is found for the
+ *     given key, defaults to undefined.
+ * @return {V|DEFAULT} The value for the given key.
+ * @template DEFAULT
  */
-ol.DeviceOrientation.prototype.getHeading = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientationProperty.HEADING));
+goog.structs.Map.prototype.get = function(key, opt_val) {
+  if (goog.structs.Map.hasKey_(this.map_, key)) {
+    return this.map_[key];
+  }
+  return opt_val;
 };
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getHeading',
-    ol.DeviceOrientation.prototype.getHeading);
 
 
 /**
- * Are we tracking the device's orientation?
- * @return {boolean} The status of tracking changes to alpha, beta and gamma.
- *     If true, changes are tracked and reported immediately.
- * @observable
- * @api
+ * Adds a key-value pair to the map.
+ * @param {*} key The key.
+ * @param {V} value The value to add.
+ * @return {*} Some subclasses return a value.
  */
-ol.DeviceOrientation.prototype.getTracking = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.DeviceOrientationProperty.TRACKING));
+goog.structs.Map.prototype.set = function(key, value) {
+  if (!(goog.structs.Map.hasKey_(this.map_, key))) {
+    this.count_++;
+    this.keys_.push(key);
+    // Only change the version if we add a new key.
+    this.version_++;
+  }
+  this.map_[key] = value;
 };
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getTracking',
-    ol.DeviceOrientation.prototype.getTracking);
 
 
 /**
- * @private
+ * Adds multiple key-value pairs from another goog.structs.Map or Object.
+ * @param {Object} map  Object containing the data to add.
  */
-ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() {
-  if (ol.has.DEVICE_ORIENTATION) {
-    var tracking = this.getTracking();
-    if (tracking && goog.isNull(this.listenerKey_)) {
-      this.listenerKey_ = goog.events.listen(goog.global, 'deviceorientation',
-          this.orientationChange_, false, this);
-    } else if (!tracking && !goog.isNull(this.listenerKey_)) {
-      goog.events.unlistenByKey(this.listenerKey_);
-      this.listenerKey_ = null;
-    }
+goog.structs.Map.prototype.addAll = function(map) {
+  var keys, values;
+  if (map instanceof goog.structs.Map) {
+    keys = map.getKeys();
+    values = map.getValues();
+  } else {
+    keys = goog.object.getKeys(map);
+    values = goog.object.getValues(map);
+  }
+  // we could use goog.array.forEach here but I don't want to introduce that
+  // dependency just for this.
+  for (var i = 0; i < keys.length; i++) {
+    this.set(keys[i], values[i]);
   }
 };
 
 
 /**
- * Enable or disable tracking of DeviceOrientation events.
- * @param {boolean} tracking The status of tracking changes to alpha, beta and
- *     gamma. If true, changes are tracked and reported immediately.
- * @observable
- * @api
+ * Calls the given function on each entry in the map.
+ * @param {function(this:T, V, K, goog.structs.Map<K,V>)} f
+ * @param {T=} opt_obj The value of "this" inside f.
+ * @template T
  */
-ol.DeviceOrientation.prototype.setTracking = function(tracking) {
-  this.set(ol.DeviceOrientationProperty.TRACKING, tracking);
+goog.structs.Map.prototype.forEach = function(f, opt_obj) {
+  var keys = this.getKeys();
+  for (var i = 0; i < keys.length; i++) {
+    var key = keys[i];
+    var value = this.get(key);
+    f.call(opt_obj, value, key, this);
+  }
 };
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'setTracking',
-    ol.DeviceOrientation.prototype.setTracking);
-
-goog.provide('ol.dom.Input');
-goog.provide('ol.dom.InputProperty');
-
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol.Object');
 
 
 /**
- * @enum {string}
+ * Clones a map and returns a new map.
+ * @return {!goog.structs.Map} A new map with the same key-value pairs.
  */
-ol.dom.InputProperty = {
-  VALUE: 'value',
-  CHECKED: 'checked'
+goog.structs.Map.prototype.clone = function() {
+  return new goog.structs.Map(this);
 };
 
 
-
 /**
- * @classdesc
- * Helper class for binding HTML input to an {@link ol.Object}.
- *
- * Example:
+ * Returns a new map in which all the keys and values are interchanged
+ * (keys become values and values become keys). If multiple keys map to the
+ * same value, the chosen transposed value is implementation-dependent.
  *
- *     // bind a checkbox with id 'visible' to a layer's visibility
- *     var visible = new ol.dom.Input(document.getElementById('visible'));
- *     visible.bindTo('checked', layer, 'visible');
+ * It acts very similarly to {goog.object.transpose(Object)}.
  *
- * @constructor
- * @extends {ol.Object}
- * @param {Element} target Target element.
- * @api
+ * @return {!goog.structs.Map} The transposed map.
  */
-ol.dom.Input = function(target) {
-  goog.base(this);
-
-  /**
-   * @private
-   * @type {HTMLInputElement}
-   */
-  this.target_ = /** @type {HTMLInputElement} */ (target);
-
-  goog.events.listen(this.target_,
-      [goog.events.EventType.CHANGE, goog.events.EventType.INPUT],
-      this.handleInputChanged_, false, this);
+goog.structs.Map.prototype.transpose = function() {
+  var transposed = new goog.structs.Map();
+  for (var i = 0; i < this.keys_.length; i++) {
+    var key = this.keys_[i];
+    var value = this.map_[key];
+    transposed.set(value, key);
+  }
 
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.dom.InputProperty.VALUE),
-      this.handleValueChanged_, false, this);
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.dom.InputProperty.CHECKED),
-      this.handleCheckedChanged_, false, this);
+  return transposed;
 };
-goog.inherits(ol.dom.Input, ol.Object);
 
 
 /**
- * If the input is a checkbox, return whether or not the checkbox is checked.
- * @return {boolean|undefined} The checked state of the Input.
- * @observable
- * @api
+ * @return {!Object} Object representation of the map.
  */
-ol.dom.Input.prototype.getChecked = function() {
-  return /** @type {boolean} */ (this.get(ol.dom.InputProperty.CHECKED));
+goog.structs.Map.prototype.toObject = function() {
+  this.cleanupKeysArray_();
+  var obj = {};
+  for (var i = 0; i < this.keys_.length; i++) {
+    var key = this.keys_[i];
+    obj[key] = this.map_[key];
+  }
+  return obj;
 };
-goog.exportProperty(
-    ol.dom.Input.prototype,
-    'getChecked',
-    ol.dom.Input.prototype.getChecked);
 
 
 /**
- * Get the value of the input.
- * @return {string|undefined} The value of the Input.
- * @observable
- * @api
+ * Returns an iterator that iterates over the keys in the map.  Removal of keys
+ * while iterating might have undesired side effects.
+ * @return {!goog.iter.Iterator} An iterator over the keys in the map.
  */
-ol.dom.Input.prototype.getValue = function() {
-  return /** @type {string} */ (this.get(ol.dom.InputProperty.VALUE));
+goog.structs.Map.prototype.getKeyIterator = function() {
+  return this.__iterator__(true);
 };
-goog.exportProperty(
-    ol.dom.Input.prototype,
-    'getValue',
-    ol.dom.Input.prototype.getValue);
 
 
 /**
- * Sets the value of the input.
- * @param {string} value The value of the Input.
- * @observable
- * @api
+ * Returns an iterator that iterates over the values in the map.  Removal of
+ * keys while iterating might have undesired side effects.
+ * @return {!goog.iter.Iterator} An iterator over the values in the map.
  */
-ol.dom.Input.prototype.setValue = function(value) {
-  this.set(ol.dom.InputProperty.VALUE, value);
+goog.structs.Map.prototype.getValueIterator = function() {
+  return this.__iterator__(false);
 };
-goog.exportProperty(
-    ol.dom.Input.prototype,
-    'setValue',
-    ol.dom.Input.prototype.setValue);
 
 
 /**
- * Set whether or not a checkbox is checked.
- * @param {boolean} checked The checked state of the Input.
- * @observable
- * @api
+ * Returns an iterator that iterates over the values or the keys in the map.
+ * This throws an exception if the map was mutated since the iterator was
+ * created.
+ * @param {boolean=} opt_keys True to iterate over the keys. False to iterate
+ *     over the values.  The default value is false.
+ * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
  */
-ol.dom.Input.prototype.setChecked = function(checked) {
-  this.set(ol.dom.InputProperty.CHECKED, checked);
-};
-goog.exportProperty(
-    ol.dom.Input.prototype,
-    'setChecked',
-    ol.dom.Input.prototype.setChecked);
+goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
+  // Clean up keys to minimize the risk of iterating over dead keys.
+  this.cleanupKeysArray_();
 
+  var i = 0;
+  var keys = this.keys_;
+  var map = this.map_;
+  var version = this.version_;
+  var selfObj = this;
 
-/**
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @private
- */
-ol.dom.Input.prototype.handleInputChanged_ = function(browserEvent) {
-  goog.asserts.assert(browserEvent.currentTarget == this.target_);
-  var target = this.target_;
-  if (target.type === 'checkbox' || target.type === 'radio') {
-    this.setChecked(target.checked);
-  } else {
-    this.setValue(target.value);
-  }
+  var newIter = new goog.iter.Iterator;
+  newIter.next = function() {
+    while (true) {
+      if (version != selfObj.version_) {
+        throw Error('The map has changed since the iterator was created');
+      }
+      if (i >= keys.length) {
+        throw goog.iter.StopIteration;
+      }
+      var key = keys[i++];
+      return opt_keys ? key : map[key];
+    }
+  };
+  return newIter;
 };
 
 
 /**
- * @param {goog.events.Event} event Change event.
+ * Safe way to test for hasOwnProperty.  It even allows testing for
+ * 'hasOwnProperty'.
+ * @param {Object} obj The object to test for presence of the given key.
+ * @param {*} key The key to check for.
+ * @return {boolean} Whether the object has the key.
  * @private
  */
-ol.dom.Input.prototype.handleCheckedChanged_ = function(event) {
-  this.target_.checked = /** @type {boolean} */ (this.getChecked());
+goog.structs.Map.hasKey_ = function(obj, key) {
+  return Object.prototype.hasOwnProperty.call(obj, key);
 };
 
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @param {goog.events.Event} event Change event.
- * @private
+ * @fileoverview Simple utilities for dealing with URI strings.
+ *
+ * This is intended to be a lightweight alternative to constructing goog.Uri
+ * objects.  Whereas goog.Uri adds several kilobytes to the binary regardless
+ * of how much of its functionality you use, this is designed to be a set of
+ * mostly-independent utilities so that the compiler includes only what is
+ * necessary for the task.  Estimated savings of porting is 5k pre-gzip and
+ * 1.5k post-gzip.  To ensure the savings remain, future developers should
+ * avoid adding new functionality to existing functions, but instead create
+ * new ones and factor out shared code.
+ *
+ * Many of these utilities have limited functionality, tailored to common
+ * cases.  The query parameter utilities assume that the parameter keys are
+ * already encoded, since most keys are compile-time alphanumeric strings.  The
+ * query parameter mutation utilities also do not tolerate fragment identifiers.
+ *
+ * By design, these functions can be slower than goog.Uri equivalents.
+ * Repeated calls to some of functions may be quadratic in behavior for IE,
+ * although the effect is somewhat limited given the 2kb limit.
+ *
+ * One advantage of the limited functionality here is that this approach is
+ * less sensitive to differences in URI encodings than goog.Uri, since these
+ * functions modify the strings in place, rather than decoding and
+ * re-encoding.
+ *
+ * Uses features of RFC 3986 for parsing/formatting URIs:
+ *   http://www.ietf.org/rfc/rfc3986.txt
+ *
+ * @author gboyer@google.com (Garrett Boyer) - The "lightened" design.
  */
-ol.dom.Input.prototype.handleValueChanged_ = function(event) {
-  this.target_.value =  /** @type {string} */ (this.getValue());
-};
 
-goog.provide('ol.Ellipsoid');
-
-goog.require('goog.math');
-goog.require('ol.Coordinate');
+goog.provide('goog.uri.utils');
+goog.provide('goog.uri.utils.ComponentIndex');
+goog.provide('goog.uri.utils.QueryArray');
+goog.provide('goog.uri.utils.QueryValue');
+goog.provide('goog.uri.utils.StandardQueryParam');
 
+goog.require('goog.asserts');
+goog.require('goog.string');
+goog.require('goog.userAgent');
 
 
 /**
- * @constructor
- * @param {number} a Major radius.
- * @param {number} flattening Flattening.
+ * Character codes inlined to avoid object allocations due to charCode.
+ * @enum {number}
+ * @private
  */
-ol.Ellipsoid = function(a, flattening) {
-
-  /**
-   * @const
-   * @type {number}
-   */
-  this.a = a;
+goog.uri.utils.CharCode_ = {
+  AMPERSAND: 38,
+  EQUAL: 61,
+  HASH: 35,
+  QUESTION: 63
+};
 
-  /**
-   * @const
-   * @type {number}
-   */
-  this.flattening = flattening;
 
-  /**
-   * @const
-   * @type {number}
-   */
-  this.b = this.a * (1 - this.flattening);
+/**
+ * Builds a URI string from already-encoded parts.
+ *
+ * No encoding is performed.  Any component may be omitted as either null or
+ * undefined.
+ *
+ * @param {?string=} opt_scheme The scheme such as 'http'.
+ * @param {?string=} opt_userInfo The user name before the '@'.
+ * @param {?string=} opt_domain The domain such as 'www.google.com', already
+ *     URI-encoded.
+ * @param {(string|number|null)=} opt_port The port number.
+ * @param {?string=} opt_path The path, already URI-encoded.  If it is not
+ *     empty, it must begin with a slash.
+ * @param {?string=} opt_queryData The URI-encoded query data.
+ * @param {?string=} opt_fragment The URI-encoded fragment identifier.
+ * @return {string} The fully combined URI.
+ */
+goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo,
+    opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) {
+  var out = '';
 
-  /**
-   * @const
-   * @type {number}
-   */
-  this.eSquared = 2 * flattening - flattening * flattening;
+  if (opt_scheme) {
+    out += opt_scheme + ':';
+  }
 
-  /**
-   * @const
-   * @type {number}
-   */
-  this.e = Math.sqrt(this.eSquared);
+  if (opt_domain) {
+    out += '//';
 
-};
+    if (opt_userInfo) {
+      out += opt_userInfo + '@';
+    }
 
+    out += opt_domain;
 
-/**
- * @param {ol.Coordinate} c1 Coordinate 1.
- * @param {ol.Coordinate} c2 Coordinate 1.
- * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
- * @param {number=} opt_maxIterations Maximum iterations.
- * @return {{distance: number, initialBearing: number, finalBearing: number}}
- *     Vincenty.
- */
-ol.Ellipsoid.prototype.vincenty =
-    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
-  var minDeltaLambda = goog.isDef(opt_minDeltaLambda) ?
-      opt_minDeltaLambda : 1e-12;
-  var maxIterations = goog.isDef(opt_maxIterations) ?
-      opt_maxIterations : 100;
-  var f = this.flattening;
-  var lat1 = goog.math.toRadians(c1[1]);
-  var lat2 = goog.math.toRadians(c2[1]);
-  var deltaLon = goog.math.toRadians(c2[0] - c1[0]);
-  var U1 = Math.atan((1 - f) * Math.tan(lat1));
-  var cosU1 = Math.cos(U1);
-  var sinU1 = Math.sin(U1);
-  var U2 = Math.atan((1 - f) * Math.tan(lat2));
-  var cosU2 = Math.cos(U2);
-  var sinU2 = Math.sin(U2);
-  var lambda = deltaLon;
-  var cosSquaredAlpha, sinAlpha;
-  var cosLambda, deltaLambda = Infinity, sinLambda;
-  var cos2SigmaM, cosSigma, sigma, sinSigma;
-  var i;
-  for (i = maxIterations; i > 0; --i) {
-    cosLambda = Math.cos(lambda);
-    sinLambda = Math.sin(lambda);
-    var x = cosU2 * sinLambda;
-    var y = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda;
-    sinSigma = Math.sqrt(x * x + y * y);
-    if (sinSigma === 0) {
-      return {
-        distance: 0,
-        initialBearing: 0,
-        finalBearing: 0
-      };
-    }
-    cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
-    sigma = Math.atan2(sinSigma, cosSigma);
-    sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
-    cosSquaredAlpha = 1 - sinAlpha * sinAlpha;
-    cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSquaredAlpha;
-    if (isNaN(cos2SigmaM)) {
-      cos2SigmaM = 0;
-    }
-    var C = f / 16 * cosSquaredAlpha * (4 + f * (4 - 3 * cosSquaredAlpha));
-    var lambdaPrime = deltaLon + (1 - C) * f * sinAlpha * (sigma +
-        C * sinSigma * (cos2SigmaM +
-        C * cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1)));
-    deltaLambda = Math.abs(lambdaPrime - lambda);
-    lambda = lambdaPrime;
-    if (deltaLambda < minDeltaLambda) {
-      break;
+    if (opt_port) {
+      out += ':' + opt_port;
     }
   }
-  if (i === 0) {
-    return {
-      distance: NaN,
-      finalBearing: NaN,
-      initialBearing: NaN
-    };
+
+  if (opt_path) {
+    out += opt_path;
   }
-  var aSquared = this.a * this.a;
-  var bSquared = this.b * this.b;
-  var uSquared = cosSquaredAlpha * (aSquared - bSquared) / bSquared;
-  var A = 1 + uSquared / 16384 *
-      (4096 + uSquared * (uSquared * (320 - 175 * uSquared) - 768));
-  var B = uSquared / 1024 *
-      (256 + uSquared * (uSquared * (74 - 47 * uSquared) - 128));
-  var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 *
-      (cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1) -
-       B / 6 * cos2SigmaM * (4 * sinSigma * sinSigma - 3) *
-       (4 * cos2SigmaM * cos2SigmaM - 3)));
-  cosLambda = Math.cos(lambda);
-  sinLambda = Math.sin(lambda);
-  var alpha1 = Math.atan2(cosU2 * sinLambda,
-                          cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
-  var alpha2 = Math.atan2(cosU1 * sinLambda,
-                          cosU1 * sinU2 * cosLambda - sinU1 * cosU2);
-  return {
-    distance: this.b * A * (sigma - deltaSigma),
-    initialBearing: goog.math.toDegrees(alpha1),
-    finalBearing: goog.math.toDegrees(alpha2)
-  };
+
+  if (opt_queryData) {
+    out += '?' + opt_queryData;
+  }
+
+  if (opt_fragment) {
+    out += '#' + opt_fragment;
+  }
+
+  return out;
 };
 
 
 /**
- * Returns the distance from c1 to c2 using Vincenty.
+ * A regular expression for breaking a URI into its component parts.
  *
- * @param {ol.Coordinate} c1 Coordinate 1.
- * @param {ol.Coordinate} c2 Coordinate 1.
- * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
- * @param {number=} opt_maxIterations Maximum iterations.
- * @return {number} Vincenty distance.
+ * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B
+ * As the "first-match-wins" algorithm is identical to the "greedy"
+ * disambiguation method used by POSIX regular expressions, it is natural and
+ * commonplace to use a regular expression for parsing the potential five
+ * components of a URI reference.
+ *
+ * The following line is the regular expression for breaking-down a
+ * well-formed URI reference into its components.
+ *
+ * <pre>
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ *  12            3  4          5       6  7        8 9
+ * </pre>
+ *
+ * The numbers in the second line above are only to assist readability; they
+ * indicate the reference points for each subexpression (i.e., each paired
+ * parenthesis). We refer to the value matched for subexpression <n> as $<n>.
+ * For example, matching the above expression to
+ * <pre>
+ *     http://www.ics.uci.edu/pub/ietf/uri/#Related
+ * </pre>
+ * results in the following subexpression matches:
+ * <pre>
+ *    $1 = http:
+ *    $2 = http
+ *    $3 = //www.ics.uci.edu
+ *    $4 = www.ics.uci.edu
+ *    $5 = /pub/ietf/uri/
+ *    $6 = <undefined>
+ *    $7 = <undefined>
+ *    $8 = #Related
+ *    $9 = Related
+ * </pre>
+ * where <undefined> indicates that the component is not present, as is the
+ * case for the query component in the above example. Therefore, we can
+ * determine the value of the five components as
+ * <pre>
+ *    scheme    = $2
+ *    authority = $4
+ *    path      = $5
+ *    query     = $7
+ *    fragment  = $9
+ * </pre>
+ *
+ * The regular expression has been modified slightly to expose the
+ * userInfo, domain, and port separately from the authority.
+ * The modified version yields
+ * <pre>
+ *    $1 = http              scheme
+ *    $2 = <undefined>       userInfo -\
+ *    $3 = www.ics.uci.edu   domain     | authority
+ *    $4 = <undefined>       port     -/
+ *    $5 = /pub/ietf/uri/    path
+ *    $6 = <undefined>       query without ?
+ *    $7 = Related           fragment without #
+ * </pre>
+ * @type {!RegExp}
+ * @private
  */
-ol.Ellipsoid.prototype.vincentyDistance =
-    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
-  var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations);
-  return vincenty.distance;
-};
+goog.uri.utils.splitRe_ = new RegExp(
+    '^' +
+    '(?:' +
+        '([^:/?#.]+)' +                  // scheme - ignore special characters
+                                         // used by other URL parts such as :,
+                                         // ?, /, #, and .
+    ':)?' +
+    '(?://' +
+        '(?:([^/?#]*)@)?' +              // userInfo
+        '([^/#?]*?)' +                   // domain
+        '(?::([0-9]+))?' +               // port
+        '(?=[/#?]|$)' +                  // authority-terminating character
+    ')?' +
+    '([^?#]+)?' +                        // path
+    '(?:\\?([^#]*))?' +                  // query
+    '(?:#(.*))?' +                       // fragment
+    '$');
 
 
 /**
- * Returns the final bearing from c1 to c2 using Vincenty.
- *
- * @param {ol.Coordinate} c1 Coordinate 1.
- * @param {ol.Coordinate} c2 Coordinate 1.
- * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
- * @param {number=} opt_maxIterations Maximum iterations.
- * @return {number} Initial bearing.
+ * The index of each URI component in the return value of goog.uri.utils.split.
+ * @enum {number}
  */
-ol.Ellipsoid.prototype.vincentyFinalBearing =
-    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
-  var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations);
-  return vincenty.finalBearing;
+goog.uri.utils.ComponentIndex = {
+  SCHEME: 1,
+  USER_INFO: 2,
+  DOMAIN: 3,
+  PORT: 4,
+  PATH: 5,
+  QUERY_DATA: 6,
+  FRAGMENT: 7
 };
 
 
 /**
- * Returns the initial bearing from c1 to c2 using Vincenty.
+ * Splits a URI into its component parts.
  *
- * @param {ol.Coordinate} c1 Coordinate 1.
- * @param {ol.Coordinate} c2 Coordinate 1.
- * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
- * @param {number=} opt_maxIterations Maximum iterations.
- * @return {number} Initial bearing.
+ * Each component can be accessed via the component indices; for example:
+ * <pre>
+ * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
+ * </pre>
+ *
+ * @param {string} uri The URI string to examine.
+ * @return {!Array<string|undefined>} Each component still URI-encoded.
+ *     Each component that is present will contain the encoded value, whereas
+ *     components that are not present will be undefined or empty, depending
+ *     on the browser's regular expression implementation.  Never null, since
+ *     arbitrary strings may still look like path names.
  */
-ol.Ellipsoid.prototype.vincentyInitialBearing =
-    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
-  var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations);
-  return vincenty.initialBearing;
-};
-
-goog.provide('ol.ellipsoid.WGS84');
+goog.uri.utils.split = function(uri) {
+  goog.uri.utils.phishingProtection_();
 
-goog.require('ol.Ellipsoid');
+  // See @return comment -- never null.
+  return /** @type {!Array<string|undefined>} */ (
+      uri.match(goog.uri.utils.splitRe_));
+};
 
 
 /**
- * @const
- * @type {ol.Ellipsoid}
+ * Safari has a nasty bug where if you have an http URL with a username, e.g.,
+ * http://evil.com%2F@google.com/
+ * Safari will report that window.location.href is
+ * http://evil.com/google.com/
+ * so that anyone who tries to parse the domain of that URL will get
+ * the wrong domain. We've seen exploits where people use this to trick
+ * Safari into loading resources from evil domains.
+ *
+ * To work around this, we run a little "Safari phishing check", and throw
+ * an exception if we see this happening.
+ *
+ * There is no convenient place to put this check. We apply it to
+ * anyone doing URI parsing on Webkit. We're not happy about this, but
+ * it fixes the problem.
+ *
+ * This should be removed once Safari fixes their bug.
+ *
+ * Exploit reported by Masato Kinugawa.
+ *
+ * @type {boolean}
+ * @private
  */
-ol.ellipsoid.WGS84 = new ol.Ellipsoid(6378137, 1 / 298.257223563);
+goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT;
 
-goog.provide('ol.MapBrowserEvent');
-goog.provide('ol.MapBrowserEvent.EventType');
-goog.provide('ol.MapBrowserEventHandler');
-goog.provide('ol.MapBrowserPointerEvent');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol');
-goog.require('ol.Coordinate');
-goog.require('ol.MapEvent');
-goog.require('ol.Pixel');
-goog.require('ol.pointer.PointerEvent');
-goog.require('ol.pointer.PointerEventHandler');
+/**
+ * Check to see if the user is being phished.
+ * @private
+ */
+goog.uri.utils.phishingProtection_ = function() {
+  if (goog.uri.utils.needsPhishingProtection_) {
+    // Turn protection off, so that we don't recurse.
+    goog.uri.utils.needsPhishingProtection_ = false;
 
+    // Use quoted access, just in case the user isn't using location externs.
+    var location = goog.global['location'];
+    if (location) {
+      var href = location['href'];
+      if (href) {
+        var domain = goog.uri.utils.getDomain(href);
+        if (domain && domain != location['hostname']) {
+          // Phishing attack
+          goog.uri.utils.needsPhishingProtection_ = true;
+          throw Error();
+        }
+      }
+    }
+  }
+};
 
 
 /**
- * @classdesc
- * Events emitted as map browser events are instances of this type.
- * See {@link ol.Map} for which events trigger a map browser event.
- *
- * @constructor
- * @extends {ol.MapEvent}
- * @implements {oli.MapBrowserEvent}
- * @param {string} type Event type.
- * @param {ol.Map} map Map.
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @param {?olx.FrameState=} opt_frameState Frame state.
+ * @param {?string} uri A possibly null string.
+ * @param {boolean=} opt_preserveReserved If true, percent-encoding of RFC-3986
+ *     reserved characters will not be removed.
+ * @return {?string} The string URI-decoded, or null if uri is null.
+ * @private
  */
-ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) {
-
-  goog.base(this, type, map, opt_frameState);
+goog.uri.utils.decodeIfPossible_ = function(uri, opt_preserveReserved) {
+  if (!uri) {
+    return uri;
+  }
 
-  /**
-   * @const
-   * @type {goog.events.BrowserEvent}
-   */
-  this.browserEvent = browserEvent;
+  return opt_preserveReserved ? decodeURI(uri) : decodeURIComponent(uri);
+};
 
-  /**
-   * @const
-   * @type {Event}
-   * @api stable
-   */
-  this.originalEvent = browserEvent.getBrowserEvent();
 
-  /**
-   * @type {ol.Pixel}
-   * @api stable
-   */
-  this.pixel = map.getEventPixel(this.originalEvent);
+/**
+ * Gets a URI component by index.
+ *
+ * It is preferred to use the getPathEncoded() variety of functions ahead,
+ * since they are more readable.
+ *
+ * @param {goog.uri.utils.ComponentIndex} componentIndex The component index.
+ * @param {string} uri The URI to examine.
+ * @return {?string} The still-encoded component, or null if the component
+ *     is not present.
+ * @private
+ */
+goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) {
+  // Convert undefined, null, and empty string into null.
+  return goog.uri.utils.split(uri)[componentIndex] || null;
+};
 
-  /**
-   * @type {ol.Coordinate}
-   * @api stable
-   */
-  this.coordinate = map.getCoordinateFromPixel(this.pixel);
 
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The protocol or scheme, or null if none.  Does not
+ *     include trailing colons or slashes.
+ */
+goog.uri.utils.getScheme = function(uri) {
+  return goog.uri.utils.getComponentByIndex_(
+      goog.uri.utils.ComponentIndex.SCHEME, uri);
 };
-goog.inherits(ol.MapBrowserEvent, ol.MapEvent);
 
 
 /**
- * Prevents the default browser action.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
- * @override
- * @api stable
+ * Gets the effective scheme for the URL.  If the URL is relative then the
+ * scheme is derived from the page's location.
+ * @param {string} uri The URI to examine.
+ * @return {string} The protocol or scheme, always lower case.
  */
-ol.MapBrowserEvent.prototype.preventDefault = function() {
-  goog.base(this, 'preventDefault');
-  this.browserEvent.preventDefault();
+goog.uri.utils.getEffectiveScheme = function(uri) {
+  var scheme = goog.uri.utils.getScheme(uri);
+  if (!scheme && self.location) {
+    var protocol = self.location.protocol;
+    scheme = protocol.substr(0, protocol.length - 1);
+  }
+  // NOTE: When called from a web worker in Firefox 3.5, location maybe null.
+  // All other browsers with web workers support self.location from the worker.
+  return scheme ? scheme.toLowerCase() : '';
 };
 
 
 /**
- * Prevents further propagation of the current event.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
- * @override
- * @api stable
+ * @param {string} uri The URI to examine.
+ * @return {?string} The user name still encoded, or null if none.
  */
-ol.MapBrowserEvent.prototype.stopPropagation = function() {
-  goog.base(this, 'stopPropagation');
-  this.browserEvent.stopPropagation();
+goog.uri.utils.getUserInfoEncoded = function(uri) {
+  return goog.uri.utils.getComponentByIndex_(
+      goog.uri.utils.ComponentIndex.USER_INFO, uri);
 };
 
 
-
 /**
- * @constructor
- * @extends {ol.MapBrowserEvent}
- * @param {string} type Event type.
- * @param {ol.Map} map Map.
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @param {?olx.FrameState=} opt_frameState Frame state.
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded user info, or null if none.
  */
-ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_frameState) {
-
-  goog.base(this, type, map, pointerEvent.browserEvent, opt_frameState);
+goog.uri.utils.getUserInfo = function(uri) {
+  return goog.uri.utils.decodeIfPossible_(
+      goog.uri.utils.getUserInfoEncoded(uri));
+};
 
-  /**
-   * @const
-   * @type {ol.pointer.PointerEvent}
-   */
-  this.pointerEvent = pointerEvent;
 
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The domain name still encoded, or null if none.
+ */
+goog.uri.utils.getDomainEncoded = function(uri) {
+  return goog.uri.utils.getComponentByIndex_(
+      goog.uri.utils.ComponentIndex.DOMAIN, uri);
 };
-goog.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);
-
 
 
 /**
- * @param {ol.Map} map The map with the viewport to listen to events on.
- * @constructor
- * @extends {goog.events.EventTarget}
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded domain, or null if none.
  */
-ol.MapBrowserEventHandler = function(map) {
+goog.uri.utils.getDomain = function(uri) {
+  return goog.uri.utils.decodeIfPossible_(
+      goog.uri.utils.getDomainEncoded(uri), true /* opt_preserveReserved */);
+};
 
-  goog.base(this);
 
-  /**
-   * This is the element that we will listen to the real events on.
-   * @type {ol.Map}
-   * @private
-   */
-  this.map_ = map;
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?number} The port number, or null if none.
+ */
+goog.uri.utils.getPort = function(uri) {
+  // Coerce to a number.  If the result of getComponentByIndex_ is null or
+  // non-numeric, the number coersion yields NaN.  This will then return
+  // null for all non-numeric cases (though also zero, which isn't a relevant
+  // port number).
+  return Number(goog.uri.utils.getComponentByIndex_(
+      goog.uri.utils.ComponentIndex.PORT, uri)) || null;
+};
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.clickTimeoutId_ = 0;
 
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.dragged_ = false;
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The path still encoded, or null if none. Includes the
+ *     leading slash, if any.
+ */
+goog.uri.utils.getPathEncoded = function(uri) {
+  return goog.uri.utils.getComponentByIndex_(
+      goog.uri.utils.ComponentIndex.PATH, uri);
+};
 
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.dragListenerKeys_ = null;
 
-  /**
-   * @type {goog.events.Key}
-   * @private
-   */
-  this.pointerdownListenerKey_ = null;
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded path, or null if none.  Includes the leading
+ *     slash, if any.
+ */
+goog.uri.utils.getPath = function(uri) {
+  return goog.uri.utils.decodeIfPossible_(
+      goog.uri.utils.getPathEncoded(uri), true /* opt_preserveReserved */);
+};
 
-  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-    /**
-     * @type {goog.events.Key}
-     * @private
-     */
-    this.ieDblclickListenerKey_ = null;
-  }
 
-  /**
-   * @type {ol.pointer.PointerEvent}
-   * @private
-   */
-  this.down_ = null;
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The query data still encoded, or null if none.  Does not
+ *     include the question mark itself.
+ */
+goog.uri.utils.getQueryData = function(uri) {
+  return goog.uri.utils.getComponentByIndex_(
+      goog.uri.utils.ComponentIndex.QUERY_DATA, uri);
+};
 
-  var element = this.map_.getViewport();
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.activePointers_ = 0;
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The fragment identifier, or null if none.  Does not
+ *     include the hash mark itself.
+ */
+goog.uri.utils.getFragmentEncoded = function(uri) {
+  // The hash mark may not appear in any other part of the URL.
+  var hashIndex = uri.indexOf('#');
+  return hashIndex < 0 ? null : uri.substr(hashIndex + 1);
+};
 
-  /**
-   * @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);
+/**
+ * @param {string} uri The URI to examine.
+ * @param {?string} fragment The encoded fragment identifier, or null if none.
+ *     Does not include the hash mark itself.
+ * @return {string} The URI with the fragment set.
+ */
+goog.uri.utils.setFragmentEncoded = function(uri, fragment) {
+  return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : '');
+};
 
-  /**
-   * Event handler which generates pointer events for
-   * the document (used when dragging).
-   *
-   * @type {ol.pointer.PointerEventHandler}
-   * @private
-   */
-  this.documentPointerEventHandler_ = null;
 
-  this.pointerdownListenerKey_ = goog.events.listen(this.pointerEventHandler_,
-      ol.pointer.EventType.POINTERDOWN,
-      this.handlePointerDown_, false, this);
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded fragment identifier, or null if none.  Does
+ *     not include the hash mark.
+ */
+goog.uri.utils.getFragment = function(uri) {
+  return goog.uri.utils.decodeIfPossible_(
+      goog.uri.utils.getFragmentEncoded(uri));
+};
 
-  this.relayedListenerKey_ = goog.events.listen(this.pointerEventHandler_,
-      ol.pointer.EventType.POINTERMOVE,
-      this.relayEvent_, false, this);
 
-  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-    /*
-     * On legacy IE, double clicks do not produce two mousedown and
-     * mouseup events. That is why a separate DBLCLICK event listener
-     * is used.
-     */
-    this.ieDblclickListenerKey_ = goog.events.listen(element,
-        goog.events.EventType.DBLCLICK,
-        this.emulateClickLegacyIE_, false, this);
-  }
+/**
+ * Extracts everything up to the port of the URI.
+ * @param {string} uri The URI string.
+ * @return {string} Everything up to and including the port.
+ */
+goog.uri.utils.getHost = function(uri) {
+  var pieces = goog.uri.utils.split(uri);
+  return goog.uri.utils.buildFromEncodedParts(
+      pieces[goog.uri.utils.ComponentIndex.SCHEME],
+      pieces[goog.uri.utils.ComponentIndex.USER_INFO],
+      pieces[goog.uri.utils.ComponentIndex.DOMAIN],
+      pieces[goog.uri.utils.ComponentIndex.PORT]);
+};
 
+
+/**
+ * Extracts the path of the URL and everything after.
+ * @param {string} uri The URI string.
+ * @return {string} The URI, starting at the path and including the query
+ *     parameters and fragment identifier.
+ */
+goog.uri.utils.getPathAndAfter = function(uri) {
+  var pieces = goog.uri.utils.split(uri);
+  return goog.uri.utils.buildFromEncodedParts(null, null, null, null,
+      pieces[goog.uri.utils.ComponentIndex.PATH],
+      pieces[goog.uri.utils.ComponentIndex.QUERY_DATA],
+      pieces[goog.uri.utils.ComponentIndex.FRAGMENT]);
 };
-goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);
 
 
 /**
- * Get the last "down" type event. This will be set on pointerdown.
- * @return {ol.pointer.PointerEvent} The most recent "down" type event (or null
- * if none have occurred).
+ * Gets the URI with the fragment identifier removed.
+ * @param {string} uri The URI to examine.
+ * @return {string} Everything preceding the hash mark.
  */
-ol.MapBrowserEventHandler.prototype.getDown = function() {
-  return this.down_;
+goog.uri.utils.removeFragment = function(uri) {
+  // The hash mark may not appear in any other part of the URL.
+  var hashIndex = uri.indexOf('#');
+  return hashIndex < 0 ? uri : uri.substr(0, hashIndex);
 };
 
 
 /**
- * @param {goog.events.BrowserEvent} browserEvent Pointer event.
- * @private
+ * Ensures that two URI's have the exact same domain, scheme, and port.
+ *
+ * Unlike the version in goog.Uri, this checks protocol, and therefore is
+ * suitable for checking against the browser's same-origin policy.
+ *
+ * @param {string} uri1 The first URI.
+ * @param {string} uri2 The second URI.
+ * @return {boolean} Whether they have the same scheme, domain and port.
  */
-ol.MapBrowserEventHandler.prototype.emulateClickLegacyIE_ =
-    function(browserEvent) {
-  var pointerEvent = this.pointerEventHandler_.wrapMouseEvent(
-      ol.MapBrowserEvent.EventType.POINTERUP,
-      browserEvent
-      );
-  this.emulateClick_(pointerEvent);
+goog.uri.utils.haveSameDomain = function(uri1, uri2) {
+  var pieces1 = goog.uri.utils.split(uri1);
+  var pieces2 = goog.uri.utils.split(uri2);
+  return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
+             pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
+         pieces1[goog.uri.utils.ComponentIndex.SCHEME] ==
+             pieces2[goog.uri.utils.ComponentIndex.SCHEME] &&
+         pieces1[goog.uri.utils.ComponentIndex.PORT] ==
+             pieces2[goog.uri.utils.ComponentIndex.PORT];
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * Asserts that there are no fragment or query identifiers, only in uncompiled
+ * mode.
+ * @param {string} uri The URI to examine.
  * @private
  */
-ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) {
-  var newEvent;
-  newEvent = new ol.MapBrowserPointerEvent(
-      ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent);
-  this.dispatchEvent(newEvent);
-  if (this.clickTimeoutId_ !== 0) {
-    // double-click
-    goog.global.clearTimeout(this.clickTimeoutId_);
-    this.clickTimeoutId_ = 0;
-    newEvent = new ol.MapBrowserPointerEvent(
-        ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent);
-    this.dispatchEvent(newEvent);
-  } else {
-    // click
-    this.clickTimeoutId_ = goog.global.setTimeout(goog.bind(function() {
-      this.clickTimeoutId_ = 0;
-      var newEvent = new ol.MapBrowserPointerEvent(
-          ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent);
-      this.dispatchEvent(newEvent);
-    }, this), 250);
+goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) {
+  // NOTE: would use goog.asserts here, but jscompiler doesn't know that
+  // indexOf has no side effects.
+  if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) {
+    throw Error('goog.uri.utils: Fragment or query identifiers are not ' +
+        'supported: [' + uri + ']');
   }
 };
 
 
 /**
- * Keeps track on how many pointers are currently active.
+ * Supported query parameter values by the parameter serializing utilities.
  *
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
+ * If a value is null or undefined, the key-value pair is skipped, as an easy
+ * way to omit parameters conditionally.  Non-array parameters are converted
+ * to a string and URI encoded.  Array values are expanded into multiple
+ * &key=value pairs, with each element stringized and URI-encoded.
+ *
+ * @typedef {*}
  */
-ol.MapBrowserEventHandler.prototype.updateActivePointers_ =
-    function(pointerEvent) {
-  var event = pointerEvent;
+goog.uri.utils.QueryValue;
 
-  if (event.type == ol.MapBrowserEvent.EventType.POINTERUP ||
-      event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) {
-    delete this.trackedTouches_[event.pointerId];
-  } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
-    this.trackedTouches_[event.pointerId] = true;
-  }
-  this.activePointers_ = goog.object.getCount(this.trackedTouches_);
-};
+
+/**
+ * An array representing a set of query parameters with alternating keys
+ * and values.
+ *
+ * Keys are assumed to be URI encoded already and live at even indices.  See
+ * goog.uri.utils.QueryValue for details on how parameter values are encoded.
+ *
+ * Example:
+ * <pre>
+ * var data = [
+ *   // Simple param: ?name=BobBarker
+ *   'name', 'BobBarker',
+ *   // Conditional param -- may be omitted entirely.
+ *   'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null,
+ *   // Multi-valued param: &house=LosAngeles&house=NewYork&house=null
+ *   'house', ['LosAngeles', 'NewYork', null]
+ * ];
+ * </pre>
+ *
+ * @typedef {!Array<string|goog.uri.utils.QueryValue>}
+ */
+goog.uri.utils.QueryArray;
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * Appends a URI and query data in a string buffer with special preconditions.
+ *
+ * Internal implementation utility, performing very few object allocations.
+ *
+ * @param {!Array<string|undefined>} buffer A string buffer.  The first element
+ *     must be the base URI, and may have a fragment identifier.  If the array
+ *     contains more than one element, the second element must be an ampersand,
+ *     and may be overwritten, depending on the base URI.  Undefined elements
+ *     are treated as empty-string.
+ * @return {string} The concatenated URI and query data.
  * @private
  */
-ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
-  this.updateActivePointers_(pointerEvent);
-  var newEvent = new ol.MapBrowserPointerEvent(
-      ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent);
-  this.dispatchEvent(newEvent);
-
-  goog.asserts.assert(this.activePointers_ >= 0);
-  if (this.activePointers_ === 0) {
-    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
-    this.dragListenerKeys_ = null;
-    goog.dispose(this.documentPointerEventHandler_);
-    this.documentPointerEventHandler_ = null;
+goog.uri.utils.appendQueryData_ = function(buffer) {
+  if (buffer[1]) {
+    // At least one query parameter was added.  We need to check the
+    // punctuation mark, which is currently an ampersand, and also make sure
+    // there aren't any interfering fragment identifiers.
+    var baseUri = /** @type {string} */ (buffer[0]);
+    var hashIndex = baseUri.indexOf('#');
+    if (hashIndex >= 0) {
+      // Move the fragment off the base part of the URI into the end.
+      buffer.push(baseUri.substr(hashIndex));
+      buffer[0] = baseUri = baseUri.substr(0, hashIndex);
+    }
+    var questionIndex = baseUri.indexOf('?');
+    if (questionIndex < 0) {
+      // No question mark, so we need a question mark instead of an ampersand.
+      buffer[1] = '?';
+    } else if (questionIndex == baseUri.length - 1) {
+      // Question mark is the very last character of the existing URI, so don't
+      // append an additional delimiter.
+      buffer[1] = undefined;
+    }
   }
 
-  // We emulate click event on left mouse button click, touch contact, and pen
-  // contact. isMouseActionButton returns true in these cases (evt.button is set
-  // to 0).
-  // See http://www.w3.org/TR/pointerevents/#button-states
-  if (!this.dragged_ && this.isMouseActionButton_(pointerEvent)) {
-    goog.asserts.assert(!goog.isNull(this.down_));
-    this.emulateClick_(this.down_);
-  }
+  return buffer.join('');
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @return {boolean} If the left mouse button was pressed.
+ * Appends key=value pairs to an array, supporting multi-valued objects.
+ * @param {string} key The key prefix.
+ * @param {goog.uri.utils.QueryValue} value The value to serialize.
+ * @param {!Array<string>} pairs The array to which the 'key=value' strings
+ *     should be appended.
  * @private
  */
-ol.MapBrowserEventHandler.prototype.isMouseActionButton_ =
-    function(pointerEvent) {
-  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-    return pointerEvent.button == 1;
-  } else {
-    return pointerEvent.button === 0;
+goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) {
+  if (goog.isArray(value)) {
+    // Convince the compiler it's an array.
+    goog.asserts.assertArray(value);
+    for (var j = 0; j < value.length; j++) {
+      // Convert to string explicitly, to short circuit the null and array
+      // logic in this function -- this ensures that null and undefined get
+      // written as literal 'null' and 'undefined', and arrays don't get
+      // expanded out but instead encoded in the default way.
+      goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs);
+    }
+  } else if (value != null) {
+    // Skip a top-level null or undefined entirely.
+    pairs.push('&', key,
+        // Check for empty string. Zero gets encoded into the url as literal
+        // strings.  For empty string, skip the equal sign, to be consistent
+        // with UriBuilder.java.
+        value === '' ? '' : '=',
+        goog.string.urlEncode(value));
   }
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * Builds a buffer of query data from a sequence of alternating keys and values.
+ *
+ * @param {!Array<string|undefined>} buffer A string buffer to append to.  The
+ *     first element appended will be an '&', and may be replaced by the caller.
+ * @param {!goog.uri.utils.QueryArray|!Arguments} keysAndValues An array with
+ *     alternating keys and values -- see the typedef.
+ * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
+ * @return {!Array<string|undefined>} The buffer argument.
  * @private
  */
-ol.MapBrowserEventHandler.prototype.handlePointerDown_ =
-    function(pointerEvent) {
-  this.updateActivePointers_(pointerEvent);
-  var newEvent = new ol.MapBrowserPointerEvent(
-      ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent);
-  this.dispatchEvent(newEvent);
+goog.uri.utils.buildQueryDataBuffer_ = function(
+    buffer, keysAndValues, opt_startIndex) {
+  goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0),
+      0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.');
 
-  this.down_ = pointerEvent;
-  this.dragged_ = false;
+  for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) {
+    goog.uri.utils.appendKeyValuePairs_(
+        keysAndValues[i], keysAndValues[i + 1], buffer);
+  }
 
-  if (goog.isNull(this.dragListenerKeys_)) {
-    /* Set up a pointer event handler on the `document`,
-     * which is required when the pointer is moved outside
-     * the viewport when dragging.
-     */
-    this.documentPointerEventHandler_ =
-        new ol.pointer.PointerEventHandler(document);
+  return buffer;
+};
 
-    this.dragListenerKeys_ = [
-      goog.events.listen(this.documentPointerEventHandler_,
-          ol.MapBrowserEvent.EventType.POINTERMOVE,
-          this.handlePointerMove_, false, this),
-      goog.events.listen(this.documentPointerEventHandler_,
-          ol.MapBrowserEvent.EventType.POINTERUP,
-          this.handlePointerUp_, false, this),
-      /* Note that the listener for `pointercancel is set up on
-       * `pointerEventHandler_` and not `documentPointerEventHandler_` like
-       * the `pointerup` and `pointermove` listeners.
-       *
-       * The reason for this is the following: `TouchSource.vacuumTouches_()`
-       * issues `pointercancel` events, when there was no `touchend` for a
-       * `touchstart`. Now, let's say a first `touchstart` is registered on
-       * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up.
-       * But `documentPointerEventHandler_` doesn't know about the first
-       * `touchstart`. If there is no `touchend` for the `touchstart`, we can
-       * only receive a `touchcancel` from `pointerEventHandler_`, because it is
-       * only registered there.
-       */
-      goog.events.listen(this.pointerEventHandler_,
-          ol.MapBrowserEvent.EventType.POINTERCANCEL,
-          this.handlePointerUp_, false, this)
-    ];
-  }
 
-  // FIXME check if/when this is necessary
-  // prevent context menu
-  pointerEvent.preventDefault();
+/**
+ * Builds a query data string from a sequence of alternating keys and values.
+ * Currently generates "&key&" for empty args.
+ *
+ * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and
+ *     values.  See the typedef.
+ * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
+ * @return {string} The encoded query string, in the form 'a=1&b=2'.
+ */
+goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) {
+  var buffer = goog.uri.utils.buildQueryDataBuffer_(
+      [], keysAndValues, opt_startIndex);
+  buffer[0] = ''; // Remove the leading ampersand.
+  return buffer.join('');
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * Builds a buffer of query data from a map.
+ *
+ * @param {!Array<string|undefined>} buffer A string buffer to append to.  The
+ *     first element appended will be an '&', and may be replaced by the caller.
+ * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys
+ *     are URI-encoded parameter keys, and the values conform to the contract
+ *     specified in the goog.uri.utils.QueryValue typedef.
+ * @return {!Array<string|undefined>} The buffer argument.
  * @private
  */
-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 (pointerEvent.clientX != this.down_.clientX ||
-      pointerEvent.clientY != this.down_.clientY) {
-    this.dragged_ = true;
-    var newEvent = new ol.MapBrowserPointerEvent(
-        ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent);
-    this.dispatchEvent(newEvent);
+goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) {
+  for (var key in map) {
+    goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer);
   }
 
-  // Some native android browser triggers mousemove events during small period
-  // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or
-  // https://code.google.com/p/android/issues/detail?id=19827
-  // ex: Galaxy Tab P3110 + Android 4.1.1
-  pointerEvent.preventDefault();
+  return buffer;
 };
 
 
 /**
- * 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
+ * Builds a query data string from a map.
+ * Currently generates "&key&" for empty args.
+ *
+ * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys
+ *     are URI-encoded parameter keys, and the values are arbitrary types
+ *     or arrays. Keys with a null value are dropped.
+ * @return {string} The encoded query string, in the form 'a=1&b=2'.
  */
-ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
-  this.dispatchEvent(new ol.MapBrowserPointerEvent(
-      pointerEvent.type, this.map_, pointerEvent));
+goog.uri.utils.buildQueryDataFromMap = function(map) {
+  var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map);
+  buffer[0] = '';
+  return buffer.join('');
 };
 
 
 /**
- * @inheritDoc
- */
-ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
-  if (!goog.isNull(this.relayedListenerKey_)) {
-    goog.events.unlistenByKey(this.relayedListenerKey_);
-    this.relayedListenerKey_ = null;
-  }
-  if (!goog.isNull(this.pointerdownListenerKey_)) {
-    goog.events.unlistenByKey(this.pointerdownListenerKey_);
-    this.pointerdownListenerKey_ = null;
-  }
-  if (!goog.isNull(this.dragListenerKeys_)) {
-    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
-    this.dragListenerKeys_ = null;
-  }
-  if (!goog.isNull(this.documentPointerEventHandler_)) {
-    goog.dispose(this.documentPointerEventHandler_);
-    this.documentPointerEventHandler_ = null;
-  }
-  if (!goog.isNull(this.pointerEventHandler_)) {
-    goog.dispose(this.pointerEventHandler_);
-    this.pointerEventHandler_ = null;
-  }
-  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE &&
-      !goog.isNull(this.ieDblclickListenerKey_)) {
-    goog.events.unlistenByKey(this.ieDblclickListenerKey_);
-    this.ieDblclickListenerKey_ = null;
-  }
-  goog.base(this, 'disposeInternal');
+ * Appends URI parameters to an existing URI.
+ *
+ * The variable arguments may contain alternating keys and values.  Keys are
+ * assumed to be already URI encoded.  The values should not be URI-encoded,
+ * and will instead be encoded by this function.
+ * <pre>
+ * appendParams('http://www.foo.com?existing=true',
+ *     'key1', 'value1',
+ *     'key2', 'value?willBeEncoded',
+ *     'key3', ['valueA', 'valueB', 'valueC'],
+ *     'key4', null);
+ * result: 'http://www.foo.com?existing=true&' +
+ *     'key1=value1&' +
+ *     'key2=value%3FwillBeEncoded&' +
+ *     'key3=valueA&key3=valueB&key3=valueC'
+ * </pre>
+ *
+ * A single call to this function will not exhibit quadratic behavior in IE,
+ * whereas multiple repeated calls may, although the effect is limited by
+ * fact that URL's generally can't exceed 2kb.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args
+ *     An array or argument list conforming to goog.uri.utils.QueryArray.
+ * @return {string} The URI with all query parameters added.
+ */
+goog.uri.utils.appendParams = function(uri, var_args) {
+  return goog.uri.utils.appendQueryData_(
+      arguments.length == 2 ?
+      goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) :
+      goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1));
 };
 
 
 /**
- * Constants for event names.
- * @enum {string}
+ * Appends query parameters from a map.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {!Object<goog.uri.utils.QueryValue>} map An object where keys are
+ *     URI-encoded parameter keys, and the values are arbitrary types or arrays.
+ *     Keys with a null value are dropped.
+ * @return {string} The new parameters.
  */
-ol.MapBrowserEvent.EventType = {
-  // derived event types
-  /**
-   * A true single click with no dragging and no double click. Note that this
-   * event is delayed by 250 ms to ensure that it is not a double click.
-   * @event ol.MapBrowserEvent#singleclick
-   * @api
-   */
-  SINGLECLICK: 'singleclick',
-  /**
-   * A click with no dragging. A double click will fire two of this.
-   * @event ol.MapBrowserEvent#click
-   * @api
-   */
-  CLICK: goog.events.EventType.CLICK,
-  /**
-   * A true double click, with no dragging.
-   * @event ol.MapBrowserEvent#dblclick
-   * @api
-   */
-  DBLCLICK: goog.events.EventType.DBLCLICK,
-  /**
-   * Triggered when a pointer is dragged.
-   * @event ol.MapBrowserEvent#pointerdrag
-   * @api
-   */
-  POINTERDRAG: 'pointerdrag',
-
-  // original pointer event types
-  /**
-   * Triggered when a pointer is moved. Note that on touch devices this is
-   * triggered when the map is panned, so is not the same as mousemove.
-   * @event ol.MapBrowserEvent#pointermove
-   * @api
-   */
-  POINTERMOVE: 'pointermove',
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
+goog.uri.utils.appendParamsFromMap = function(uri, map) {
+  return goog.uri.utils.appendQueryData_(
+      goog.uri.utils.buildQueryDataBufferFromMap_([uri], map));
 };
 
-goog.provide('ol.events.ConditionType');
-goog.provide('ol.events.condition');
-
-goog.require('goog.asserts');
-goog.require('goog.dom.TagName');
-goog.require('goog.functions');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserPointerEvent');
-
 
 /**
- * A function that takes an {@link ol.MapBrowserEvent} and returns a
- * `{boolean}`. If the condition is met, true should be returned.
+ * Appends a single URI parameter.
  *
- * @typedef {function(ol.MapBrowserEvent): boolean}
- * @api stable
+ * Repeated calls to this can exhibit quadratic behavior in IE6 due to the
+ * way string append works, though it should be limited given the 2kb limit.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {string} key The key, which must already be URI encoded.
+ * @param {*=} opt_value The value, which will be stringized and encoded
+ *     (assumed not already to be encoded).  If omitted, undefined, or null, the
+ *     key will be added as a valueless parameter.
+ * @return {string} The URI with the query parameter added.
  */
-ol.events.ConditionType;
+goog.uri.utils.appendParam = function(uri, key, opt_value) {
+  var paramArr = [uri, '&', key];
+  if (goog.isDefAndNotNull(opt_value)) {
+    paramArr.push('=', goog.string.urlEncode(opt_value));
+  }
+  return goog.uri.utils.appendQueryData_(paramArr);
+};
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the alt key is pressed.
- * @api stable
+ * Finds the next instance of a query parameter with the specified name.
+ *
+ * Does not instantiate any objects.
+ *
+ * @param {string} uri The URI to search.  May contain a fragment identifier
+ *     if opt_hashIndex is specified.
+ * @param {number} startIndex The index to begin searching for the key at.  A
+ *     match may be found even if this is one character after the ampersand.
+ * @param {string} keyEncoded The URI-encoded key.
+ * @param {number} hashOrEndIndex Index to stop looking at.  If a hash
+ *     mark is present, it should be its index, otherwise it should be the
+ *     length of the string.
+ * @return {number} The position of the first character in the key's name,
+ *     immediately after either a question mark or a dot.
+ * @private
  */
-ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
-  var browserEvent = mapBrowserEvent.browserEvent;
-  return (
-      browserEvent.altKey &&
-      !browserEvent.platformModifierKey &&
-      !browserEvent.shiftKey);
-};
+goog.uri.utils.findParam_ = function(
+    uri, startIndex, keyEncoded, hashOrEndIndex) {
+  var index = startIndex;
+  var keyLength = keyEncoded.length;
 
+  // Search for the key itself and post-filter for surronuding punctuation,
+  // rather than expensively building a regexp.
+  while ((index = uri.indexOf(keyEncoded, index)) >= 0 &&
+      index < hashOrEndIndex) {
+    var precedingChar = uri.charCodeAt(index - 1);
+    // Ensure that the preceding character is '&' or '?'.
+    if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND ||
+        precedingChar == goog.uri.utils.CharCode_.QUESTION) {
+      // Ensure the following character is '&', '=', '#', or NaN
+      // (end of string).
+      var followingChar = uri.charCodeAt(index + keyLength);
+      if (!followingChar ||
+          followingChar == goog.uri.utils.CharCode_.EQUAL ||
+          followingChar == goog.uri.utils.CharCode_.AMPERSAND ||
+          followingChar == goog.uri.utils.CharCode_.HASH) {
+        return index;
+      }
+    }
+    index += keyLength + 1;
+  }
 
-/**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the alt and shift keys are pressed.
- * @api stable
- */
-ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
-  var browserEvent = mapBrowserEvent.browserEvent;
-  return (
-      browserEvent.altKey &&
-      !browserEvent.platformModifierKey &&
-      browserEvent.shiftKey);
+  return -1;
 };
 
 
 /**
- * Always true.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True.
- * @function
- * @api stable
+ * Regular expression for finding a hash mark or end of string.
+ * @type {RegExp}
+ * @private
  */
-ol.events.condition.always = goog.functions.TRUE;
+goog.uri.utils.hashOrEndRe_ = /#|$/;
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event is a map `click` event.
- * @api stable
+ * Determines if the URI contains a specific key.
+ *
+ * Performs no object instantiations.
+ *
+ * @param {string} uri The URI to process.  May contain a fragment
+ *     identifier.
+ * @param {string} keyEncoded The URI-encoded key.  Case-sensitive.
+ * @return {boolean} Whether the key is present.
  */
-ol.events.condition.click = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK;
+goog.uri.utils.hasParam = function(uri, keyEncoded) {
+  return goog.uri.utils.findParam_(uri, 0, keyEncoded,
+      uri.search(goog.uri.utils.hashOrEndRe_)) >= 0;
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the browser event is a `mousemove` event.
- * @api
+ * Gets the first value of a query parameter.
+ * @param {string} uri The URI to process.  May contain a fragment.
+ * @param {string} keyEncoded The URI-encoded key.  Case-sensitive.
+ * @return {?string} The first value of the parameter (URI-decoded), or null
+ *     if the parameter is not found.
  */
-ol.events.condition.mouseMove = function(mapBrowserEvent) {
-  return mapBrowserEvent.originalEvent.type == 'mousemove';
+goog.uri.utils.getParamValue = function(uri, keyEncoded) {
+  var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
+  var foundIndex = goog.uri.utils.findParam_(
+      uri, 0, keyEncoded, hashOrEndIndex);
+
+  if (foundIndex < 0) {
+    return null;
+  } else {
+    var endPosition = uri.indexOf('&', foundIndex);
+    if (endPosition < 0 || endPosition > hashOrEndIndex) {
+      endPosition = hashOrEndIndex;
+    }
+    // Progress forth to the end of the "key=" or "key&" substring.
+    foundIndex += keyEncoded.length + 1;
+    // Use substr, because it (unlike substring) will return empty string
+    // if foundIndex > endPosition.
+    return goog.string.urlDecode(
+        uri.substr(foundIndex, endPosition - foundIndex));
+  }
 };
 
 
 /**
- * Always false.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} False.
- * @function
- * @api stable
+ * Gets all values of a query parameter.
+ * @param {string} uri The URI to process.  May contain a framgnet.
+ * @param {string} keyEncoded The URI-encoded key.  Case-snsitive.
+ * @return {!Array<string>} All URI-decoded values with the given key.
+ *     If the key is not found, this will have length 0, but never be null.
  */
-ol.events.condition.never = goog.functions.FALSE;
+goog.uri.utils.getParamValues = function(uri, keyEncoded) {
+  var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
+  var position = 0;
+  var foundIndex;
+  var result = [];
+
+  while ((foundIndex = goog.uri.utils.findParam_(
+      uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
+    // Find where this parameter ends, either the '&' or the end of the
+    // query parameters.
+    position = uri.indexOf('&', foundIndex);
+    if (position < 0 || position > hashOrEndIndex) {
+      position = hashOrEndIndex;
+    }
 
+    // Progress forth to the end of the "key=" or "key&" substring.
+    foundIndex += keyEncoded.length + 1;
+    // Use substr, because it (unlike substring) will return empty string
+    // if foundIndex > position.
+    result.push(goog.string.urlDecode(uri.substr(
+        foundIndex, position - foundIndex)));
+  }
 
-/**
- * @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 result;
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True only if there no modifier keys are pressed.
- * @api stable
+ * Regexp to find trailing question marks and ampersands.
+ * @type {RegExp}
+ * @private
  */
-ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
-  var browserEvent = mapBrowserEvent.browserEvent;
-  return (
-      !browserEvent.altKey &&
-      !browserEvent.platformModifierKey &&
-      !browserEvent.shiftKey);
-};
+goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/;
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the platform modifier key is pressed.
- * @api stable
+ * Removes all instances of a query parameter.
+ * @param {string} uri The URI to process.  Must not contain a fragment.
+ * @param {string} keyEncoded The URI-encoded key.
+ * @return {string} The URI with all instances of the parameter removed.
  */
-ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
-  var browserEvent = mapBrowserEvent.browserEvent;
-  return (
-      !browserEvent.altKey &&
-      browserEvent.platformModifierKey &&
-      !browserEvent.shiftKey);
+goog.uri.utils.removeParam = function(uri, keyEncoded) {
+  var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
+  var position = 0;
+  var foundIndex;
+  var buffer = [];
+
+  // Look for a query parameter.
+  while ((foundIndex = goog.uri.utils.findParam_(
+      uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
+    // Get the portion of the query string up to, but not including, the ?
+    // or & starting the parameter.
+    buffer.push(uri.substring(position, foundIndex));
+    // Progress to immediately after the '&'.  If not found, go to the end.
+    // Avoid including the hash mark.
+    position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex,
+        hashOrEndIndex);
+  }
+
+  // Append everything that is remaining.
+  buffer.push(uri.substr(position));
+
+  // Join the buffer, and remove trailing punctuation that remains.
+  return buffer.join('').replace(
+      goog.uri.utils.trailingQueryPunctuationRe_, '$1');
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the shift key is pressed.
- * @api stable
+ * Replaces all existing definitions of a parameter with a single definition.
+ *
+ * Repeated calls to this can exhibit quadratic behavior due to the need to
+ * find existing instances and reconstruct the string, though it should be
+ * limited given the 2kb limit.  Consider using appendParams to append multiple
+ * parameters in bulk.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {string} keyEncoded The key, which must already be URI encoded.
+ * @param {*} value The value, which will be stringized and encoded (assumed
+ *     not already to be encoded).
+ * @return {string} The URI with the query parameter added.
  */
-ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
-  var browserEvent = mapBrowserEvent.browserEvent;
-  return (
-      !browserEvent.altKey &&
-      !browserEvent.platformModifierKey &&
-      browserEvent.shiftKey);
+goog.uri.utils.setParam = function(uri, keyEncoded, value) {
+  return goog.uri.utils.appendParam(
+      goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value);
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True only if the target element is not editable.
- * @api
+ * Generates a URI path using a given URI and a path with checks to
+ * prevent consecutive "//". The baseUri passed in must not contain
+ * query or fragment identifiers. The path to append may not contain query or
+ * fragment identifiers.
+ *
+ * @param {string} baseUri URI to use as the base.
+ * @param {string} path Path to append.
+ * @return {string} Updated URI.
  */
-ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
-  var target = mapBrowserEvent.browserEvent.target;
-  goog.asserts.assertInstanceof(target, Element);
-  var tagName = target.tagName;
-  return (
-      tagName !== goog.dom.TagName.INPUT &&
-      tagName !== goog.dom.TagName.SELECT &&
-      tagName !== goog.dom.TagName.TEXTAREA);
+goog.uri.utils.appendPath = function(baseUri, path) {
+  goog.uri.utils.assertNoFragmentsOrQueries_(baseUri);
+
+  // Remove any trailing '/'
+  if (goog.string.endsWith(baseUri, '/')) {
+    baseUri = baseUri.substr(0, baseUri.length - 1);
+  }
+  // Remove any leading '/'
+  if (goog.string.startsWith(path, '/')) {
+    path = path.substr(1);
+  }
+  return goog.string.buildString(baseUri, '/', path);
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event originates from a mouse device.
- * @api stable
+ * Replaces the path.
+ * @param {string} uri URI to use as the base.
+ * @param {string} path New path.
+ * @return {string} Updated URI.
  */
-ol.events.condition.mouseOnly = function(mapBrowserEvent) {
-  goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent);
-  /* pointerId must be 1 for mouse devices,
-   * see: http://www.w3.org/Submission/pointer-events/#pointerevent-interface
-   */
-  return mapBrowserEvent.pointerEvent.pointerId == 1;
+goog.uri.utils.setPath = function(uri, path) {
+  // Add any missing '/'.
+  if (!goog.string.startsWith(path, '/')) {
+    path = '/' + path;
+  }
+  var parts = goog.uri.utils.split(uri);
+  return goog.uri.utils.buildFromEncodedParts(
+      parts[goog.uri.utils.ComponentIndex.SCHEME],
+      parts[goog.uri.utils.ComponentIndex.USER_INFO],
+      parts[goog.uri.utils.ComponentIndex.DOMAIN],
+      parts[goog.uri.utils.ComponentIndex.PORT],
+      path,
+      parts[goog.uri.utils.ComponentIndex.QUERY_DATA],
+      parts[goog.uri.utils.ComponentIndex.FRAGMENT]);
 };
 
-goog.provide('ol.geom.Geometry');
-goog.provide('ol.geom.GeometryType');
-
-goog.require('goog.asserts');
-goog.require('goog.functions');
-goog.require('ol.Observable');
-goog.require('ol.proj');
-
 
 /**
- * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
- * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
- * `'GeometryCollection'`, `'Circle'`.
+ * Standard supported query parameters.
  * @enum {string}
- * @api stable
  */
-ol.geom.GeometryType = {
-  POINT: 'Point',
-  LINE_STRING: 'LineString',
-  LINEAR_RING: 'LinearRing',
-  POLYGON: 'Polygon',
-  MULTI_POINT: 'MultiPoint',
-  MULTI_LINE_STRING: 'MultiLineString',
-  MULTI_POLYGON: 'MultiPolygon',
-  GEOMETRY_COLLECTION: 'GeometryCollection',
-  CIRCLE: 'Circle'
+goog.uri.utils.StandardQueryParam = {
+
+  /** Unused parameter for unique-ifying. */
+  RANDOM: 'zx'
 };
 
 
 /**
- * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z')
- * or measure ('M') coordinate is available. Supported values are `'XY'`,
- * `'XYZ'`, `'XYM'`, `'XYZM'`.
- * @enum {string}
- * @api stable
+ * Sets the zx parameter of a URI to a random value.
+ * @param {string} uri Any URI.
+ * @return {string} That URI with the "zx" parameter added or replaced to
+ *     contain a random string.
  */
-ol.geom.GeometryLayout = {
-  XY: 'XY',
-  XYZ: 'XYZ',
-  XYM: 'XYM',
-  XYZM: 'XYZM'
+goog.uri.utils.makeUnique = function(uri) {
+  return goog.uri.utils.setParam(uri,
+      goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString());
 };
 
-
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for vector geometries.
+ * @fileoverview Class for parsing and formatting URIs.
+ *
+ * Use goog.Uri(string) to parse a URI string.  Use goog.Uri.create(...) to
+ * create a new instance of the goog.Uri object from Uri parts.
+ *
+ * e.g: <code>var myUri = new goog.Uri(window.location);</code>
+ *
+ * Implements RFC 3986 for parsing/formatting URIs.
+ * http://www.ietf.org/rfc/rfc3986.txt
+ *
+ * Some changes have been made to the interface (more like .NETs), though the
+ * internal representation is now of un-encoded parts, this will change the
+ * behavior slightly.
  *
- * @constructor
- * @extends {ol.Observable}
- * @fires change Triggered when the geometry changes.
- * @api stable
  */
-ol.geom.Geometry = function() {
 
-  goog.base(this);
+goog.provide('goog.Uri');
+goog.provide('goog.Uri.QueryData');
 
-  /**
-   * @protected
-   * @type {ol.Extent|undefined}
-   */
-  this.extent = undefined;
+goog.require('goog.array');
+goog.require('goog.string');
+goog.require('goog.structs');
+goog.require('goog.structs.Map');
+goog.require('goog.uri.utils');
+goog.require('goog.uri.utils.ComponentIndex');
+goog.require('goog.uri.utils.StandardQueryParam');
 
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.extentRevision = -1;
-
-  /**
-   * @protected
-   * @type {Object.<string, ol.geom.Geometry>}
-   */
-  this.simplifiedGeometryCache = {};
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.simplifiedGeometryMaxMinSquaredTolerance = 0;
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.simplifiedGeometryRevision = 0;
-
-};
-goog.inherits(ol.geom.Geometry, ol.Observable);
-
-
-/**
- * Make a complete copy of the geometry.
- * @function
- * @return {!ol.geom.Geometry} Clone.
- * @api stable
- */
-ol.geom.Geometry.prototype.clone = goog.abstractMethod;
-
-
-/**
- * @param {number} x X.
- * @param {number} y Y.
- * @param {ol.Coordinate} closestPoint Closest point.
- * @param {number} minSquaredDistance Minimum squared distance.
- * @return {number} Minimum squared distance.
- */
-ol.geom.Geometry.prototype.closestPointXY = goog.abstractMethod;
 
 
 /**
- * @param {ol.Coordinate} point Point.
- * @param {ol.Coordinate=} opt_closestPoint Closest point.
- * @return {ol.Coordinate} Closest point.
- * @api stable
+ * This class contains setters and getters for the parts of the URI.
+ * The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part
+ * -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the
+ * decoded path, <code>/foo bar</code>.
+ *
+ * Reserved characters (see RFC 3986 section 2.2) can be present in
+ * their percent-encoded form in scheme, domain, and path URI components and
+ * will not be auto-decoded. For example:
+ * <code>goog.Uri.parse('rel%61tive/path%2fto/resource').getPath()</code> will
+ * return <code>relative/path%2fto/resource</code>.
+ *
+ * The constructor accepts an optional unparsed, raw URI string.  The parser
+ * is relaxed, so special characters that aren't escaped but don't cause
+ * ambiguities will not cause parse failures.
+ *
+ * All setters return <code>this</code> and so may be chained, a la
+ * <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>.
+ *
+ * @param {*=} opt_uri Optional string URI to parse
+ *        (use goog.Uri.create() to create a URI from parts), or if
+ *        a goog.Uri is passed, a clone is created.
+ * @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore
+ * the case of the parameter name.
+ *
+ * @constructor
  */
-ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) {
-  var closestPoint = goog.isDef(opt_closestPoint) ?
-      opt_closestPoint : [NaN, NaN];
-  this.closestPointXY(point[0], point[1], closestPoint, Infinity);
-  return closestPoint;
-};
+goog.Uri = function(opt_uri, opt_ignoreCase) {
+  // Parse in the uri string
+  var m;
+  if (opt_uri instanceof goog.Uri) {
+    this.ignoreCase_ = goog.isDef(opt_ignoreCase) ?
+        opt_ignoreCase : opt_uri.getIgnoreCase();
+    this.setScheme(opt_uri.getScheme());
+    this.setUserInfo(opt_uri.getUserInfo());
+    this.setDomain(opt_uri.getDomain());
+    this.setPort(opt_uri.getPort());
+    this.setPath(opt_uri.getPath());
+    this.setQueryData(opt_uri.getQueryData().clone());
+    this.setFragment(opt_uri.getFragment());
+  } else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) {
+    this.ignoreCase_ = !!opt_ignoreCase;
 
+    // Set the parts -- decoding as we do so.
+    // COMPATABILITY NOTE - In IE, unmatched fields may be empty strings,
+    // whereas in other browsers they will be undefined.
+    this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true);
+    this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true);
+    this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true);
+    this.setPort(m[goog.uri.utils.ComponentIndex.PORT]);
+    this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true);
+    this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true);
+    this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true);
 
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {boolean} Contains coordinate.
- */
-ol.geom.Geometry.prototype.containsCoordinate = function(coordinate) {
-  return this.containsXY(coordinate[0], coordinate[1]);
+  } else {
+    this.ignoreCase_ = !!opt_ignoreCase;
+    this.queryData_ = new goog.Uri.QueryData(null, null, this.ignoreCase_);
+  }
 };
 
 
 /**
- * @param {number} x X.
- * @param {number} y Y.
- * @return {boolean} Contains (x, y).
+ * If true, we preserve the type of query parameters set programmatically.
+ *
+ * This means that if you set a parameter to a boolean, and then call
+ * getParameterValue, you will get a boolean back.
+ *
+ * If false, we will coerce parameters to strings, just as they would
+ * appear in real URIs.
+ *
+ * TODO(nicksantos): Remove this once people have time to fix all tests.
+ *
+ * @type {boolean}
  */
-ol.geom.Geometry.prototype.containsXY = goog.functions.FALSE;
+goog.Uri.preserveParameterTypesCompatibilityFlag = false;
 
 
 /**
- * Get the extent of the geometry.
- * @function
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} extent Extent.
- * @api stable
+ * Parameter name added to stop caching.
+ * @type {string}
  */
-ol.geom.Geometry.prototype.getExtent = goog.abstractMethod;
+goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;
 
 
 /**
- * Create a simplified version of this geometry using the Douglas Peucker
- * algorithm.
- * @see http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
- * @function
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.Geometry} Simplified geometry.
+ * Scheme such as "http".
+ * @type {string}
+ * @private
  */
-ol.geom.Geometry.prototype.getSimplifiedGeometry = goog.abstractMethod;
+goog.Uri.prototype.scheme_ = '';
 
 
 /**
- * Get the type of this geometry.
- * @function
- * @return {ol.geom.GeometryType} Geometry type.
- * @api stable
+ * User credentials in the form "username:password".
+ * @type {string}
+ * @private
  */
-ol.geom.Geometry.prototype.getType = goog.abstractMethod;
+goog.Uri.prototype.userInfo_ = '';
 
 
 /**
- * Apply a transform function to the geometry.  Modifies the geometry in place.
- * If you do not want the geometry modified in place, first clone() it and
- * then use this function on the clone.
- * @function
- * @param {ol.TransformFunction} transformFn Transform.
- * @api stable
+ * Domain part, e.g. "www.google.com".
+ * @type {string}
+ * @private
  */
-ol.geom.Geometry.prototype.applyTransform = goog.abstractMethod;
+goog.Uri.prototype.domain_ = '';
 
 
 /**
- * Transform a geometry from one coordinate reference system to another.
- * Modifies the geometry in place.
- * If you do not want the geometry modified in place, first clone() it and
- * then use this function on the clone.
- *
- * @param {ol.proj.ProjectionLike} source The current projection.  Can be a
- *     string identifier or a {@link ol.proj.Projection} object.
- * @param {ol.proj.ProjectionLike} destination The desired projection.  Can be a
- *     string identifier or a {@link ol.proj.Projection} object.
- * @return {ol.geom.Geometry} This geometry.  Note that original geometry is
- *     modified in place.
- * @api stable
+ * Port, e.g. 8080.
+ * @type {?number}
+ * @private
  */
-ol.geom.Geometry.prototype.transform = function(source, destination) {
-  this.applyTransform(ol.proj.getTransform(source, destination));
-  return this;
-};
-
-goog.provide('ol.render.canvas');
+goog.Uri.prototype.port_ = null;
 
 
 /**
- * @typedef {{fillStyle: string}}
+ * Path, e.g. "/tests/img.png".
+ * @type {string}
+ * @private
  */
-ol.render.canvas.FillState;
+goog.Uri.prototype.path_ = '';
 
 
 /**
- * @typedef {{lineCap: string,
- *            lineDash: Array.<number>,
- *            lineJoin: string,
- *            lineWidth: number,
- *            miterLimit: number,
- *            strokeStyle: string}}
+ * Object representing query data.
+ * @type {!goog.Uri.QueryData}
+ * @private
  */
-ol.render.canvas.StrokeState;
+goog.Uri.prototype.queryData_;
 
 
 /**
- * @typedef {{font: string,
- *            textAlign: string,
- *            textBaseline: string}}
+ * The fragment without the #.
+ * @type {string}
+ * @private
  */
-ol.render.canvas.TextState;
+goog.Uri.prototype.fragment_ = '';
 
 
 /**
- * @const
- * @type {string}
+ * Whether or not this Uri should be treated as Read Only.
+ * @type {boolean}
+ * @private
  */
-ol.render.canvas.defaultFont = '10px sans-serif';
+goog.Uri.prototype.isReadOnly_ = false;
 
 
 /**
- * @const
- * @type {ol.Color}
+ * Whether or not to ignore case when comparing query params.
+ * @type {boolean}
+ * @private
  */
-ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
+goog.Uri.prototype.ignoreCase_ = false;
 
 
 /**
- * @const
- * @type {string}
+ * @return {string} The string form of the url.
+ * @override
  */
-ol.render.canvas.defaultLineCap = 'round';
+goog.Uri.prototype.toString = function() {
+  var out = [];
 
+  var scheme = this.getScheme();
+  if (scheme) {
+    out.push(goog.Uri.encodeSpecialChars_(
+        scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), ':');
+  }
 
-/**
- * @const
- * @type {Array.<number>}
- */
-ol.render.canvas.defaultLineDash = [];
+  var domain = this.getDomain();
+  if (domain) {
+    out.push('//');
 
+    var userInfo = this.getUserInfo();
+    if (userInfo) {
+      out.push(goog.Uri.encodeSpecialChars_(
+          userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), '@');
+    }
 
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultLineJoin = 'round';
+    out.push(goog.Uri.removeDoubleEncoding_(goog.string.urlEncode(domain)));
 
+    var port = this.getPort();
+    if (port != null) {
+      out.push(':', String(port));
+    }
+  }
 
-/**
- * @const
- * @type {number}
- */
-ol.render.canvas.defaultMiterLimit = 10;
+  var path = this.getPath();
+  if (path) {
+    if (this.hasDomain() && path.charAt(0) != '/') {
+      out.push('/');
+    }
+    out.push(goog.Uri.encodeSpecialChars_(
+        path,
+        path.charAt(0) == '/' ?
+            goog.Uri.reDisallowedInAbsolutePath_ :
+            goog.Uri.reDisallowedInRelativePath_,
+        true));
+  }
 
+  var query = this.getEncodedQuery();
+  if (query) {
+    out.push('?', query);
+  }
 
-/**
- * @const
- * @type {ol.Color}
- */
-ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
+  var fragment = this.getFragment();
+  if (fragment) {
+    out.push('#', goog.Uri.encodeSpecialChars_(
+        fragment, goog.Uri.reDisallowedInFragment_));
+  }
+  return out.join('');
+};
 
 
 /**
- * @const
- * @type {string}
+ * Resolves the given relative URI (a goog.Uri object), using the URI
+ * represented by this instance as the base URI.
+ *
+ * There are several kinds of relative URIs:<br>
+ * 1. foo - replaces the last part of the path, the whole query and fragment<br>
+ * 2. /foo - replaces the the path, the query and fragment<br>
+ * 3. //foo - replaces everything from the domain on.  foo is a domain name<br>
+ * 4. ?foo - replace the query and fragment<br>
+ * 5. #foo - replace the fragment only
+ *
+ * Additionally, if relative URI has a non-empty path, all ".." and "."
+ * segments will be resolved, as described in RFC 3986.
+ *
+ * @param {!goog.Uri} relativeUri The relative URI to resolve.
+ * @return {!goog.Uri} The resolved URI.
  */
-ol.render.canvas.defaultTextAlign = 'center';
+goog.Uri.prototype.resolve = function(relativeUri) {
 
+  var absoluteUri = this.clone();
 
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultTextBaseline = 'middle';
+  // we satisfy these conditions by looking for the first part of relativeUri
+  // that is not blank and applying defaults to the rest
 
+  var overridden = relativeUri.hasScheme();
 
-/**
- * @const
- * @type {number}
- */
-ol.render.canvas.defaultLineWidth = 1;
+  if (overridden) {
+    absoluteUri.setScheme(relativeUri.getScheme());
+  } else {
+    overridden = relativeUri.hasUserInfo();
+  }
 
-goog.provide('ol.style.Fill');
+  if (overridden) {
+    absoluteUri.setUserInfo(relativeUri.getUserInfo());
+  } else {
+    overridden = relativeUri.hasDomain();
+  }
 
-goog.require('ol.color');
+  if (overridden) {
+    absoluteUri.setDomain(relativeUri.getDomain());
+  } else {
+    overridden = relativeUri.hasPort();
+  }
 
+  var path = relativeUri.getPath();
+  if (overridden) {
+    absoluteUri.setPort(relativeUri.getPort());
+  } else {
+    overridden = relativeUri.hasPath();
+    if (overridden) {
+      // resolve path properly
+      if (path.charAt(0) != '/') {
+        // path is relative
+        if (this.hasDomain() && !this.hasPath()) {
+          // RFC 3986, section 5.2.3, case 1
+          path = '/' + path;
+        } else {
+          // RFC 3986, section 5.2.3, case 2
+          var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/');
+          if (lastSlashIndex != -1) {
+            path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path;
+          }
+        }
+      }
+      path = goog.Uri.removeDotSegments(path);
+    }
+  }
 
+  if (overridden) {
+    absoluteUri.setPath(path);
+  } else {
+    overridden = relativeUri.hasQuery();
+  }
 
-/**
- * @classdesc
- * Set fill style for vector features.
- *
- * @constructor
- * @param {olx.style.FillOptions=} opt_options Options.
- * @api
- */
-ol.style.Fill = function(opt_options) {
+  if (overridden) {
+    absoluteUri.setQueryData(relativeUri.getDecodedQuery());
+  } else {
+    overridden = relativeUri.hasFragment();
+  }
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  if (overridden) {
+    absoluteUri.setFragment(relativeUri.getFragment());
+  }
 
-  /**
-   * @private
-   * @type {ol.Color|string}
-   */
-  this.color_ = goog.isDef(options.color) ? options.color : null;
+  return absoluteUri;
 };
 
 
 /**
- * @return {ol.Color|string} Color.
- * @api
+ * Clones the URI instance.
+ * @return {!goog.Uri} New instance of the URI object.
  */
-ol.style.Fill.prototype.getColor = function() {
-  return this.color_;
+goog.Uri.prototype.clone = function() {
+  return new goog.Uri(this);
 };
 
-goog.provide('ol.style.Image');
-goog.provide('ol.style.ImageState');
-
 
 /**
- * @enum {number}
+ * @return {string} The encoded scheme/protocol for the URI.
  */
-ol.style.ImageState = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3
+goog.Uri.prototype.getScheme = function() {
+  return this.scheme_;
 };
 
 
 /**
- * @typedef {{opacity: number,
- *            rotateWithView: boolean,
- *            rotation: number,
- *            scale: number,
- *            snapToPixel: boolean}}
+ * Sets the scheme/protocol.
+ * @param {string} newScheme New scheme value.
+ * @param {boolean=} opt_decode Optional param for whether to decode new value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.ImageOptions;
+goog.Uri.prototype.setScheme = function(newScheme, opt_decode) {
+  this.enforceReadOnly();
+  this.scheme_ = opt_decode ? goog.Uri.decodeOrEmpty_(newScheme, true) :
+      newScheme;
 
+  // remove an : at the end of the scheme so somebody can pass in
+  // window.location.protocol
+  if (this.scheme_) {
+    this.scheme_ = this.scheme_.replace(/:$/, '');
+  }
+  return this;
+};
 
 
 /**
- * @classdesc
- * Set image style for vector features.
- *
- * @constructor
- * @param {ol.style.ImageOptions} options Options.
+ * @return {boolean} Whether the scheme has been set.
  */
-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;
-
+goog.Uri.prototype.hasScheme = function() {
+  return !!this.scheme_;
 };
 
 
 /**
- * @return {number} Opacity.
+ * @return {string} The decoded user info.
  */
-ol.style.Image.prototype.getOpacity = function() {
-  return this.opacity_;
+goog.Uri.prototype.getUserInfo = function() {
+  return this.userInfo_;
 };
 
 
 /**
- * @return {boolean} Rotate with map.
+ * Sets the userInfo.
+ * @param {string} newUserInfo New userInfo value.
+ * @param {boolean=} opt_decode Optional param for whether to decode new value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Image.prototype.getRotateWithView = function() {
-  return this.rotateWithView_;
+goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) {
+  this.enforceReadOnly();
+  this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) :
+                   newUserInfo;
+  return this;
 };
 
 
 /**
- * @return {number} Rotation.
- * @api
+ * @return {boolean} Whether the user info has been set.
  */
-ol.style.Image.prototype.getRotation = function() {
-  return this.rotation_;
+goog.Uri.prototype.hasUserInfo = function() {
+  return !!this.userInfo_;
 };
 
 
 /**
- * @return {number} Scale.
- * @api
+ * @return {string} The decoded domain.
  */
-ol.style.Image.prototype.getScale = function() {
-  return this.scale_;
+goog.Uri.prototype.getDomain = function() {
+  return this.domain_;
 };
 
 
 /**
- * @return {boolean} Snap to pixel?
+ * Sets the domain.
+ * @param {string} newDomain New domain value.
+ * @param {boolean=} opt_decode Optional param for whether to decode new value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Image.prototype.getSnapToPixel = function() {
-  return this.snapToPixel_;
+goog.Uri.prototype.setDomain = function(newDomain, opt_decode) {
+  this.enforceReadOnly();
+  this.domain_ = opt_decode ? goog.Uri.decodeOrEmpty_(newDomain, true) :
+      newDomain;
+  return this;
 };
 
 
 /**
- * @function
- * @return {Array.<number>} Anchor.
+ * @return {boolean} Whether the domain has been set.
  */
-ol.style.Image.prototype.getAnchor = goog.abstractMethod;
+goog.Uri.prototype.hasDomain = function() {
+  return !!this.domain_;
+};
 
 
 /**
- * @function
- * @param {number} pixelRatio Pixel ratio.
- * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
+ * @return {?number} The port number.
  */
-ol.style.Image.prototype.getImage = goog.abstractMethod;
+goog.Uri.prototype.getPort = function() {
+  return this.port_;
+};
 
 
 /**
- * @return {ol.style.ImageState} Image state.
+ * Sets the port number.
+ * @param {*} newPort Port number. Will be explicitly casted to a number.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Image.prototype.getImageState = goog.abstractMethod;
+goog.Uri.prototype.setPort = function(newPort) {
+  this.enforceReadOnly();
 
+  if (newPort) {
+    newPort = Number(newPort);
+    if (isNaN(newPort) || newPort < 0) {
+      throw Error('Bad port number ' + newPort);
+    }
+    this.port_ = newPort;
+  } else {
+    this.port_ = null;
+  }
 
-/**
- * @param {number} pixelRatio Pixel ratio.
- * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
- */
-ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;
+  return this;
+};
 
 
 /**
- * @function
- * @return {Array.<number>} Origin.
+ * @return {boolean} Whether the port has been set.
  */
-ol.style.Image.prototype.getOrigin = goog.abstractMethod;
+goog.Uri.prototype.hasPort = function() {
+  return this.port_ != null;
+};
 
 
 /**
- * @function
- * @return {ol.Size} Size.
+  * @return {string} The decoded path.
  */
-ol.style.Image.prototype.getSize = goog.abstractMethod;
+goog.Uri.prototype.getPath = function() {
+  return this.path_;
+};
 
 
 /**
- * @param {function(this: T, goog.events.Event)} listener Listener function.
- * @param {T} thisArg Value to use as `this` when executing `listener`.
- * @return {goog.events.Key|undefined} Listener key.
- * @template T
+ * Sets the path.
+ * @param {string} newPath New path value.
+ * @param {boolean=} opt_decode Optional param for whether to decode new value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Image.prototype.listenImageChange = goog.abstractMethod;
+goog.Uri.prototype.setPath = function(newPath, opt_decode) {
+  this.enforceReadOnly();
+  this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath;
+  return this;
+};
 
 
 /**
- * Load not yet loaded URI.
+ * @return {boolean} Whether the path has been set.
  */
-ol.style.Image.prototype.load = goog.abstractMethod;
+goog.Uri.prototype.hasPath = function() {
+  return !!this.path_;
+};
 
 
 /**
- * @param {function(this: T, goog.events.Event)} listener Listener function.
- * @param {T} thisArg Value to use as `this` when executing `listener`.
- * @template T
+ * @return {boolean} Whether the query string has been set.
  */
-ol.style.Image.prototype.unlistenImageChange = goog.abstractMethod;
-
-goog.provide('ol.style.Stroke');
-
-goog.require('ol.color');
-
+goog.Uri.prototype.hasQuery = function() {
+  return this.queryData_.toString() !== '';
+};
 
 
 /**
- * @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
+ * Sets the query data.
+ * @param {goog.Uri.QueryData|string|undefined} queryData QueryData object.
+ * @param {boolean=} opt_decode Optional param for whether to decode new value.
+ *     Applies only if queryData is a string.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Stroke = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.Color|string}
-   */
-  this.color_ = goog.isDef(options.color) ? options.color : null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.lineCap_ = options.lineCap;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.lineDash_ = goog.isDef(options.lineDash) ? options.lineDash : null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.lineJoin_ = options.lineJoin;
+goog.Uri.prototype.setQueryData = function(queryData, opt_decode) {
+  this.enforceReadOnly();
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.miterLimit_ = options.miterLimit;
+  if (queryData instanceof goog.Uri.QueryData) {
+    this.queryData_ = queryData;
+    this.queryData_.setIgnoreCase(this.ignoreCase_);
+  } else {
+    if (!opt_decode) {
+      // QueryData accepts encoded query string, so encode it if
+      // opt_decode flag is not true.
+      queryData = goog.Uri.encodeSpecialChars_(queryData,
+                                               goog.Uri.reDisallowedInQuery_);
+    }
+    this.queryData_ = new goog.Uri.QueryData(queryData, null, this.ignoreCase_);
+  }
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.width_ = options.width;
+  return this;
 };
 
 
 /**
- * @return {ol.Color|string} Color.
- * @api
+ * Sets the URI query.
+ * @param {string} newQuery New query value.
+ * @param {boolean=} opt_decode Optional param for whether to decode new value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Stroke.prototype.getColor = function() {
-  return this.color_;
+goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {
+  return this.setQueryData(newQuery, opt_decode);
 };
 
 
 /**
- * @return {string|undefined} Line cap.
- * @api
+ * @return {string} The encoded URI query, not including the ?.
  */
-ol.style.Stroke.prototype.getLineCap = function() {
-  return this.lineCap_;
+goog.Uri.prototype.getEncodedQuery = function() {
+  return this.queryData_.toString();
 };
 
 
 /**
- * @return {Array.<number>} Line dash.
- * @api
+ * @return {string} The decoded URI query, not including the ?.
  */
-ol.style.Stroke.prototype.getLineDash = function() {
-  return this.lineDash_;
+goog.Uri.prototype.getDecodedQuery = function() {
+  return this.queryData_.toDecodedString();
 };
 
 
 /**
- * @return {string|undefined} Line join.
- * @api
+ * Returns the query data.
+ * @return {!goog.Uri.QueryData} QueryData object.
  */
-ol.style.Stroke.prototype.getLineJoin = function() {
-  return this.lineJoin_;
+goog.Uri.prototype.getQueryData = function() {
+  return this.queryData_;
 };
 
 
 /**
- * @return {number|undefined} Miter limit.
- * @api
+ * @return {string} The encoded URI query, not including the ?.
+ *
+ * Warning: This method, unlike other getter methods, returns encoded
+ * value, instead of decoded one.
  */
-ol.style.Stroke.prototype.getMiterLimit = function() {
-  return this.miterLimit_;
+goog.Uri.prototype.getQuery = function() {
+  return this.getEncodedQuery();
 };
 
 
 /**
- * @return {number|undefined} Width.
- * @api
+ * Sets the value of the named query parameters, clearing previous values for
+ * that key.
+ *
+ * @param {string} key The parameter to set.
+ * @param {*} value The new value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Stroke.prototype.getWidth = function() {
-  return this.width_;
+goog.Uri.prototype.setParameterValue = function(key, value) {
+  this.enforceReadOnly();
+  this.queryData_.set(key, value);
+  return this;
 };
 
-goog.provide('ol.style.Circle');
-
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('ol.color');
-goog.require('ol.render.canvas');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Image');
-goog.require('ol.style.ImageState');
-goog.require('ol.style.Stroke');
-
-
 
 /**
- * @classdesc
- * Set circle style for vector features.
+ * Sets the values of the named query parameters, clearing previous values for
+ * that key.  Not new values will currently be moved to the end of the query
+ * string.
  *
- * @constructor
- * @param {olx.style.CircleOptions=} opt_options Options.
- * @extends {ol.style.Image}
- * @api
+ * So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new'])
+ * </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p>
+ *
+ * @param {string} key The parameter to set.
+ * @param {*} values The new values. If values is a single
+ *     string then it will be treated as the sole value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Circle = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = /** @type {HTMLCanvasElement} */
-      (goog.dom.createElement(goog.dom.TagName.CANVAS));
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.hitDetectionCanvas_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.Fill}
-   */
-  this.fill_ = goog.isDef(options.fill) ? options.fill : null;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.origin_ = [0, 0];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.radius_ = options.radius;
-
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
-
-  var size = this.render_();
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.anchor_ = [size / 2, size / 2];
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.size_ = [size, size];
+goog.Uri.prototype.setParameterValues = function(key, values) {
+  this.enforceReadOnly();
 
-  /**
-   * @type {boolean}
-   */
-  var snapToPixel = goog.isDef(options.snapToPixel) ?
-      options.snapToPixel : true;
+  if (!goog.isArray(values)) {
+    values = [String(values)];
+  }
 
-  goog.base(this, {
-    opacity: 1,
-    rotateWithView: false,
-    rotation: 0,
-    scale: 1,
-    snapToPixel: snapToPixel
-  });
+  this.queryData_.setValues(key, values);
 
+  return this;
 };
-goog.inherits(ol.style.Circle, ol.style.Image);
 
 
 /**
- * @inheritDoc
- * @api
+ * Returns the value<b>s</b> for a given cgi parameter as a list of decoded
+ * query parameter values.
+ * @param {string} name The parameter to get values for.
+ * @return {!Array<?>} The values for a given cgi parameter as a list of
+ *     decoded query parameter values.
  */
-ol.style.Circle.prototype.getAnchor = function() {
-  return this.anchor_;
+goog.Uri.prototype.getParameterValues = function(name) {
+  return this.queryData_.getValues(name);
 };
 
 
 /**
- * @return {ol.style.Fill} Fill style.
- * @api
+ * Returns the first value for a given cgi parameter or undefined if the given
+ * parameter name does not appear in the query string.
+ * @param {string} paramName Unescaped parameter name.
+ * @return {string|undefined} The first value for a given cgi parameter or
+ *     undefined if the given parameter name does not appear in the query
+ *     string.
  */
-ol.style.Circle.prototype.getFill = function() {
-  return this.fill_;
+goog.Uri.prototype.getParameterValue = function(paramName) {
+  // NOTE(nicksantos): This type-cast is a lie when
+  // preserveParameterTypesCompatibilityFlag is set to true.
+  // But this should only be set to true in tests.
+  return /** @type {string|undefined} */ (this.queryData_.get(paramName));
 };
 
 
 /**
- * @inheritDoc
+ * @return {string} The URI fragment, not including the #.
  */
-ol.style.Circle.prototype.getHitDetectionImage = function(pixelRatio) {
-  return this.hitDetectionCanvas_;
+goog.Uri.prototype.getFragment = function() {
+  return this.fragment_;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * Sets the URI fragment.
+ * @param {string} newFragment New fragment value.
+ * @param {boolean=} opt_decode Optional param for whether to decode new value.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Circle.prototype.getImage = function(pixelRatio) {
-  return this.canvas_;
+goog.Uri.prototype.setFragment = function(newFragment, opt_decode) {
+  this.enforceReadOnly();
+  this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) :
+                   newFragment;
+  return this;
 };
 
 
 /**
- * @inheritDoc
+ * @return {boolean} Whether the URI has a fragment set.
  */
-ol.style.Circle.prototype.getImageState = function() {
-  return ol.style.ImageState.LOADED;
+goog.Uri.prototype.hasFragment = function() {
+  return !!this.fragment_;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * Returns true if this has the same domain as that of uri2.
+ * @param {!goog.Uri} uri2 The URI object to compare to.
+ * @return {boolean} true if same domain; false otherwise.
  */
-ol.style.Circle.prototype.getOrigin = function() {
-  return this.origin_;
+goog.Uri.prototype.hasSameDomainAs = function(uri2) {
+  return ((!this.hasDomain() && !uri2.hasDomain()) ||
+          this.getDomain() == uri2.getDomain()) &&
+      ((!this.hasPort() && !uri2.hasPort()) ||
+          this.getPort() == uri2.getPort());
 };
 
 
 /**
- * @return {number} Radius.
- * @api
+ * Adds a random parameter to the Uri.
+ * @return {!goog.Uri} Reference to this Uri object.
  */
-ol.style.Circle.prototype.getRadius = function() {
-  return this.radius_;
+goog.Uri.prototype.makeUnique = function() {
+  this.enforceReadOnly();
+  this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString());
+
+  return this;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * Removes the named query parameter.
+ *
+ * @param {string} key The parameter to remove.
+ * @return {!goog.Uri} Reference to this URI object.
  */
-ol.style.Circle.prototype.getSize = function() {
-  return this.size_;
+goog.Uri.prototype.removeParameter = function(key) {
+  this.enforceReadOnly();
+  this.queryData_.remove(key);
+  return this;
 };
 
 
 /**
- * @return {ol.style.Stroke} Stroke style.
- * @api
+ * Sets whether Uri is read only. If this goog.Uri is read-only,
+ * enforceReadOnly_ will be called at the start of any function that may modify
+ * this Uri.
+ * @param {boolean} isReadOnly whether this goog.Uri should be read only.
+ * @return {!goog.Uri} Reference to this Uri object.
  */
-ol.style.Circle.prototype.getStroke = function() {
-  return this.stroke_;
+goog.Uri.prototype.setReadOnly = function(isReadOnly) {
+  this.isReadOnly_ = isReadOnly;
+  return this;
 };
 
 
 /**
- * @inheritDoc
+ * @return {boolean} Whether the URI is read only.
  */
-ol.style.Circle.prototype.listenImageChange = goog.nullFunction;
+goog.Uri.prototype.isReadOnly = function() {
+  return this.isReadOnly_;
+};
 
 
 /**
- * @inheritDoc
+ * Checks if this Uri has been marked as read only, and if so, throws an error.
+ * This should be called whenever any modifying function is called.
  */
-ol.style.Circle.prototype.load = goog.nullFunction;
+goog.Uri.prototype.enforceReadOnly = function() {
+  if (this.isReadOnly_) {
+    throw Error('Tried to modify a read-only Uri');
+  }
+};
 
 
 /**
- * @inheritDoc
+ * Sets whether to ignore case.
+ * NOTE: If there are already key/value pairs in the QueryData, and
+ * ignoreCase_ is set to false, the keys will all be lower-cased.
+ * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
+ * @return {!goog.Uri} Reference to this Uri object.
  */
-ol.style.Circle.prototype.unlistenImageChange = goog.nullFunction;
+goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {
+  this.ignoreCase_ = ignoreCase;
+  if (this.queryData_) {
+    this.queryData_.setIgnoreCase(ignoreCase);
+  }
+  return this;
+};
 
 
 /**
- * @private
- * @return {number} Size.
+ * @return {boolean} Whether to ignore case.
  */
-ol.style.Circle.prototype.render_ = function() {
-  var canvas = this.canvas_;
-  var strokeStyle, strokeWidth;
+goog.Uri.prototype.getIgnoreCase = function() {
+  return this.ignoreCase_;
+};
 
-  if (goog.isNull(this.stroke_)) {
-    strokeWidth = 0;
-  } else {
-    strokeStyle = ol.color.asString(this.stroke_.getColor());
-    strokeWidth = this.stroke_.getWidth();
-    if (!goog.isDef(strokeWidth)) {
-      strokeWidth = ol.render.canvas.defaultLineWidth;
-    }
-  }
 
-  var size = 2 * (this.radius_ + strokeWidth) + 1;
+//==============================================================================
+// Static members
+//==============================================================================
 
-  // draw the circle on the canvas
 
-  canvas.height = size;
-  canvas.width = size;
+/**
+ * Creates a uri from the string form.  Basically an alias of new goog.Uri().
+ * If a Uri object is passed to parse then it will return a clone of the object.
+ *
+ * @param {*} uri Raw URI string or instance of Uri
+ *     object.
+ * @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter
+ * names in #getParameterValue.
+ * @return {!goog.Uri} The new URI object.
+ */
+goog.Uri.parse = function(uri, opt_ignoreCase) {
+  return uri instanceof goog.Uri ?
+         uri.clone() : new goog.Uri(uri, opt_ignoreCase);
+};
 
-  // canvas.width and height are rounded to the closest integer
-  size = canvas.width;
 
-  var context = /** @type {CanvasRenderingContext2D} */
-      (canvas.getContext('2d'));
-  context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true);
+/**
+ * Creates a new goog.Uri object from unencoded parts.
+ *
+ * @param {?string=} opt_scheme Scheme/protocol or full URI to parse.
+ * @param {?string=} opt_userInfo username:password.
+ * @param {?string=} opt_domain www.google.com.
+ * @param {?number=} opt_port 9830.
+ * @param {?string=} opt_path /some/path/to/a/file.html.
+ * @param {string|goog.Uri.QueryData=} opt_query a=1&b=2.
+ * @param {?string=} opt_fragment The fragment without the #.
+ * @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in
+ *     #getParameterValue.
+ *
+ * @return {!goog.Uri} The new URI object.
+ */
+goog.Uri.create = function(opt_scheme, opt_userInfo, opt_domain, opt_port,
+                           opt_path, opt_query, opt_fragment, opt_ignoreCase) {
 
-  if (!goog.isNull(this.fill_)) {
-    context.fillStyle = ol.color.asString(this.fill_.getColor());
-    context.fill();
-  }
-  if (!goog.isNull(this.stroke_)) {
-    context.strokeStyle = strokeStyle;
-    context.lineWidth = strokeWidth;
-    context.stroke();
-  }
+  var uri = new goog.Uri(null, opt_ignoreCase);
 
-  // deal with the hit detection canvas
+  // Only set the parts if they are defined and not empty strings.
+  opt_scheme && uri.setScheme(opt_scheme);
+  opt_userInfo && uri.setUserInfo(opt_userInfo);
+  opt_domain && uri.setDomain(opt_domain);
+  opt_port && uri.setPort(opt_port);
+  opt_path && uri.setPath(opt_path);
+  opt_query && uri.setQueryData(opt_query);
+  opt_fragment && uri.setFragment(opt_fragment);
 
-  if (!goog.isNull(this.fill_)) {
-    this.hitDetectionCanvas_ = canvas;
-  } else {
-    this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
-        (goog.dom.createElement(goog.dom.TagName.CANVAS));
-    canvas = this.hitDetectionCanvas_;
+  return uri;
+};
 
-    canvas.height = size;
-    canvas.width = size;
 
-    context = /** @type {CanvasRenderingContext2D} */
-        (canvas.getContext('2d'));
-    context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true);
+/**
+ * Resolves a relative Uri against a base Uri, accepting both strings and
+ * Uri objects.
+ *
+ * @param {*} base Base Uri.
+ * @param {*} rel Relative Uri.
+ * @return {!goog.Uri} Resolved uri.
+ */
+goog.Uri.resolve = function(base, rel) {
+  if (!(base instanceof goog.Uri)) {
+    base = goog.Uri.parse(base);
+  }
 
-    context.fillStyle = ol.render.canvas.defaultFillStyle;
-    context.fill();
-    if (!goog.isNull(this.stroke_)) {
-      context.strokeStyle = strokeStyle;
-      context.lineWidth = strokeWidth;
-      context.stroke();
-    }
+  if (!(rel instanceof goog.Uri)) {
+    rel = goog.Uri.parse(rel);
   }
 
-  return size;
+  return base.resolve(rel);
 };
 
-goog.provide('ol.style.Style');
-
-goog.require('goog.asserts');
-goog.require('goog.functions');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.style.Circle');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Image');
-goog.require('ol.style.Stroke');
-
-
 
 /**
- * @classdesc
- * Base class for vector feature rendering styles.
+ * Removes dot segments in given path component, as described in
+ * RFC 3986, section 5.2.4.
  *
- * @constructor
- * @param {olx.style.StyleOptions=} opt_options Style options.
- * @api
+ * @param {string} path A non-empty path component.
+ * @return {string} Path component with removed dot segments.
  */
-ol.style.Style = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.style.Fill}
-   */
-  this.fill_ = goog.isDef(options.fill) ? options.fill : null;
+goog.Uri.removeDotSegments = function(path) {
+  if (path == '..' || path == '.') {
+    return '';
 
-  /**
-   * @private
-   * @type {ol.style.Image}
-   */
-  this.image_ = goog.isDef(options.image) ? options.image : null;
+  } else if (!goog.string.contains(path, './') &&
+             !goog.string.contains(path, '/.')) {
+    // This optimization detects uris which do not contain dot-segments,
+    // and as a consequence do not require any processing.
+    return path;
 
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
+  } else {
+    var leadingSlash = goog.string.startsWith(path, '/');
+    var segments = path.split('/');
+    var out = [];
 
-  /**
-   * @private
-   * @type {ol.style.Text}
-   */
-  this.text_ = goog.isDef(options.text) ? options.text : null;
+    for (var pos = 0; pos < segments.length; ) {
+      var segment = segments[pos++];
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.zIndex_ = options.zIndex;
+      if (segment == '.') {
+        if (leadingSlash && pos == segments.length) {
+          out.push('');
+        }
+      } else if (segment == '..') {
+        if (out.length > 1 || out.length == 1 && out[0] != '') {
+          out.pop();
+        }
+        if (leadingSlash && pos == segments.length) {
+          out.push('');
+        }
+      } else {
+        out.push(segment);
+        leadingSlash = true;
+      }
+    }
 
+    return out.join('/');
+  }
 };
 
 
 /**
- * @return {ol.style.Fill} Fill style.
- * @api
+ * Decodes a value or returns the empty string if it isn't defined or empty.
+ * @param {string|undefined} val Value to decode.
+ * @param {boolean=} opt_preserveReserved If true, restricted characters will
+ *     not be decoded.
+ * @return {string} Decoded value.
+ * @private
  */
-ol.style.Style.prototype.getFill = function() {
-  return this.fill_;
+goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) {
+  // Don't use UrlDecode() here because val is not a query parameter.
+  if (!val) {
+    return '';
+  }
+
+  return opt_preserveReserved ? decodeURI(val) : decodeURIComponent(val);
 };
 
 
 /**
- * @return {ol.style.Image} Image style.
- * @api
+ * If unescapedPart is non null, then escapes any characters in it that aren't
+ * valid characters in a url and also escapes any special characters that
+ * appear in extra.
+ *
+ * @param {*} unescapedPart The string to encode.
+ * @param {RegExp} extra A character set of characters in [\01-\177].
+ * @param {boolean=} opt_removeDoubleEncoding If true, remove double percent
+ *     encoding.
+ * @return {?string} null iff unescapedPart == null.
+ * @private
  */
-ol.style.Style.prototype.getImage = function() {
-  return this.image_;
+goog.Uri.encodeSpecialChars_ = function(unescapedPart, extra,
+    opt_removeDoubleEncoding) {
+  if (goog.isString(unescapedPart)) {
+    var encoded = encodeURI(unescapedPart).
+        replace(extra, goog.Uri.encodeChar_);
+    if (opt_removeDoubleEncoding) {
+      // encodeURI double-escapes %XX sequences used to represent restricted
+      // characters in some URI components, remove the double escaping here.
+      encoded = goog.Uri.removeDoubleEncoding_(encoded);
+    }
+    return encoded;
+  }
+  return null;
 };
 
 
 /**
- * @return {ol.style.Stroke} Stroke style.
- * @api
+ * Converts a character in [\01-\177] to its unicode character equivalent.
+ * @param {string} ch One character string.
+ * @return {string} Encoded string.
+ * @private
  */
-ol.style.Style.prototype.getStroke = function() {
-  return this.stroke_;
+goog.Uri.encodeChar_ = function(ch) {
+  var n = ch.charCodeAt(0);
+  return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);
 };
 
 
 /**
- * @return {ol.style.Text} Text style.
- * @api
+ * Removes double percent-encoding from a string.
+ * @param  {string} doubleEncodedString String
+ * @return {string} String with double encoding removed.
+ * @private
  */
-ol.style.Style.prototype.getText = function() {
-  return this.text_;
+goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) {
+  return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1');
 };
 
 
 /**
- * @return {number|undefined} ZIndex.
- * @api
+ * Regular expression for characters that are disallowed in the scheme or
+ * userInfo part of the URI.
+ * @type {RegExp}
+ * @private
  */
-ol.style.Style.prototype.getZIndex = function() {
-  return this.zIndex_;
-};
+goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g;
 
 
 /**
- * A function that takes an {@link ol.Feature} and a `{number}` representing
- * the view's resolution. The function should return an array of
- * {@link ol.style.Style}. This way e.g. a vector layer can be styled.
- *
- * @typedef {function(ol.Feature, number): Array.<ol.style.Style>}
- * @api
+ * Regular expression for characters that are disallowed in a relative path.
+ * Colon is included due to RFC 3986 3.3.
+ * @type {RegExp}
+ * @private
  */
-ol.style.StyleFunction;
+goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g;
 
 
 /**
- * Convert the provided object into a style function.  Functions passed through
- * unchanged.  Arrays of ol.style.Style or single style objects wrapped in a
- * new style function.
- * @param {ol.style.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj
- *     A style function, a single style, or an array of styles.
- * @return {ol.style.StyleFunction} A style function.
+ * Regular expression for characters that are disallowed in an absolute path.
+ * @type {RegExp}
+ * @private
  */
-ol.style.createStyleFunction = function(obj) {
-  /**
-   * @type {ol.style.StyleFunction}
-   */
-  var styleFunction;
-
-  if (goog.isFunction(obj)) {
-    styleFunction = /** @type {ol.style.StyleFunction} */ (obj);
-  } else {
-    /**
-     * @type {Array.<ol.style.Style>}
-     */
-    var styles;
-    if (goog.isArray(obj)) {
-      styles = obj;
-    } else {
-      goog.asserts.assertInstanceof(obj, ol.style.Style);
-      styles = [obj];
-    }
-    styleFunction = goog.functions.constant(styles);
-  }
-  return styleFunction;
-};
+goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @return {Array.<ol.style.Style>} Style.
+ * Regular expression for characters that are disallowed in the query.
+ * @type {RegExp}
+ * @private
  */
-ol.style.defaultStyleFunction = function(feature, resolution) {
-  var fill = new ol.style.Fill({
-    color: 'rgba(255,255,255,0.4)'
-  });
-  var stroke = new ol.style.Stroke({
-    color: '#3399CC',
-    width: 1.25
-  });
-  var styles = [
-    new ol.style.Style({
-      image: new ol.style.Circle({
-        fill: fill,
-        stroke: stroke,
-        radius: 5
-      }),
-      fill: fill,
-      stroke: stroke
-    })
-  ];
-
-  // Now that we've run it the first time, replace the function with
-  // a constant version. 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.)
-
-  /**
-   * @param {ol.Feature} feature Feature.
-   * @param {number} resolution Resolution.
-   * @return {Array.<ol.style.Style>} Style.
-   */
-  ol.style.defaultStyleFunction = function(feature, resolution) {
-    return styles;
-  };
-
-  return styles;
-};
+goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;
 
 
 /**
- * Default styles for editing features.
- * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles
+ * Regular expression for characters that are disallowed in the fragment.
+ * @type {RegExp}
+ * @private
  */
-ol.style.createDefaultEditingStyles = function() {
-  /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */
-  var styles = {};
-  var white = [255, 255, 255, 1];
-  var blue = [0, 153, 255, 1];
-  var width = 3;
-  styles[ol.geom.GeometryType.POLYGON] = [
-    new ol.style.Style({
-      fill: new ol.style.Fill({
-        color: [255, 255, 255, 0.5]
-      })
-    })
-  ];
-  styles[ol.geom.GeometryType.MULTI_POLYGON] =
-      styles[ol.geom.GeometryType.POLYGON];
-
-  styles[ol.geom.GeometryType.LINE_STRING] = [
-    new ol.style.Style({
-      stroke: new ol.style.Stroke({
-        color: white,
-        width: width + 2
-      })
-    }),
-    new ol.style.Style({
-      stroke: new ol.style.Stroke({
-        color: blue,
-        width: width
-      })
-    })
-  ];
-  styles[ol.geom.GeometryType.MULTI_LINE_STRING] =
-      styles[ol.geom.GeometryType.LINE_STRING];
-
-  styles[ol.geom.GeometryType.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];
+goog.Uri.reDisallowedInFragment_ = /#/g;
 
-  styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] =
-      styles[ol.geom.GeometryType.POLYGON].concat(
-          styles[ol.geom.GeometryType.POINT]
-      );
 
-  return styles;
+/**
+ * Checks whether two URIs have the same domain.
+ * @param {string} uri1String First URI string.
+ * @param {string} uri2String Second URI string.
+ * @return {boolean} true if the two URIs have the same domain; false otherwise.
+ */
+goog.Uri.haveSameDomain = function(uri1String, uri2String) {
+  // Differs from goog.uri.utils.haveSameDomain, since this ignores scheme.
+  // TODO(gboyer): Have this just call goog.uri.util.haveSameDomain.
+  var pieces1 = goog.uri.utils.split(uri1String);
+  var pieces2 = goog.uri.utils.split(uri2String);
+  return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
+             pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
+         pieces1[goog.uri.utils.ComponentIndex.PORT] ==
+             pieces2[goog.uri.utils.ComponentIndex.PORT];
 };
 
-goog.provide('ol.Feature');
-goog.provide('ol.feature');
-
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.functions');
-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 or feature overlay.
- *
- * 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');
+ * Class used to represent URI query parameters.  It is essentially a hash of
+ * name-value pairs, though a name can be present more than once.
  *
- * // get the point geometry
- * var point = feature.getGeometry();
- * ```
+ * Has the same interface as the collections in goog.structs.
  *
+ * @param {?string=} opt_query Optional encoded query string to parse into
+ *     the object.
+ * @param {goog.Uri=} opt_uri Optional uri object that should have its
+ *     cache invalidated when this object updates. Deprecated -- this
+ *     is no longer required.
+ * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
+ *     name in #get.
  * @constructor
- * @extends {ol.Object}
- * @fires change Triggered when the geometry or style of the feature changes.
- * @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
+ * @final
  */
-ol.Feature = function(opt_geometryOrProperties) {
-
-  goog.base(this);
-
-  /**
-   * @private
-   * @type {number|string|undefined}
-   */
-  this.id_ = undefined;
-
-  /**
-   * @type {string}
-   * @private
-   */
-  this.geometryName_ = 'geometry';
-
+goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) {
   /**
-   * User provided style.
+   * Encoded query string, or null if it requires computing from the key map.
+   * @type {?string}
    * @private
-   * @type {ol.style.Style|Array.<ol.style.Style>|
-   *     ol.feature.FeatureStyleFunction}
    */
-  this.style_ = null;
+  this.encodedQuery_ = opt_query || null;
 
   /**
+   * If true, ignore the case of the parameter name in #get.
+   * @type {boolean}
    * @private
-   * @type {ol.feature.FeatureStyleFunction|undefined}
    */
-  this.styleFunction_ = undefined;
+  this.ignoreCase_ = !!opt_ignoreCase;
+};
 
-  /**
-   * @private
-   * @type {goog.events.Key}
-   */
-  this.geometryChangeKey_ = null;
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(this.geometryName_),
-      this.handleGeometryChanged_, false, this);
+/**
+ * If the underlying key map is not yet initialized, it parses the
+ * query string and fills the map with parsed data.
+ * @private
+ */
+goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {
+  if (!this.keyMap_) {
+    this.keyMap_ = new goog.structs.Map();
+    this.count_ = 0;
 
-  if (goog.isDef(opt_geometryOrProperties)) {
-    if (opt_geometryOrProperties instanceof ol.geom.Geometry ||
-        goog.isNull(opt_geometryOrProperties)) {
-      var geometry = /** @type {ol.geom.Geometry} */ (opt_geometryOrProperties);
-      this.setGeometry(geometry);
-    } else {
-      goog.asserts.assert(goog.isObject(opt_geometryOrProperties));
-      var properties = /** @type {Object.<string, *>} */
-          (opt_geometryOrProperties);
-      this.setProperties(properties);
+    if (this.encodedQuery_) {
+      var pairs = this.encodedQuery_.split('&');
+      for (var i = 0; i < pairs.length; i++) {
+        var indexOfEquals = pairs[i].indexOf('=');
+        var name = null;
+        var value = null;
+        if (indexOfEquals >= 0) {
+          name = pairs[i].substring(0, indexOfEquals);
+          value = pairs[i].substring(indexOfEquals + 1);
+        } else {
+          name = pairs[i];
+        }
+        name = goog.string.urlDecode(name);
+        name = this.getKeyName_(name);
+        this.add(name, value ? goog.string.urlDecode(value) : '');
+      }
     }
   }
 };
-goog.inherits(ol.Feature, ol.Object);
 
 
 /**
- * Clone this feature. If the original feature has a geometry it
- * is also cloned. The feature id is not set in the clone.
- * @return {ol.Feature} The clone.
- * @api stable
+ * Creates a new query data instance from a map of names and values.
+ *
+ * @param {!goog.structs.Map<string, ?>|!Object} map Map of string parameter
+ *     names to parameter value. If parameter value is an array, it is
+ *     treated as if the key maps to each individual value in the
+ *     array.
+ * @param {goog.Uri=} opt_uri URI object that should have its cache
+ *     invalidated when this object updates.
+ * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
+ *     name in #get.
+ * @return {!goog.Uri.QueryData} The populated query data instance.
  */
-ol.Feature.prototype.clone = function() {
-  var clone = new ol.Feature(this.getProperties());
-  clone.setGeometryName(this.getGeometryName());
-  var geometry = this.getGeometry();
-  if (goog.isDefAndNotNull(geometry)) {
-    clone.setGeometry(geometry.clone());
+goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) {
+  var keys = goog.structs.getKeys(map);
+  if (typeof keys == 'undefined') {
+    throw Error('Keys are undefined');
   }
-  var style = this.getStyle();
-  if (!goog.isNull(style)) {
-    clone.setStyle(style);
+
+  var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
+  var values = goog.structs.getValues(map);
+  for (var i = 0; i < keys.length; i++) {
+    var key = keys[i];
+    var value = values[i];
+    if (!goog.isArray(value)) {
+      queryData.add(key, value);
+    } else {
+      queryData.setValues(key, value);
+    }
   }
-  return clone;
+  return queryData;
 };
 
 
 /**
- * @return {ol.geom.Geometry|undefined} Returns the Geometry associated
- *     with this feature using the current geometry name property.  By
- *     default, this is `geometry` but it may be changed by calling
- *     `setGeometryName`.
- * @api stable
- * @observable
+ * Creates a new query data instance from parallel arrays of parameter names
+ * and values. Allows for duplicate parameter names. Throws an error if the
+ * lengths of the arrays differ.
+ *
+ * @param {!Array<string>} keys Parameter names.
+ * @param {!Array<?>} values Parameter values.
+ * @param {goog.Uri=} opt_uri URI object that should have its cache
+ *     invalidated when this object updates.
+ * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
+ *     name in #get.
+ * @return {!goog.Uri.QueryData} The populated query data instance.
  */
-ol.Feature.prototype.getGeometry = function() {
-  return /** @type {ol.geom.Geometry|undefined} */ (
-      this.get(this.geometryName_));
+goog.Uri.QueryData.createFromKeysValues = function(
+    keys, values, opt_uri, opt_ignoreCase) {
+  if (keys.length != values.length) {
+    throw Error('Mismatched lengths for keys/values');
+  }
+  var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
+  for (var i = 0; i < keys.length; i++) {
+    queryData.add(keys[i], values[i]);
+  }
+  return queryData;
 };
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getGeometry',
-    ol.Feature.prototype.getGeometry);
 
 
 /**
- * @return {number|string|undefined} Id.
- * @api stable
- */
-ol.Feature.prototype.getId = function() {
-  return this.id_;
-};
+ * The map containing name/value or name/array-of-values pairs.
+ * May be null if it requires parsing from the query string.
+ *
+ * We need to use a Map because we cannot guarantee that the key names will
+ * not be problematic for IE.
+ *
+ * @private {goog.structs.Map<string, !Array<*>>}
+ */
+goog.Uri.QueryData.prototype.keyMap_ = null;
 
 
 /**
- * @return {string} Get the property name associated with the geometry for
- *     this feature.  By default, this is `geometry` but it may be changed by
- *     calling `setGeometryName`.
- * @api stable
+ * The number of params, or null if it requires computing.
+ * @type {?number}
+ * @private
  */
-ol.Feature.prototype.getGeometryName = function() {
-  return this.geometryName_;
-};
+goog.Uri.QueryData.prototype.count_ = null;
 
 
 /**
- * @return {ol.style.Style|Array.<ol.style.Style>|
- *     ol.feature.FeatureStyleFunction} Return the style as set by setStyle in
- *     the same format that it was provided in. If setStyle has not been run,
- *     return `undefined`.
- * @api stable
+ * @return {?number} The number of parameters.
  */
-ol.Feature.prototype.getStyle = function() {
-  return this.style_;
+goog.Uri.QueryData.prototype.getCount = function() {
+  this.ensureKeyMapInitialized_();
+  return this.count_;
 };
 
 
 /**
- * @return {ol.feature.FeatureStyleFunction|undefined} Return a function
- * representing the current style of this feature.
- * @api stable
+ * Adds a key value pair.
+ * @param {string} key Name.
+ * @param {*} value Value.
+ * @return {!goog.Uri.QueryData} Instance of this object.
  */
-ol.Feature.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
+goog.Uri.QueryData.prototype.add = function(key, value) {
+  this.ensureKeyMapInitialized_();
+  this.invalidateCache_();
+
+  key = this.getKeyName_(key);
+  var values = this.keyMap_.get(key);
+  if (!values) {
+    this.keyMap_.set(key, (values = []));
+  }
+  values.push(value);
+  this.count_++;
+  return this;
 };
 
 
 /**
- * @private
+ * Removes all the params with the given key.
+ * @param {string} key Name.
+ * @return {boolean} Whether any parameter was removed.
  */
-ol.Feature.prototype.handleGeometryChange_ = function() {
-  this.dispatchChangeEvent();
-};
+goog.Uri.QueryData.prototype.remove = function(key) {
+  this.ensureKeyMapInitialized_();
 
+  key = this.getKeyName_(key);
+  if (this.keyMap_.containsKey(key)) {
+    this.invalidateCache_();
 
-/**
- * @private
- */
-ol.Feature.prototype.handleGeometryChanged_ = function() {
-  if (!goog.isNull(this.geometryChangeKey_)) {
-    goog.events.unlistenByKey(this.geometryChangeKey_);
-    this.geometryChangeKey_ = null;
-  }
-  var geometry = this.getGeometry();
-  if (goog.isDefAndNotNull(geometry)) {
-    this.geometryChangeKey_ = goog.events.listen(geometry,
-        goog.events.EventType.CHANGE, this.handleGeometryChange_, false, this);
-    this.dispatchChangeEvent();
+    // Decrement parameter count.
+    this.count_ -= this.keyMap_.get(key).length;
+    return this.keyMap_.remove(key);
   }
+  return false;
 };
 
 
 /**
- * @param {ol.geom.Geometry|undefined} geometry Set the geometry for this
- * feature. This will update the property associated with the current
- * geometry property name.  By default, this is `geometry` but it can be
- * changed by calling `setGeometryName`.
- * @api stable
- * @observable
+ * Clears the parameters.
  */
-ol.Feature.prototype.setGeometry = function(geometry) {
-  this.set(this.geometryName_, geometry);
+goog.Uri.QueryData.prototype.clear = function() {
+  this.invalidateCache_();
+  this.keyMap_ = null;
+  this.count_ = 0;
 };
-goog.exportProperty(
-    ol.Feature.prototype,
-    'setGeometry',
-    ol.Feature.prototype.setGeometry);
 
 
 /**
- * @param {ol.style.Style|Array.<ol.style.Style>|
- *     ol.feature.FeatureStyleFunction} style Set the style for this feature.
- * @api stable
+ * @return {boolean} Whether we have any parameters.
  */
-ol.Feature.prototype.setStyle = function(style) {
-  this.style_ = style;
-  this.styleFunction_ = ol.feature.createFeatureStyleFunction(style);
-  this.dispatchChangeEvent();
+goog.Uri.QueryData.prototype.isEmpty = function() {
+  this.ensureKeyMapInitialized_();
+  return this.count_ == 0;
 };
 
 
 /**
- * @param {number|string|undefined} id Set a unique id for this feature.
- * The id may be used to retrieve a feature from a vector source with the
- * {@link ol.source.Vector#getFeatureById} method.
- * @api stable
+ * Whether there is a parameter with the given name
+ * @param {string} key The parameter name to check for.
+ * @return {boolean} Whether there is a parameter with the given name.
  */
-ol.Feature.prototype.setId = function(id) {
-  this.id_ = id;
-  this.dispatchChangeEvent();
+goog.Uri.QueryData.prototype.containsKey = function(key) {
+  this.ensureKeyMapInitialized_();
+  key = this.getKeyName_(key);
+  return this.keyMap_.containsKey(key);
 };
 
 
 /**
- * @param {string} name Set the property name from which this feature's
- *     geometry will be fetched when calling `getGeometry`.
- * @api stable
+ * Whether there is a parameter with the given value.
+ * @param {*} value The value to check for.
+ * @return {boolean} Whether there is a parameter with the given value.
  */
-ol.Feature.prototype.setGeometryName = function(name) {
-  goog.events.unlisten(
-      this, ol.Object.getChangeEventType(this.geometryName_),
-      this.handleGeometryChanged_, false, this);
-  this.geometryName_ = name;
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(this.geometryName_),
-      this.handleGeometryChanged_, false, this);
-  this.handleGeometryChanged_();
+goog.Uri.QueryData.prototype.containsValue = function(value) {
+  // NOTE(arv): This solution goes through all the params even if it was the
+  // first param. We can get around this by not reusing code or by switching to
+  // iterators.
+  var vals = this.getValues();
+  return goog.array.contains(vals, value);
 };
 
 
 /**
- * A function that takes a `{number}` representing the view's resolution. It
- * returns an Array of {@link ol.style.Style}. This way individual features
- * can be styled. The this keyword inside the function references the
- * {@link ol.Feature} to be styled.
- *
- * @typedef {function(this: ol.Feature, number): Array.<ol.style.Style>}
- * @api stable
+ * Returns all the keys of the parameters. If a key is used multiple times
+ * it will be included multiple times in the returned array
+ * @return {!Array<string>} All the keys of the parameters.
  */
-ol.feature.FeatureStyleFunction;
+goog.Uri.QueryData.prototype.getKeys = function() {
+  this.ensureKeyMapInitialized_();
+  // We need to get the values to know how many keys to add.
+  var vals = /** @type {!Array<*>} */ (this.keyMap_.getValues());
+  var keys = this.keyMap_.getKeys();
+  var rv = [];
+  for (var i = 0; i < keys.length; i++) {
+    var val = vals[i];
+    for (var j = 0; j < val.length; j++) {
+      rv.push(keys[i]);
+    }
+  }
+  return rv;
+};
 
 
 /**
- * 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.feature.FeatureStyleFunction|Array.<ol.style.Style>|
- *     ol.style.Style} obj A feature style function, a single style, or an array
- *     of styles.
- * @return {ol.feature.FeatureStyleFunction} A style function.
+ * Returns all the values of the parameters with the given name. If the query
+ * data has no such key this will return an empty array. If no key is given
+ * all values wil be returned.
+ * @param {string=} opt_key The name of the parameter to get the values for.
+ * @return {!Array<?>} All the values of the parameters with the given name.
  */
-ol.feature.createFeatureStyleFunction = function(obj) {
-  /**
-   * @type {ol.feature.FeatureStyleFunction}
-   */
-  var styleFunction;
-
-  if (goog.isFunction(obj)) {
-    styleFunction = /** @type {ol.feature.FeatureStyleFunction} */ (obj);
+goog.Uri.QueryData.prototype.getValues = function(opt_key) {
+  this.ensureKeyMapInitialized_();
+  var rv = [];
+  if (goog.isString(opt_key)) {
+    if (this.containsKey(opt_key)) {
+      rv = goog.array.concat(rv, this.keyMap_.get(this.getKeyName_(opt_key)));
+    }
   } else {
-    /**
-     * @type {Array.<ol.style.Style>}
-     */
-    var styles;
-    if (goog.isArray(obj)) {
-      styles = obj;
-    } else {
-      goog.asserts.assertInstanceof(obj, ol.style.Style);
-      styles = [obj];
+    // Return all values.
+    var values = this.keyMap_.getValues();
+    for (var i = 0; i < values.length; i++) {
+      rv = goog.array.concat(rv, values[i]);
     }
-    styleFunction = goog.functions.constant(styles);
   }
-  return styleFunction;
+  return rv;
 };
 
-// FIXME remove trailing "Geometry" in method names
-
-goog.provide('ol.render.IVectorContext');
-
-
 
 /**
- * VectorContext interface. Currently implemented by
- * {@link ol.render.canvas.Immediate}
- * @interface
+ * Sets a key value pair and removes all other keys with the same value.
+ *
+ * @param {string} key Name.
+ * @param {*} value Value.
+ * @return {!goog.Uri.QueryData} Instance of this object.
  */
-ol.render.IVectorContext = function() {
-};
-
+goog.Uri.QueryData.prototype.set = function(key, value) {
+  this.ensureKeyMapInitialized_();
+  this.invalidateCache_();
 
-/**
- * @param {number} zIndex Z index.
- * @param {function(ol.render.canvas.Immediate)} callback Callback.
- */
-ol.render.IVectorContext.prototype.drawAsync = function(zIndex, callback) {
+  // TODO(chrishenry): This could be better written as
+  // this.remove(key), this.add(key, value), but that would reorder
+  // the key (since the key is first removed and then added at the
+  // end) and we would have to fix unit tests that depend on key
+  // ordering.
+  key = this.getKeyName_(key);
+  if (this.containsKey(key)) {
+    this.count_ -= this.keyMap_.get(key).length;
+  }
+  this.keyMap_.set(key, [value]);
+  this.count_++;
+  return this;
 };
 
 
 /**
- * @param {ol.geom.Circle} circleGeometry Circle geometry.
- * @param {Object} data Opaque data object,
+ * Returns the first value associated with the key. If the query data has no
+ * such key this will return undefined or the optional default.
+ * @param {string} key The name of the parameter to get the value for.
+ * @param {*=} opt_default The default value to return if the query data
+ *     has no such key.
+ * @return {*} The first string value associated with the key, or opt_default
+ *     if there's no value.
  */
-ol.render.IVectorContext.prototype.drawCircleGeometry =
-    function(circleGeometry, data) {
+goog.Uri.QueryData.prototype.get = function(key, opt_default) {
+  var values = key ? this.getValues(key) : [];
+  if (goog.Uri.preserveParameterTypesCompatibilityFlag) {
+    return values.length > 0 ? values[0] : opt_default;
+  } else {
+    return values.length > 0 ? String(values[0]) : opt_default;
+  }
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
+ * Sets the values for a key. If the key already exists, this will
+ * override all of the existing values that correspond to the key.
+ * @param {string} key The key to set values for.
+ * @param {!Array<?>} values The values to set.
  */
-ol.render.IVectorContext.prototype.drawFeature = function(feature, style) {
+goog.Uri.QueryData.prototype.setValues = function(key, values) {
+  this.remove(key);
+
+  if (values.length > 0) {
+    this.invalidateCache_();
+    this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values));
+    this.count_ += values.length;
+  }
 };
 
 
 /**
- * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
- *     collection.
- * @param {Object} data Opaque data object.
+ * @return {string} Encoded query string.
+ * @override
  */
-ol.render.IVectorContext.prototype.drawGeometryCollectionGeometry =
-    function(geometryCollectionGeometry, data) {
-};
+goog.Uri.QueryData.prototype.toString = function() {
+  if (this.encodedQuery_) {
+    return this.encodedQuery_;
+  }
 
+  if (!this.keyMap_) {
+    return '';
+  }
 
-/**
- * @param {ol.geom.Point} pointGeometry Point geometry.
- * @param {Object} data Opaque data object.
- */
-ol.render.IVectorContext.prototype.drawPointGeometry =
-    function(pointGeometry, data) {
-};
+  var sb = [];
 
+  // In the past, we use this.getKeys() and this.getVals(), but that
+  // generates a lot of allocations as compared to simply iterating
+  // over the keys.
+  var keys = this.keyMap_.getKeys();
+  for (var i = 0; i < keys.length; i++) {
+    var key = keys[i];
+    var encodedKey = goog.string.urlEncode(key);
+    var val = this.getValues(key);
+    for (var j = 0; j < val.length; j++) {
+      var param = encodedKey;
+      // Ensure that null and undefined are encoded into the url as
+      // literal strings.
+      if (val[j] !== '') {
+        param += '=' + goog.string.urlEncode(val[j]);
+      }
+      sb.push(param);
+    }
+  }
 
-/**
- * @param {ol.geom.LineString} lineStringGeometry Line string geometry.
- * @param {Object} data Opaque data object.
- */
-ol.render.IVectorContext.prototype.drawLineStringGeometry =
-    function(lineStringGeometry, data) {
+  return this.encodedQuery_ = sb.join('&');
 };
 
 
 /**
- * @param {ol.geom.MultiLineString} multiLineStringGeometry
- *     MultiLineString geometry.
- * @param {Object} data Opaque data object.
+ * @return {string} Decoded query string.
  */
-ol.render.IVectorContext.prototype.drawMultiLineStringGeometry =
-    function(multiLineStringGeometry, data) {
+goog.Uri.QueryData.prototype.toDecodedString = function() {
+  return goog.Uri.decodeOrEmpty_(this.toString());
 };
 
 
 /**
- * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry.
- * @param {Object} data Opaque data object.
+ * Invalidate the cache.
+ * @private
  */
-ol.render.IVectorContext.prototype.drawMultiPointGeometry =
-    function(multiPointGeometry, data) {
+goog.Uri.QueryData.prototype.invalidateCache_ = function() {
+  this.encodedQuery_ = null;
 };
 
 
 /**
- * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
- * @param {Object} data Opaque data object.
+ * Removes all keys that are not in the provided list. (Modifies this object.)
+ * @param {Array<string>} keys The desired keys.
+ * @return {!goog.Uri.QueryData} a reference to this object.
  */
-ol.render.IVectorContext.prototype.drawMultiPolygonGeometry =
-    function(multiPolygonGeometry, data) {
+goog.Uri.QueryData.prototype.filterKeys = function(keys) {
+  this.ensureKeyMapInitialized_();
+  this.keyMap_.forEach(
+      function(value, key) {
+        if (!goog.array.contains(keys, key)) {
+          this.remove(key);
+        }
+      }, this);
+  return this;
 };
 
 
 /**
- * @param {ol.geom.Polygon} polygonGeometry Polygon geometry.
- * @param {Object} data Opaque data object.
+ * Clone the query data instance.
+ * @return {!goog.Uri.QueryData} New instance of the QueryData object.
  */
-ol.render.IVectorContext.prototype.drawPolygonGeometry =
-    function(polygonGeometry, data) {
+goog.Uri.QueryData.prototype.clone = function() {
+  var rv = new goog.Uri.QueryData();
+  rv.encodedQuery_ = this.encodedQuery_;
+  if (this.keyMap_) {
+    rv.keyMap_ = this.keyMap_.clone();
+    rv.count_ = this.count_;
+  }
+  return rv;
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {Object} data Opaque data object.
+ * Helper function to get the key name from a JavaScript object. Converts
+ * the object to a string, and to lower case if necessary.
+ * @private
+ * @param {*} arg The object to get a key name from.
+ * @return {string} valid key name which can be looked up in #keyMap_.
  */
-ol.render.IVectorContext.prototype.drawText =
-    function(flatCoordinates, offset, end, stride, geometry, data) {
+goog.Uri.QueryData.prototype.getKeyName_ = function(arg) {
+  var keyName = String(arg);
+  if (this.ignoreCase_) {
+    keyName = keyName.toLowerCase();
+  }
+  return keyName;
 };
 
 
 /**
- * @param {ol.style.Fill} fillStyle Fill style.
- * @param {ol.style.Stroke} strokeStyle Stroke style.
+ * Ignore case in parameter names.
+ * NOTE: If there are already key/value pairs in the QueryData, and
+ * ignoreCase_ is set to false, the keys will all be lower-cased.
+ * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
  */
-ol.render.IVectorContext.prototype.setFillStrokeStyle =
-    function(fillStyle, strokeStyle) {
+goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) {
+  var resetKeys = ignoreCase && !this.ignoreCase_;
+  if (resetKeys) {
+    this.ensureKeyMapInitialized_();
+    this.invalidateCache_();
+    this.keyMap_.forEach(
+        function(value, key) {
+          var lowerCase = key.toLowerCase();
+          if (key != lowerCase) {
+            this.remove(key);
+            this.setValues(lowerCase, value);
+          }
+        }, this);
+  }
+  this.ignoreCase_ = ignoreCase;
 };
 
 
 /**
- * @param {ol.style.Image} imageStyle Image style.
+ * Extends a query data object with another query data or map like object. This
+ * operates 'in-place', it does not create a new QueryData object.
+ *
+ * @param {...(goog.Uri.QueryData|goog.structs.Map<?, ?>|Object)} var_args
+ *     The object from which key value pairs will be copied.
  */
-ol.render.IVectorContext.prototype.setImageStyle = function(imageStyle) {
+goog.Uri.QueryData.prototype.extend = function(var_args) {
+  for (var i = 0; i < arguments.length; i++) {
+    var data = arguments[i];
+    goog.structs.forEach(data,
+        /** @this {goog.Uri.QueryData} */
+        function(value, key) {
+          this.add(key, value);
+        }, this);
+  }
 };
 
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @param {ol.style.Text} textStyle Text style.
+ * @fileoverview A delayed callback that pegs to the next animation frame
+ * instead of a user-configurable timeout.
+ *
+ * @author nicksantos@google.com (Nick Santos)
  */
-ol.render.IVectorContext.prototype.setTextStyle = function(textStyle) {
-};
 
-goog.provide('ol.render.Event');
-goog.provide('ol.render.EventType');
+goog.provide('goog.async.AnimationDelay');
 
-goog.require('goog.events.Event');
-goog.require('ol.render.IVectorContext');
+goog.require('goog.Disposable');
+goog.require('goog.events');
+goog.require('goog.functions');
 
 
-/**
- * @enum {string}
- */
-ol.render.EventType = {
-  /**
-   * @event ol.render.Event#postcompose
-   * @api
-   */
-  POSTCOMPOSE: 'postcompose',
-  /**
-   * @event ol.render.Event#precompose
-   * @api
-   */
-  PRECOMPOSE: 'precompose',
-  /**
-   * @event ol.render.Event#render
-   * @api
-   */
-  RENDER: 'render'
-};
+
+// TODO(nicksantos): Should we factor out the common code between this and
+// goog.async.Delay? I'm not sure if there's enough code for this to really
+// make sense. Subclassing seems like the wrong approach for a variety of
+// reasons. Maybe there should be a common interface?
 
 
 
 /**
+ * A delayed callback that pegs to the next animation frame
+ * instead of a user configurable timeout. By design, this should have
+ * the same interface as goog.async.Delay.
+ *
+ * Uses requestAnimationFrame and friends when available, but falls
+ * back to a timeout of goog.async.AnimationDelay.TIMEOUT.
+ *
+ * For more on requestAnimationFrame and how you can use it to create smoother
+ * animations, see:
+ * @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ *
+ * @param {function(number)} listener Function to call when the delay completes.
+ *     Will be passed the timestamp when it's called, in unix ms.
+ * @param {Window=} opt_window The window object to execute the delay in.
+ *     Defaults to the global object.
+ * @param {Object=} opt_handler The object scope to invoke the function in.
  * @constructor
- * @extends {goog.events.Event}
- * @implements {oli.render.Event}
- * @param {ol.render.EventType} type Type.
- * @param {Object=} opt_target Target.
- * @param {ol.render.IVectorContext=} opt_vectorContext Vector context.
- * @param {ol.render.IReplayGroup=} opt_replayGroup Replay group.
- * @param {olx.FrameState=} opt_frameState Frame state.
- * @param {?CanvasRenderingContext2D=} opt_context Context.
- * @param {?ol.webgl.Context=} opt_glContext WebGL Context.
+ * @extends {goog.Disposable}
+ * @final
  */
-ol.render.Event = function(
-    type, opt_target, opt_vectorContext, opt_replayGroup, opt_frameState,
-    opt_context, opt_glContext) {
-
-  goog.base(this, type, opt_target);
+goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
+  goog.async.AnimationDelay.base(this, 'constructor');
 
   /**
-   * For canvas, this is an instance of {@link ol.render.canvas.Immediate}.
-   * @type {ol.render.IVectorContext|undefined}
-   * @api
+   * The function that will be invoked after a delay.
+   * @type {function(number)}
+   * @private
    */
-  this.vectorContext = opt_vectorContext;
+  this.listener_ = listener;
 
   /**
-   * @type {ol.render.IReplayGroup|undefined}
+   * The object context to invoke the callback in.
+   * @type {Object|undefined}
+   * @private
    */
-  this.replayGroup = opt_replayGroup;
+  this.handler_ = opt_handler;
 
   /**
-   * @type {olx.FrameState|undefined}
-   * @api
+   * @type {Window}
+   * @private
    */
-  this.frameState = opt_frameState;
+  this.win_ = opt_window || window;
 
   /**
-   * 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
+   * Cached callback function invoked when the delay finishes.
+   * @type {function()}
+   * @private
    */
-  this.glContext = opt_glContext;
-
+  this.callback_ = goog.bind(this.doAction_, this);
 };
-goog.inherits(ol.render.Event, goog.events.Event);
-
-goog.provide('ol.geom.flat.transform');
-
-goog.require('goog.vec.Mat4');
+goog.inherits(goog.async.AnimationDelay, goog.Disposable);
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed coordinates.
+ * Identifier of the active delay timeout, or event listener,
+ * or null when inactive.
+ * @type {goog.events.Key|number|null}
+ * @private
  */
-ol.geom.flat.transform.transform2D =
-    function(flatCoordinates, offset, end, stride, transform, opt_dest) {
-  var m00 = goog.vec.Mat4.getElement(transform, 0, 0);
-  var m10 = goog.vec.Mat4.getElement(transform, 1, 0);
-  var m01 = goog.vec.Mat4.getElement(transform, 0, 1);
-  var m11 = goog.vec.Mat4.getElement(transform, 1, 1);
-  var m03 = goog.vec.Mat4.getElement(transform, 0, 3);
-  var m13 = goog.vec.Mat4.getElement(transform, 1, 3);
-  var dest = goog.isDef(opt_dest) ? opt_dest : [];
-  var i = 0;
-  var j, jj;
-  for (j = offset; j < end; j += stride) {
-    var x = flatCoordinates[j];
-    var y = flatCoordinates[j + 1];
-    dest[i++] = m00 * x + m01 * y + m03;
-    dest[i++] = m10 * x + m11 * y + m13;
-  }
-  if (goog.isDef(opt_dest) && dest.length != i) {
-    dest.length = i;
-  }
-  return dest;
-};
-
-goog.provide('ol.geom.SimpleGeometry');
-
-goog.require('goog.asserts');
-goog.require('goog.functions');
-goog.require('goog.object');
-goog.require('ol.extent');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.flat.transform');
-
+goog.async.AnimationDelay.prototype.id_ = null;
 
 
 /**
- * @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
+ * If we're using dom listeners.
+ * @type {?boolean}
+ * @private
  */
-ol.geom.SimpleGeometry = function() {
-
-  goog.base(this);
-
-  /**
-   * @protected
-   * @type {ol.geom.GeometryLayout}
-   */
-  this.layout = ol.geom.GeometryLayout.XY;
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.stride = 2;
-
-  /**
-   * @protected
-   * @type {Array.<number>}
-   */
-  this.flatCoordinates = null;
-
-};
-goog.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
+goog.async.AnimationDelay.prototype.usingListeners_ = false;
 
 
 /**
- * @param {number} stride Stride.
- * @private
- * @return {ol.geom.GeometryLayout} layout Layout.
+ * Default wait timeout for animations (in milliseconds).  Only used for timed
+ * animation, which uses a timer (setTimeout) to schedule animation.
+ *
+ * @type {number}
+ * @const
  */
-ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) {
-  if (stride == 2) {
-    return ol.geom.GeometryLayout.XY;
-  } else if (stride == 3) {
-    return ol.geom.GeometryLayout.XYZ;
-  } else if (stride == 4) {
-    return ol.geom.GeometryLayout.XYZM;
-  } else {
-    goog.asserts.fail('unsupported stride: ' + stride);
-  }
-};
+goog.async.AnimationDelay.TIMEOUT = 20;
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
+ * Name of event received from the requestAnimationFrame in Firefox.
+ *
+ * @type {string}
+ * @const
  * @private
- * @return {number} Stride.
  */
-ol.geom.SimpleGeometry.getStrideForLayout_ = function(layout) {
-  if (layout == ol.geom.GeometryLayout.XY) {
-    return 2;
-  } else if (layout == ol.geom.GeometryLayout.XYZ) {
-    return 3;
-  } else if (layout == ol.geom.GeometryLayout.XYM) {
-    return 3;
-  } else if (layout == ol.geom.GeometryLayout.XYZM) {
-    return 4;
-  } else {
-    goog.asserts.fail('unsupported layout: ' + layout);
-  }
-};
+goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint';
 
 
 /**
- * @inheritDoc
+ * Starts the delay timer. The provided listener function will be called
+ * before the next animation frame.
  */
-ol.geom.SimpleGeometry.prototype.containsXY = goog.functions.FALSE;
-
+goog.async.AnimationDelay.prototype.start = function() {
+  this.stop();
+  this.usingListeners_ = false;
 
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.SimpleGeometry.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    this.extent = ol.extent.createOrUpdateFromFlatCoordinates(
-        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-        this.extent);
-    this.extentRevision = this.getRevision();
+  var raf = this.getRaf_();
+  var cancelRaf = this.getCancelRaf_();
+  if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
+    // Because Firefox (Gecko) runs animation in separate threads, it also saves
+    // time by running the requestAnimationFrame callbacks in that same thread.
+    // Sadly this breaks the assumption of implicit thread-safety in JS, and can
+    // thus create thread-based inconsistencies on counters etc.
+    //
+    // Calling cycleAnimations_ using the MozBeforePaint event instead of as
+    // callback fixes this.
+    //
+    // Trigger this condition only if the mozRequestAnimationFrame is available,
+    // but not the W3C requestAnimationFrame function (as in draft) or the
+    // equivalent cancel functions.
+    this.id_ = goog.events.listen(
+        this.win_,
+        goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_,
+        this.callback_);
+    this.win_.mozRequestAnimationFrame(null);
+    this.usingListeners_ = true;
+  } else if (raf && cancelRaf) {
+    this.id_ = raf.call(this.win_, this.callback_);
+  } else {
+    this.id_ = this.win_.setTimeout(
+        // Prior to Firefox 13, Gecko passed a non-standard parameter
+        // to the callback that we want to ignore.
+        goog.functions.lock(this.callback_),
+        goog.async.AnimationDelay.TIMEOUT);
   }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
 };
 
 
 /**
- * @return {ol.Coordinate} First coordinate.
- * @api stable
+ * Stops the delay timer if it is active. No action is taken if the timer is not
+ * in use.
  */
-ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
-  return this.flatCoordinates.slice(0, this.stride);
+goog.async.AnimationDelay.prototype.stop = function() {
+  if (this.isActive()) {
+    var raf = this.getRaf_();
+    var cancelRaf = this.getCancelRaf_();
+    if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
+      goog.events.unlistenByKey(this.id_);
+    } else if (raf && cancelRaf) {
+      cancelRaf.call(this.win_, /** @type {number} */ (this.id_));
+    } else {
+      this.win_.clearTimeout(/** @type {number} */ (this.id_));
+    }
+  }
+  this.id_ = null;
 };
 
 
 /**
- * @return {Array.<number>} Flat coordinates.
+ * Fires delay's action even if timer has already gone off or has not been
+ * started yet; guarantees action firing. Stops the delay timer.
  */
-ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
-  return this.flatCoordinates;
+goog.async.AnimationDelay.prototype.fire = function() {
+  this.stop();
+  this.doAction_();
 };
 
 
 /**
- * @return {ol.Coordinate} Last point.
- * @api stable
+ * Fires delay's action only if timer is currently active. Stops the delay
+ * timer.
  */
-ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
-  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
+goog.async.AnimationDelay.prototype.fireIfActive = function() {
+  if (this.isActive()) {
+    this.fire();
+  }
 };
 
 
 /**
- * @return {ol.geom.GeometryLayout} Layout.
- * @api stable
+ * @return {boolean} True if the delay is currently active, false otherwise.
  */
-ol.geom.SimpleGeometry.prototype.getLayout = function() {
-  return this.layout;
+goog.async.AnimationDelay.prototype.isActive = function() {
+  return this.id_ != null;
 };
 
 
 /**
- * @inheritDoc
+ * Invokes the callback function after the delay successfully completes.
+ * @private
  */
-ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry =
-    function(squaredTolerance) {
-  if (this.simplifiedGeometryRevision != this.getRevision()) {
-    goog.object.clear(this.simplifiedGeometryCache);
-    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
-    this.simplifiedGeometryRevision = this.getRevision();
-  }
-  // If squaredTolerance is negative or if we know that simplification will not
-  // have any effect then just return this.
-  if (squaredTolerance < 0 ||
-      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
-       squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) {
-    return this;
-  }
-  var key = squaredTolerance.toString();
-  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
-    return this.simplifiedGeometryCache[key];
-  } else {
-    var simplifiedGeometry =
-        this.getSimplifiedGeometryInternal(squaredTolerance);
-    var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates();
-    if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) {
-      this.simplifiedGeometryCache[key] = simplifiedGeometry;
-      return simplifiedGeometry;
-    } else {
-      // Simplification did not actually remove any coordinates.  We now know
-      // that any calls to getSimplifiedGeometry with a squaredTolerance less
-      // than or equal to the current squaredTolerance will also not have any
-      // effect.  This allows us to short circuit simplification (saving CPU
-      // cycles) and prevents the cache of simplified geometries from filling
-      // up with useless identical copies of this geometry (saving memory).
-      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
-      return this;
-    }
+goog.async.AnimationDelay.prototype.doAction_ = function() {
+  if (this.usingListeners_ && this.id_) {
+    goog.events.unlistenByKey(this.id_);
   }
+  this.id_ = null;
+
+  // We are not using the timestamp returned by requestAnimationFrame
+  // because it may be either a Date.now-style time or a
+  // high-resolution time (depending on browser implementation). Using
+  // goog.now() will ensure that the timestamp used is consistent and
+  // compatible with goog.fx.Animation.
+  this.listener_.call(this.handler_, goog.now());
 };
 
 
-/**
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.SimpleGeometry} Simplified geometry.
- * @protected
- */
-ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal =
-    function(squaredTolerance) {
-  return this;
+/** @override */
+goog.async.AnimationDelay.prototype.disposeInternal = function() {
+  this.stop();
+  goog.async.AnimationDelay.base(this, 'disposeInternal');
 };
 
 
 /**
- * @return {number} Stride.
+ * @return {?function(function(number)): number} The requestAnimationFrame
+ *     function, or null if not available on this browser.
+ * @private
  */
-ol.geom.SimpleGeometry.prototype.getStride = function() {
-  return this.stride;
+goog.async.AnimationDelay.prototype.getRaf_ = function() {
+  var win = this.win_;
+  return win.requestAnimationFrame ||
+      win.webkitRequestAnimationFrame ||
+      win.mozRequestAnimationFrame ||
+      win.oRequestAnimationFrame ||
+      win.msRequestAnimationFrame ||
+      null;
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @protected
+ * @return {?function(number): number} The cancelAnimationFrame function,
+ *     or null if not available on this browser.
+ * @private
  */
-ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal =
-    function(layout, flatCoordinates) {
-  this.stride = ol.geom.SimpleGeometry.getStrideForLayout_(layout);
-  this.layout = layout;
-  this.flatCoordinates = flatCoordinates;
+goog.async.AnimationDelay.prototype.getCancelRaf_ = function() {
+  var win = this.win_;
+  return win.cancelRequestAnimationFrame ||
+      win.webkitCancelRequestAnimationFrame ||
+      win.mozCancelRequestAnimationFrame ||
+      win.oCancelRequestAnimationFrame ||
+      win.msCancelRequestAnimationFrame ||
+      null;
 };
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @param {ol.geom.GeometryLayout|undefined} layout Layout.
- * @param {Array} coordinates Coordinates.
- * @param {number} nesting Nesting.
- * @protected
+ * @fileoverview Provides a function to schedule running a function as soon
+ * as possible after the current JS execution stops and yields to the event
+ * loop.
+ *
  */
-ol.geom.SimpleGeometry.prototype.setLayout =
-    function(layout, coordinates, nesting) {
-  /** @type {number} */
-  var stride;
-  if (goog.isDef(layout)) {
-    stride = ol.geom.SimpleGeometry.getStrideForLayout_(layout);
-  } else {
-    var i;
-    for (i = 0; i < nesting; ++i) {
-      if (coordinates.length === 0) {
-        this.layout = ol.geom.GeometryLayout.XY;
-        this.stride = 2;
-        return;
-      } else {
-        coordinates = /** @type {Array} */ (coordinates[0]);
-      }
-    }
-    stride = (/** @type {Array} */ (coordinates)).length;
-    layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride);
-  }
-  this.layout = layout;
-  this.stride = stride;
-};
 
+goog.provide('goog.async.nextTick');
+goog.provide('goog.async.throwException');
 
-/**
- * @inheritDoc
- */
-ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
-  if (!goog.isNull(this.flatCoordinates)) {
-    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
-    this.dispatchChangeEvent();
-  }
-};
+goog.require('goog.debug.entryPointRegistry');
+goog.require('goog.functions');
+goog.require('goog.labs.userAgent.browser');
 
 
 /**
- * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed flat coordinates.
+ * Throw an item without interrupting the current execution context.  For
+ * example, if processing a group of items in a loop, sometimes it is useful
+ * to report an error while still allowing the rest of the batch to be
+ * processed.
+ * @param {*} exception
  */
-ol.geom.transformSimpleGeometry2D =
-    function(simpleGeometry, transform, opt_dest) {
-  var flatCoordinates = simpleGeometry.getFlatCoordinates();
-  if (goog.isNull(flatCoordinates)) {
-    return null;
-  } else {
-    var stride = simpleGeometry.getStride();
-    return ol.geom.flat.transform.transform2D(
-        flatCoordinates, 0, flatCoordinates.length, stride,
-        transform, opt_dest);
-  }
+goog.async.throwException = function(exception) {
+  // Each throw needs to be in its own context.
+  goog.global.setTimeout(function() { throw exception; }, 0);
 };
 
-goog.provide('ol.geom.flat.deflate');
-
-goog.require('goog.asserts');
-
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} stride Stride.
- * @return {number} offset Offset.
+ * Fires the provided callbacks as soon as possible after the current JS
+ * execution context. setTimeout(…, 0) takes at least 4ms when called from
+ * within another setTimeout(…, 0) for legacy reasons.
+ *
+ * This will not schedule the callback as a microtask (i.e. a task that can
+ * preempt user input or networking callbacks). It is meant to emulate what
+ * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
+ * behavior, use {@see goog.Promise} instead.
+ *
+ * @param {function(this:SCOPE)} callback Callback function to fire as soon as
+ *     possible.
+ * @param {SCOPE=} opt_context Object in whose scope to call the listener.
+ * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
+ *     ensures correctness at the cost of speed. See comments for details.
+ * @template SCOPE
  */
-ol.geom.flat.deflate.coordinate =
-    function(flatCoordinates, offset, coordinate, stride) {
-  goog.asserts.assert(coordinate.length == stride);
-  var i, ii;
-  for (i = 0, ii = coordinate.length; i < ii; ++i) {
-    flatCoordinates[offset++] = coordinate[i];
+goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
+  var cb = callback;
+  if (opt_context) {
+    cb = goog.bind(callback, opt_context);
+  }
+  cb = goog.async.nextTick.wrapCallback_(cb);
+  // window.setImmediate was introduced and currently only supported by IE10+,
+  // but due to a bug in the implementation it is not guaranteed that
+  // setImmediate is faster than setTimeout nor that setImmediate N is before
+  // setImmediate N+1. That is why we do not use the native version if
+  // available. We do, however, call setImmediate if it is a normal function
+  // because that indicates that it has been replaced by goog.testing.MockClock
+  // which we do want to support.
+  // See
+  // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
+  //
+  // Note we do allow callers to also request setImmediate if they are willing
+  // to accept the possible tradeoffs of incorrectness in exchange for speed.
+  // The IE fallback of readystate change is much slower.
+  if (goog.isFunction(goog.global.setImmediate) &&
+      (opt_useSetImmediate || !goog.global.Window ||
+       goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) {
+    goog.global.setImmediate(cb);
+    return;
   }
-  return offset;
-};
-
 
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {number} stride Stride.
- * @return {number} offset Offset.
- */
-ol.geom.flat.deflate.coordinates =
-    function(flatCoordinates, offset, coordinates, stride) {
-  var i, ii;
-  for (i = 0, ii = coordinates.length; i < ii; ++i) {
-    var coordinate = coordinates[i];
-    goog.asserts.assert(coordinate.length == stride);
-    var j;
-    for (j = 0; j < stride; ++j) {
-      flatCoordinates[offset++] = coordinate[j];
-    }
+  // Look for and cache the custom fallback version of setImmediate.
+  if (!goog.async.nextTick.setImmediate_) {
+    goog.async.nextTick.setImmediate_ =
+        goog.async.nextTick.getSetImmediateEmulator_();
   }
-  return offset;
+  goog.async.nextTick.setImmediate_(cb);
 };
 
 
 /**
- * @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.
+ * Cache for the setImmediate implementation.
+ * @type {function(function())}
+ * @private
  */
-ol.geom.flat.deflate.coordinatess =
-    function(flatCoordinates, offset, coordinatess, stride, opt_ends) {
-  var ends = goog.isDef(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;
-};
+goog.async.nextTick.setImmediate_;
 
 
 /**
- * @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.
+ * Determines the best possible implementation to run a function as soon as
+ * the JS event loop is idle.
+ * @return {function(function())} The "setImmediate" implementation.
+ * @private
  */
-ol.geom.flat.deflate.coordinatesss =
-    function(flatCoordinates, offset, coordinatesss, stride, opt_endss) {
-  var endss = goog.isDef(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.Circle');
-
-goog.require('goog.asserts');
-goog.require('ol.extent');
-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) {
-  goog.base(this);
-  var radius = goog.isDef(opt_radius) ? opt_radius : 0;
-  this.setCenterAndRadius(center, radius,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
+goog.async.nextTick.getSetImmediateEmulator_ = function() {
+  // Create a private message channel and use it to postMessage empty messages
+  // to ourselves.
+  var Channel = goog.global['MessageChannel'];
+  // If MessageChannel is not available and we are in a browser, implement
+  // an iframe based polyfill in browsers that have postMessage and
+  // document.addEventListener. The latter excludes IE8 because it has a
+  // synchronous postMessage implementation.
+  if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
+      window.postMessage && window.addEventListener) {
+    /** @constructor */
+    Channel = function() {
+      // Make an empty, invisible iframe.
+      var iframe = document.createElement('iframe');
+      iframe.style.display = 'none';
+      iframe.src = '';
+      document.documentElement.appendChild(iframe);
+      var win = iframe.contentWindow;
+      var doc = win.document;
+      doc.open();
+      doc.write('');
+      doc.close();
+      // Do not post anything sensitive over this channel, as the workaround for
+      // pages with file: origin could allow that information to be modified or
+      // intercepted.
+      var message = 'callImmediate' + Math.random();
+      // The same origin policy rejects attempts to postMessage from file: urls
+      // unless the origin is '*'.
+      // TODO(b/16335441): Use '*' origin for data: and other similar protocols.
+      var origin = win.location.protocol == 'file:' ?
+          '*' : win.location.protocol + '//' + win.location.host;
+      var onmessage = goog.bind(function(e) {
+        // Validate origin and message to make sure that this message was
+        // intended for us. If the origin is set to '*' (see above) only the
+        // message needs to match since, for example, '*' != 'file://'. Allowing
+        // the wildcard is ok, as we are not concerned with security here.
+        if ((origin != '*' && e.origin != origin) || e.data != message) {
+          return;
+        }
+        this['port1'].onmessage();
+      }, this);
+      win.addEventListener('message', onmessage, false);
+      this['port1'] = {};
+      this['port2'] = {
+        postMessage: function() {
+          win.postMessage(message, origin);
+        }
+      };
+    };
+  }
+  if (typeof Channel !== 'undefined' &&
+      (!goog.labs.userAgent.browser.isIE())) {
+    // Exclude all of IE due to
+    // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
+    // which allows starving postMessage with a busy setTimeout loop.
+    // This currently affects IE10 and IE11 which would otherwise be able
+    // to use the postMessage based fallbacks.
+    var channel = new Channel();
+    // Use a fifo linked list to call callbacks in the right order.
+    var head = {};
+    var tail = head;
+    channel['port1'].onmessage = function() {
+      if (goog.isDef(head.next)) {
+        head = head.next;
+        var cb = head.cb;
+        head.cb = null;
+        cb();
+      }
+    };
+    return function(cb) {
+      tail.next = {
+        cb: cb
+      };
+      tail = tail.next;
+      channel['port2'].postMessage(0);
+    };
+  }
+  // Implementation for IE6+: Script elements fire an asynchronous
+  // onreadystatechange event when inserted into the DOM.
+  if (typeof document !== 'undefined' && 'onreadystatechange' in
+      document.createElement('script')) {
+    return function(cb) {
+      var script = document.createElement('script');
+      script.onreadystatechange = function() {
+        // Clean up and call the callback.
+        script.onreadystatechange = null;
+        script.parentNode.removeChild(script);
+        script = null;
+        cb();
+        cb = null;
+      };
+      document.documentElement.appendChild(script);
+    };
+  }
+  // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
+  // or more.
+  return function(cb) {
+    goog.global.setTimeout(cb, 0);
+  };
 };
-goog.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Circle} Clone.
- * @api
+ * Helper function that is overrided to protect callbacks with entry point
+ * monitor if the application monitors entry points.
+ * @param {function()} callback Callback function to fire as soon as possible.
+ * @return {function()} The wrapped callback.
+ * @private
  */
-ol.geom.Circle.prototype.clone = function() {
-  var circle = new ol.geom.Circle(null);
-  circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return circle;
-};
+goog.async.nextTick.wrapCallback_ = goog.functions.identity;
 
 
-/**
- * @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;
-  }
-};
+// Register the callback function as an entry point, so that it can be
+// monitored for exception handling, etc. This has to be done in this file
+// since it requires special code to handle all browsers.
+goog.debug.entryPointRegistry.register(
+    /**
+     * @param {function(!Function): !Function} transformer The transforming
+     *     function.
+     */
+    function(transformer) {
+      goog.async.nextTick.wrapCallback_ = transformer;
+    });
 
+// Copyright 2014 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @inheritDoc
+ * @fileoverview The SafeScript type and its builders.
+ *
+ * TODO(user): Link to document stating type contract.
  */
-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_();
-};
 
+goog.provide('goog.html.SafeScript');
 
-/**
- * @return {ol.Coordinate} Center.
- * @api
+goog.require('goog.asserts');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
+
+
+
+/**
+ * A string-like object which represents JavaScript code and that carries the
+ * security type contract that its value, as a string, will not cause execution
+ * of unconstrained attacker controlled code (XSS) when evaluated as JavaScript
+ * in a browser.
+ *
+ * Instances of this type must be created via the factory method
+ * {@code goog.html.SafeScript.fromConstant} and not by invoking its
+ * constructor. The constructor intentionally takes no parameters and the type
+ * is immutable; hence only a default instance corresponding to the empty string
+ * can be obtained via constructor invocation.
+ *
+ * A SafeScript's string representation can safely be interpolated as the
+ * content of a script element within HTML. The SafeScript string should not be
+ * escaped before interpolation.
+ *
+ * Note that the SafeScript might contain text that is attacker-controlled but
+ * that text should have been interpolated with appropriate escaping,
+ * sanitization and/or validation into the right location in the script, such
+ * that it is highly constrained in its effect (for example, it had to match a
+ * set of whitelisted words).
+ *
+ * A SafeScript can be constructed via security-reviewed unchecked
+ * conversions. In this case producers of SafeScript must ensure themselves that
+ * the SafeScript does not contain unsafe script. Note in particular that
+ * {@code &lt;} is dangerous, even when inside JavaScript strings, and so should
+ * always be forbidden or JavaScript escaped in user controlled input. For
+ * example, if {@code &lt;/script&gt;&lt;script&gt;evil&lt;/script&gt;"} were
+ * interpolated inside a JavaScript string, it would break out of the context
+ * of the original script element and {@code evil} would execute. Also note
+ * that within an HTML script (raw text) element, HTML character references,
+ * such as "&lt;" are not allowed. See
+ * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.
+ *
+ * @see goog.html.SafeScript#fromConstant
+ * @constructor
+ * @final
+ * @struct
+ * @implements {goog.string.TypedString}
  */
-ol.geom.Circle.prototype.getCenter = function() {
-  return this.flatCoordinates.slice(0, this.stride);
-};
-
+goog.html.SafeScript = function() {
+  /**
+   * The contained value of this SafeScript.  The field has a purposely
+   * ugly name to make (non-compiled) code that attempts to directly access this
+   * field stand out.
+   * @private {string}
+   */
+  this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = '';
 
-/**
- * @inheritDoc
- * @api
- */
-ol.geom.Circle.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    var flatCoordinates = this.flatCoordinates;
-    var radius = flatCoordinates[this.stride] - flatCoordinates[0];
-    this.extent = ol.extent.createOrUpdate(
-        flatCoordinates[0] - radius, flatCoordinates[1] - radius,
-        flatCoordinates[0] + radius, flatCoordinates[1] + radius,
-        this.extent);
-    this.extentRevision = this.getRevision();
-  }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
+  /**
+   * A type marker used to implement additional run-time type checking.
+   * @see goog.html.SafeScript#unwrap
+   * @const
+   * @private
+   */
+  this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
+      goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
 };
 
 
 /**
- * @return {number} Radius.
- * @api
+ * @override
+ * @const
  */
-ol.geom.Circle.prototype.getRadius = function() {
-  return Math.sqrt(this.getRadiusSquared_());
-};
+goog.html.SafeScript.prototype.implementsGoogStringTypedString = true;
 
 
 /**
+ * Type marker for the SafeScript type, used to implement additional
+ * run-time type checking.
+ * @const
  * @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;
-};
+goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
 
 
 /**
- * @inheritDoc
- * @api
+ * Creates a SafeScript object from a compile-time constant string.
+ *
+ * @param {!goog.string.Const} script A compile-time-constant string from which
+ *     to create a SafeScript.
+ * @return {!goog.html.SafeScript} A SafeScript object initialized to
+ *     {@code script}.
  */
-ol.geom.Circle.prototype.getType = function() {
-  return ol.geom.GeometryType.CIRCLE;
+goog.html.SafeScript.fromConstant = function(script) {
+  var scriptString = goog.string.Const.unwrap(script);
+  if (scriptString.length === 0) {
+    return goog.html.SafeScript.EMPTY;
+  }
+  return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
+      scriptString);
 };
 
 
 /**
- * @param {ol.Coordinate} center Center.
- * @api
+ * Returns this SafeScript's value as a string.
+ *
+ * IMPORTANT: In code where it is security relevant that an object's type is
+ * indeed {@code SafeScript}, use {@code goog.html.SafeScript.unwrap} instead of
+ * this method. If in doubt, assume that it's security relevant. In particular,
+ * note that goog.html functions which return a goog.html type do not guarantee
+ * the returned instance is of the right type. For example:
+ *
+ * <pre>
+ * var fakeSafeHtml = new String('fake');
+ * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
+ * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
+ * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
+ * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
+ * // instanceof goog.html.SafeHtml.
+ * </pre>
+ *
+ * @see goog.html.SafeScript#unwrap
+ * @override
  */
-ol.geom.Circle.prototype.setCenter = function(center) {
-  var stride = this.stride;
-  goog.asserts.assert(center.length == 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);
+goog.html.SafeScript.prototype.getTypedStringValue = function() {
+  return this.privateDoNotAccessOrElseSafeScriptWrappedValue_;
 };
 
 
+if (goog.DEBUG) {
+  /**
+   * Returns a debug string-representation of this value.
+   *
+   * To obtain the actual string value wrapped in a SafeScript, use
+   * {@code goog.html.SafeScript.unwrap}.
+   *
+   * @see goog.html.SafeScript#unwrap
+   * @override
+   */
+  goog.html.SafeScript.prototype.toString = function() {
+    return 'SafeScript{' +
+        this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}';
+  };
+}
+
+
 /**
- * @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 (goog.isNull(center)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+ * Performs a runtime check that the provided object is indeed a
+ * SafeScript object, and returns its value.
+ *
+ * @param {!goog.html.SafeScript} safeScript The object to extract from.
+ * @return {string} The safeScript object's contained string, unless
+ *     the run-time type check fails. In that case, {@code unwrap} returns an
+ *     innocuous string, or, if assertions are enabled, throws
+ *     {@code goog.asserts.AssertionError}.
+ */
+goog.html.SafeScript.unwrap = function(safeScript) {
+  // Perform additional Run-time type-checking to ensure that
+  // safeScript is indeed an instance of the expected type.  This
+  // provides some additional protection against security bugs due to
+  // application code that disables type checks.
+  // Specifically, the following checks are performed:
+  // 1. The object is an instance of the expected type.
+  // 2. The object is not an instance of a subclass.
+  // 3. The object carries a type marker for the expected type. "Faking" an
+  // object requires a reference to the type marker, which has names intended
+  // to stand out in code reviews.
+  if (safeScript instanceof goog.html.SafeScript &&
+      safeScript.constructor === goog.html.SafeScript &&
+      safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
+          goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
+    return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;
   } else {
-    this.setLayout(opt_layout, center, 0);
-    if (goog.isNull(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.dispatchChangeEvent();
+    goog.asserts.fail(
+        'expected object of type SafeScript, got \'' + safeScript + '\'');
+    return 'type_error:SafeScript';
   }
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * Package-internal utility method to create SafeScript instances.
+ *
+ * @param {string} script The string to initialize the SafeScript object with.
+ * @return {!goog.html.SafeScript} The initialized SafeScript object.
+ * @package
  */
-ol.geom.Circle.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.dispatchChangeEvent();
+goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse =
+    function(script) {
+  var safeScript = new goog.html.SafeScript();
+  safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script;
+  return safeScript;
 };
 
 
 /**
- * @param {number} radius Radius.
- * @api
+ * A SafeScript instance corresponding to the empty string.
+ * @const {!goog.html.SafeScript}
  */
-ol.geom.Circle.prototype.setRadius = function(radius) {
-  goog.asserts.assert(!goog.isNull(this.flatCoordinates));
-  this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
-  this.dispatchChangeEvent();
-};
+goog.html.SafeScript.EMPTY =
+    goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');
 
+// Copyright 2014 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @inheritDoc
+ * @fileoverview The SafeStyleSheet type and its builders.
+ *
+ * TODO(user): Link to document stating type contract.
  */
-ol.geom.Circle.prototype.applyTransform = goog.abstractMethod;
 
-goog.provide('ol.geom.GeometryCollection');
+goog.provide('goog.html.SafeStyleSheet');
 
 goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol.extent');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.GeometryType');
+goog.require('goog.string');
+goog.require('goog.string.Const');
+goog.require('goog.string.TypedString');
+
+
+
+/**
+ * A string-like object which represents a CSS style sheet and that carries the
+ * security type contract that its value, as a string, will not cause untrusted
+ * script execution (XSS) when evaluated as CSS in a browser.
+ *
+ * Instances of this type must be created via the factory method
+ * {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its
+ * constructor. The constructor intentionally takes no parameters and the type
+ * is immutable; hence only a default instance corresponding to the empty string
+ * can be obtained via constructor invocation.
+ *
+ * A SafeStyleSheet's string representation can safely be interpolated as the
+ * content of a style element within HTML. The SafeStyleSheet string should
+ * not be escaped before interpolation.
+ *
+ * Values of this type must be composable, i.e. for any two values
+ * {@code styleSheet1} and {@code styleSheet2} of this type,
+ * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) +
+ * goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that
+ * satisfies the SafeStyleSheet type constraint. This requirement implies that
+ * for any value {@code styleSheet} of this type,
+ * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in
+ * "beginning of rule" context.
+
+ * A SafeStyleSheet can be constructed via security-reviewed unchecked
+ * conversions. In this case producers of SafeStyleSheet must ensure themselves
+ * that the SafeStyleSheet does not contain unsafe script. Note in particular
+ * that {@code &lt;} is dangerous, even when inside CSS strings, and so should
+ * always be forbidden or CSS-escaped in user controlled input. For example, if
+ * {@code &lt;/style&gt;&lt;script&gt;evil&lt;/script&gt;"} were interpolated
+ * inside a CSS string, it would break out of the context of the original
+ * style element and {@code evil} would execute. Also note that within an HTML
+ * style (raw text) element, HTML character references, such as
+ * {@code &amp;lt;}, are not allowed. See
+ * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
+ * (similar considerations apply to the style element).
+ *
+ * @see goog.html.SafeStyleSheet#fromConstant
+ * @constructor
+ * @final
+ * @struct
+ * @implements {goog.string.TypedString}
+ */
+goog.html.SafeStyleSheet = function() {
+  /**
+   * The contained value of this SafeStyleSheet.  The field has a purposely
+   * ugly name to make (non-compiled) code that attempts to directly access this
+   * field stand out.
+   * @private {string}
+   */
+  this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = '';
 
+  /**
+   * A type marker used to implement additional run-time type checking.
+   * @see goog.html.SafeStyleSheet#unwrap
+   * @const
+   * @private
+   */
+  this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
+      goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
+};
 
 
 /**
- * @classdesc
- * An array of {@link ol.geom.Geometry} objects.
- *
- * @constructor
- * @extends {ol.geom.Geometry}
- * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries.
- * @api stable
+ * @override
+ * @const
  */
-ol.geom.GeometryCollection = function(opt_geometries) {
+goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true;
 
-  goog.base(this);
+
+/**
+ * Type marker for the SafeStyleSheet type, used to implement additional
+ * run-time type checking.
+ * @const
+ * @private
+ */
+goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
+
+
+/**
+ * Creates a new SafeStyleSheet object by concatenating values.
+ * @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)}
+ *     var_args Values to concatenate.
+ * @return {!goog.html.SafeStyleSheet}
+ */
+goog.html.SafeStyleSheet.concat = function(var_args) {
+  var result = '';
 
   /**
-   * @private
-   * @type {Array.<ol.geom.Geometry>}
+   * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
+   *     argument
    */
-  this.geometries_ = goog.isDef(opt_geometries) ? opt_geometries : null;
+  var addArgument = function(argument) {
+    if (goog.isArray(argument)) {
+      goog.array.forEach(argument, addArgument);
+    } else {
+      result += goog.html.SafeStyleSheet.unwrap(argument);
+    }
+  };
 
-  this.listenGeometriesChange_();
+  goog.array.forEach(arguments, addArgument);
+  return goog.html.SafeStyleSheet
+      .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result);
 };
-goog.inherits(ol.geom.GeometryCollection, ol.geom.Geometry);
 
 
 /**
- * @param {Array.<ol.geom.Geometry>} geometries Geometries.
- * @private
- * @return {Array.<ol.geom.Geometry>} Cloned geometries.
+ * Creates a SafeStyleSheet object from a compile-time constant string.
+ *
+ * {@code styleSheet} must not have any &lt; characters in it, so that
+ * the syntactic structure of the surrounding HTML is not affected.
+ *
+ * @param {!goog.string.Const} styleSheet A compile-time-constant string from
+ *     which to create a SafeStyleSheet.
+ * @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to
+ *     {@code styleSheet}.
  */
-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());
+goog.html.SafeStyleSheet.fromConstant = function(styleSheet) {
+  var styleSheetString = goog.string.Const.unwrap(styleSheet);
+  if (styleSheetString.length === 0) {
+    return goog.html.SafeStyleSheet.EMPTY;
   }
-  return clonedGeometries;
+  // > is a valid character in CSS selectors and there's no strict need to
+  // block it if we already block <.
+  goog.asserts.assert(!goog.string.contains(styleSheetString, '<'),
+      "Forbidden '<' character in style sheet string: " + styleSheetString);
+  return goog.html.SafeStyleSheet.
+      createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString);
 };
 
 
 /**
- * @private
+ * Returns this SafeStyleSheet's value as a string.
+ *
+ * IMPORTANT: In code where it is security relevant that an object's type is
+ * indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap}
+ * instead of this method. If in doubt, assume that it's security relevant. In
+ * particular, note that goog.html functions which return a goog.html type do
+ * not guarantee the returned instance is of the right type. For example:
+ *
+ * <pre>
+ * var fakeSafeHtml = new String('fake');
+ * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
+ * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
+ * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
+ * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
+ * // instanceof goog.html.SafeHtml.
+ * </pre>
+ *
+ * @see goog.html.SafeStyleSheet#unwrap
+ * @override
  */
-ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() {
-  var i, ii;
-  if (goog.isNull(this.geometries_)) {
-    return;
-  }
-  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
-    goog.events.unlisten(
-        this.geometries_[i], goog.events.EventType.CHANGE,
-        this.dispatchChangeEvent, false, this);
-  }
+goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() {
+  return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
 };
 
 
+if (goog.DEBUG) {
+  /**
+   * Returns a debug string-representation of this value.
+   *
+   * To obtain the actual string value wrapped in a SafeStyleSheet, use
+   * {@code goog.html.SafeStyleSheet.unwrap}.
+   *
+   * @see goog.html.SafeStyleSheet#unwrap
+   * @override
+   */
+  goog.html.SafeStyleSheet.prototype.toString = function() {
+    return 'SafeStyleSheet{' +
+        this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}';
+  };
+}
+
+
 /**
- * @private
- */
-ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() {
-  var i, ii;
-  if (goog.isNull(this.geometries_)) {
-    return;
-  }
-  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
-    goog.events.listen(
-        this.geometries_[i], goog.events.EventType.CHANGE,
-        this.dispatchChangeEvent, false, this);
+ * Performs a runtime check that the provided object is indeed a
+ * SafeStyleSheet object, and returns its value.
+ *
+ * @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from.
+ * @return {string} The safeStyleSheet object's contained string, unless
+ *     the run-time type check fails. In that case, {@code unwrap} returns an
+ *     innocuous string, or, if assertions are enabled, throws
+ *     {@code goog.asserts.AssertionError}.
+ */
+goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) {
+  // Perform additional Run-time type-checking to ensure that
+  // safeStyleSheet is indeed an instance of the expected type.  This
+  // provides some additional protection against security bugs due to
+  // application code that disables type checks.
+  // Specifically, the following checks are performed:
+  // 1. The object is an instance of the expected type.
+  // 2. The object is not an instance of a subclass.
+  // 3. The object carries a type marker for the expected type. "Faking" an
+  // object requires a reference to the type marker, which has names intended
+  // to stand out in code reviews.
+  if (safeStyleSheet instanceof goog.html.SafeStyleSheet &&
+      safeStyleSheet.constructor === goog.html.SafeStyleSheet &&
+      safeStyleSheet.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
+          goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
+    return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
+  } else {
+    goog.asserts.fail(
+        "expected object of type SafeStyleSheet, got '" + safeStyleSheet +
+        "'");
+    return 'type_error:SafeStyleSheet';
   }
 };
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.GeometryCollection} Clone.
- * @api stable
+ * Package-internal utility method to create SafeStyleSheet instances.
+ *
+ * @param {string} styleSheet The string to initialize the SafeStyleSheet
+ *     object with.
+ * @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object.
+ * @package
  */
-ol.geom.GeometryCollection.prototype.clone = function() {
-  var geometryCollection = new ol.geom.GeometryCollection(null);
-  geometryCollection.setGeometries(this.geometries_);
-  return geometryCollection;
+goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse =
+    function(styleSheet) {
+  var safeStyleSheet = new goog.html.SafeStyleSheet();
+  safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ =
+      styleSheet;
+  return safeStyleSheet;
 };
 
 
 /**
- * @inheritDoc
+ * A SafeStyleSheet instance corresponding to the empty string.
+ * @const {!goog.html.SafeStyleSheet}
  */
-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;
-};
+goog.html.SafeStyleSheet.EMPTY =
+    goog.html.SafeStyleSheet.
+        createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @inheritDoc
+ * @fileoverview Unchecked conversions to create values of goog.html types from
+ * plain strings.  Use of these functions could potentially result in instances
+ * of goog.html types that violate their type contracts, and hence result in
+ * security vulnerabilties.
+ *
+ * Therefore, all uses of the methods herein must be carefully security
+ * reviewed.  Avoid use of the methods in this file whenever possible; instead
+ * prefer to create instances of goog.html types using inherently safe builders
+ * or template systems.
+ *
+ *
+ * @visibility {//closure/goog/html:approved_for_unchecked_conversion}
+ * @visibility {//closure/goog/bin/sizetests:__pkg__}
  */
-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;
+
+
+goog.provide('goog.html.uncheckedconversions');
+
+goog.require('goog.asserts');
+goog.require('goog.html.SafeHtml');
+goog.require('goog.html.SafeScript');
+goog.require('goog.html.SafeStyle');
+goog.require('goog.html.SafeStyleSheet');
+goog.require('goog.html.SafeUrl');
+goog.require('goog.html.TrustedResourceUrl');
+goog.require('goog.string');
+goog.require('goog.string.Const');
+
+
+/**
+ * Performs an "unchecked conversion" to SafeHtml from a plain string that is
+ * known to satisfy the SafeHtml type contract.
+ *
+ * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
+ * that the value of {@code html} satisfies the SafeHtml type contract in all
+ * possible program states.
+ *
+ *
+ * @param {!goog.string.Const} justification A constant string explaining why
+ *     this use of this method is safe. May include a security review ticket
+ *     number.
+ * @param {string} html A string that is claimed to adhere to the SafeHtml
+ *     contract.
+ * @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the
+ *     SafeHtml to be constructed. A null or undefined value signifies an
+ *     unknown directionality.
+ * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml
+ *     object.
+ * @suppress {visibility} For access to SafeHtml.create...  Note that this
+ *     use is appropriate since this method is intended to be "package private"
+ *     withing goog.html.  DO NOT call SafeHtml.create... from outside this
+ *     package; use appropriate wrappers instead.
+ */
+goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract =
+    function(justification, html, opt_dir) {
+  // unwrap() called inside an assert so that justification can be optimized
+  // away in production code.
+  goog.asserts.assertString(goog.string.Const.unwrap(justification),
+                            'must provide justification');
+  goog.asserts.assert(
+      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
+      'must provide non-empty justification');
+  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
+      html, opt_dir || null);
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Performs an "unchecked conversion" to SafeScript from a plain string that is
+ * known to satisfy the SafeScript type contract.
+ *
+ * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
+ * that the value of {@code script} satisfies the SafeScript type contract in
+ * all possible program states.
+ *
+ *
+ * @param {!goog.string.Const} justification A constant string explaining why
+ *     this use of this method is safe. May include a security review ticket
+ *     number.
+ * @param {string} script The string to wrap as a SafeScript.
+ * @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a
+ *     SafeScript object.
  */
-ol.geom.GeometryCollection.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    var extent = ol.extent.createOrUpdateEmpty(this.extent);
-    var geometries = this.geometries_;
-    var i, ii;
-    for (i = 0, ii = geometries.length; i < ii; ++i) {
-      ol.extent.extend(extent, geometries[i].getExtent());
-    }
-    this.extent = extent;
-    this.extentRevision = this.getRevision();
-  }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
+goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract =
+    function(justification, script) {
+  // unwrap() called inside an assert so that justification can be optimized
+  // away in production code.
+  goog.asserts.assertString(goog.string.Const.unwrap(justification),
+                            'must provide justification');
+  goog.asserts.assert(
+      !goog.string.isEmpty(goog.string.Const.unwrap(justification)),
+      'must provide non-empty justification');
+  return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
+      script);
 };
 
 
 /**
- * @return {Array.<ol.geom.Geometry>} Geometries.
- * @api stable
+ * Performs an "unchecked conversion" to SafeStyle from a plain string that is
+ * known to satisfy the SafeStyle type contract.
+ *
+ * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
+ * that the value of {@code style} satisfies the SafeUrl type contract in all
+ * possible program states.
+ *
+ *
+ * @param {!goog.string.Const} justification A constant string explaining why
+ *     this use of this method is safe. May include a security review ticket
+ *     number.
+ * @param {string} style The string to wrap as a SafeStyle.
+ * @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a
+ *     SafeStyle object.
  */
-ol.geom.GeometryCollection.prototype.getGeometries = function() {
-  return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_);
+goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract =
+    function(justification, style) {
+  // unwrap() called inside an assert so that justification can be optimized
+  // away in production code.
+  goog.asserts.assertString(goog.string.Const.unwrap(justification),
+                            'must provide justification');
+  goog.asserts.assert(
+      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
+      'must provide non-empty justification');
+  return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
+      style);
 };
 
 
 /**
- * @return {Array.<ol.geom.Geometry>} Geometries.
+ * Performs an "unchecked conversion" to SafeStyleSheet from a plain string
+ * that is known to satisfy the SafeStyleSheet type contract.
+ *
+ * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
+ * that the value of {@code styleSheet} satisfies the SafeUrl type contract in
+ * all possible program states.
+ *
+ *
+ * @param {!goog.string.Const} justification A constant string explaining why
+ *     this use of this method is safe. May include a security review ticket
+ *     number.
+ * @param {string} styleSheet The string to wrap as a SafeStyleSheet.
+ * @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped
+ *     in a SafeStyleSheet object.
  */
-ol.geom.GeometryCollection.prototype.getGeometriesArray = function() {
-  return this.geometries_;
+goog.html.uncheckedconversions.
+    safeStyleSheetFromStringKnownToSatisfyTypeContract =
+    function(justification, styleSheet) {
+  // unwrap() called inside an assert so that justification can be optimized
+  // away in production code.
+  goog.asserts.assertString(goog.string.Const.unwrap(justification),
+                            'must provide justification');
+  goog.asserts.assert(
+      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
+      'must provide non-empty justification');
+  return goog.html.SafeStyleSheet.
+      createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
 };
 
 
 /**
- * @inheritDoc
+ * Performs an "unchecked conversion" to SafeUrl from a plain string that is
+ * known to satisfy the SafeUrl type contract.
+ *
+ * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
+ * that the value of {@code url} satisfies the SafeUrl type contract in all
+ * possible program states.
+ *
+ *
+ * @param {!goog.string.Const} justification A constant string explaining why
+ *     this use of this method is safe. May include a security review ticket
+ *     number.
+ * @param {string} url The string to wrap as a SafeUrl.
+ * @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl
+ *     object.
  */
-ol.geom.GeometryCollection.prototype.getSimplifiedGeometry =
-    function(squaredTolerance) {
-  if (this.simplifiedGeometryRevision != this.getRevision()) {
-    goog.object.clear(this.simplifiedGeometryCache);
-    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
-    this.simplifiedGeometryRevision = this.getRevision();
-  }
-  if (squaredTolerance < 0 ||
-      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
-       squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) {
-    return this;
-  }
-  var key = squaredTolerance.toString();
-  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
-    return this.simplifiedGeometryCache[key];
-  } else {
-    var simplifiedGeometries = [];
-    var geometries = this.geometries_;
-    var simplified = false;
-    var i, ii;
-    for (i = 0, ii = geometries.length; i < ii; ++i) {
-      var geometry = geometries[i];
-      var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
-      simplifiedGeometries.push(simplifiedGeometry);
-      if (simplifiedGeometry !== geometry) {
-        simplified = true;
-      }
-    }
-    if (simplified) {
-      var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null);
-      simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries);
-      this.simplifiedGeometryCache[key] = simplifiedGeometryCollection;
-      return simplifiedGeometryCollection;
-    } else {
-      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
-      return this;
-    }
-  }
+goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract =
+    function(justification, url) {
+  // unwrap() called inside an assert so that justification can be optimized
+  // away in production code.
+  goog.asserts.assertString(goog.string.Const.unwrap(justification),
+                            'must provide justification');
+  goog.asserts.assert(
+      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
+      'must provide non-empty justification');
+  return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Performs an "unchecked conversion" to TrustedResourceUrl from a plain string
+ * that is known to satisfy the TrustedResourceUrl type contract.
+ *
+ * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
+ * that the value of {@code url} satisfies the TrustedResourceUrl type contract
+ * in all possible program states.
+ *
+ *
+ * @param {!goog.string.Const} justification A constant string explaining why
+ *     this use of this method is safe. May include a security review ticket
+ *     number.
+ * @param {string} url The string to wrap as a TrustedResourceUrl.
+ * @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in
+ *     a TrustedResourceUrl object.
  */
-ol.geom.GeometryCollection.prototype.getType = function() {
-  return ol.geom.GeometryType.GEOMETRY_COLLECTION;
+goog.html.uncheckedconversions.
+    trustedResourceUrlFromStringKnownToSatisfyTypeContract =
+    function(justification, url) {
+  // unwrap() called inside an assert so that justification can be optimized
+  // away in production code.
+  goog.asserts.assertString(goog.string.Const.unwrap(justification),
+                            'must provide justification');
+  goog.asserts.assert(
+      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
+      'must provide non-empty justification');
+  return goog.html.TrustedResourceUrl.
+      createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
 };
 
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @return {boolean} Is empty.
+ * @fileoverview Defines the collection interface.
+ *
+ * @author nnaze@google.com (Nathan Naze)
  */
-ol.geom.GeometryCollection.prototype.isEmpty = function() {
-  return goog.array.isEmpty(this.geometries_);
-};
+
+goog.provide('goog.structs.Collection');
+
 
 
 /**
- * @param {Array.<ol.geom.Geometry>} geometries Geometries.
- * @api stable
+ * An interface for a collection of values.
+ * @interface
+ * @template T
  */
-ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) {
-  this.setGeometriesArray(
-      ol.geom.GeometryCollection.cloneGeometries_(geometries));
-};
+goog.structs.Collection = function() {};
 
 
 /**
- * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ * @param {T} value Value to add to the collection.
  */
-ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) {
-  this.unlistenGeometriesChange_();
-  this.geometries_ = geometries;
-  this.listenGeometriesChange_();
-  this.dispatchChangeEvent();
-};
+goog.structs.Collection.prototype.add;
 
 
 /**
- * @inheritDoc
+ * @param {T} value Value to remove from the collection.
  */
-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.dispatchChangeEvent();
-};
+goog.structs.Collection.prototype.remove;
 
 
 /**
- * @inheritDoc
+ * @param {T} value Value to find in the collection.
+ * @return {boolean} Whether the collection contains the specified value.
  */
-ol.geom.GeometryCollection.prototype.disposeInternal = function() {
-  this.unlistenGeometriesChange_();
-  goog.base(this, 'disposeInternal');
-};
+goog.structs.Collection.prototype.contains;
 
-goog.provide('ol.math');
 
-goog.require('goog.asserts');
+/**
+ * @return {number} The number of values stored in the collection.
+ */
+goog.structs.Collection.prototype.getCount;
+
 
+// 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.
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic cosine of x.
+ * @fileoverview Datastructure: Set.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ *
+ * This class implements a set data structure. Adding and removing is O(1). It
+ * supports both object and primitive values. Be careful because you can add
+ * both 1 and new Number(1), because these are not the same. You can even add
+ * multiple new Number(1) because these are not equal.
  */
-ol.math.cosh = function(x) {
-  return (Math.exp(x) + Math.exp(-x)) / 2;
-};
+
+
+goog.provide('goog.structs.Set');
+
+goog.require('goog.structs');
+goog.require('goog.structs.Collection');
+goog.require('goog.structs.Map');
+
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic cotangent of x.
+ * A set that can contain both primitives and objects.  Adding and removing
+ * elements is O(1).  Primitives are treated as identical if they have the same
+ * type and convert to the same string.  Objects are treated as identical only
+ * if they are references to the same object.  WARNING: A goog.structs.Set can
+ * contain both 1 and (new Number(1)), because they are not the same.  WARNING:
+ * Adding (new Number(1)) twice will yield two distinct elements, because they
+ * are two different objects.  WARNING: Any object that is added to a
+ * goog.structs.Set will be modified!  Because goog.getUid() is used to
+ * identify objects, every object in the set will be mutated.
+ * @param {Array<T>|Object<?,T>=} opt_values Initial values to start with.
+ * @constructor
+ * @implements {goog.structs.Collection<T>}
+ * @final
+ * @template T
  */
-ol.math.coth = function(x) {
-  var expMinusTwoX = Math.exp(-2 * x);
-  return (1 + expMinusTwoX) / (1 - expMinusTwoX);
+goog.structs.Set = function(opt_values) {
+  this.map_ = new goog.structs.Map;
+  if (opt_values) {
+    this.addAll(opt_values);
+  }
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic cosecant of x.
+ * Obtains a unique key for an element of the set.  Primitives will yield the
+ * same key if they have the same type and convert to the same string.  Object
+ * references will yield the same key only if they refer to the same object.
+ * @param {*} val Object or primitive value to get a key for.
+ * @return {string} A unique key for this value/object.
+ * @private
  */
-ol.math.csch = function(x) {
-  return 2 / (Math.exp(x) - Math.exp(-x));
+goog.structs.Set.getKey_ = function(val) {
+  var type = typeof val;
+  if (type == 'object' && val || type == 'function') {
+    return 'o' + goog.getUid(/** @type {Object} */ (val));
+  } else {
+    return type.substr(0, 1) + val;
+  }
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} The smallest power of two greater than or equal to x.
+ * @return {number} The number of elements in the set.
+ * @override
  */
-ol.math.roundUpToPowerOfTwo = function(x) {
-  goog.asserts.assert(0 < x);
-  return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
+goog.structs.Set.prototype.getCount = function() {
+  return this.map_.getCount();
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic secant of x.
+ * Add a primitive or an object to the set.
+ * @param {T} element The primitive or object to add.
+ * @override
  */
-ol.math.sech = function(x) {
-  return 2 / (Math.exp(x) + Math.exp(-x));
+goog.structs.Set.prototype.add = function(element) {
+  this.map_.set(goog.structs.Set.getKey_(element), element);
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic sine of x.
+ * Adds all the values in the given collection to this set.
+ * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
+ *     containing the elements to add.
  */
-ol.math.sinh = function(x) {
-  return (Math.exp(x) - Math.exp(-x)) / 2;
+goog.structs.Set.prototype.addAll = function(col) {
+  var values = goog.structs.getValues(col);
+  var l = values.length;
+  for (var i = 0; i < l; i++) {
+    this.add(values[i]);
+  }
 };
 
 
 /**
- * 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.
+ * Removes all values in the given collection from this set.
+ * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
+ *     containing the elements to remove.
  */
-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;
-    }
+goog.structs.Set.prototype.removeAll = function(col) {
+  var values = goog.structs.getValues(col);
+  var l = values.length;
+  for (var i = 0; i < l; i++) {
+    this.remove(values[i]);
   }
-  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.
+ * Removes the given element from this set.
+ * @param {T} element The primitive or object to remove.
+ * @return {boolean} Whether the element was found and removed.
+ * @override
  */
-ol.math.squaredDistance = function(x1, y1, x2, y2) {
-  var dx = x2 - x1;
-  var dy = y2 - y1;
-  return dx * dx + dy * dy;
+goog.structs.Set.prototype.remove = function(element) {
+  return this.map_.remove(goog.structs.Set.getKey_(element));
 };
 
 
 /**
- * @param {number} x X.
- * @return {number} Hyperbolic tangent of x.
+ * Removes all elements from this set.
  */
-ol.math.tanh = function(x) {
-  var expMinusTwoX = Math.exp(-2 * x);
-  return (1 - expMinusTwoX) / (1 + expMinusTwoX);
+goog.structs.Set.prototype.clear = function() {
+  this.map_.clear();
 };
 
-goog.provide('ol.geom.flat.closest');
 
-goog.require('goog.asserts');
-goog.require('goog.math');
-goog.require('ol.math');
+/**
+ * Tests whether this set is empty.
+ * @return {boolean} True if there are no elements in this set.
+ */
+goog.structs.Set.prototype.isEmpty = function() {
+  return this.map_.isEmpty();
+};
 
 
 /**
- * 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.
+ * Tests whether this set contains the given element.
+ * @param {T} element The primitive or object to test for.
+ * @return {boolean} True if this set contains the given element.
+ * @override
  */
-ol.geom.flat.closest.point =
-    function(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) {
-  var x1 = flatCoordinates[offset1];
-  var y1 = flatCoordinates[offset1 + 1];
-  var dx = flatCoordinates[offset2] - x1;
-  var dy = flatCoordinates[offset2 + 1] - y1;
-  var i, offset;
-  if (dx === 0 && dy === 0) {
-    offset = offset1;
-  } else {
-    var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
-    if (t > 1) {
-      offset = offset2;
-    } else if (t > 0) {
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = goog.math.lerp(flatCoordinates[offset1 + i],
-            flatCoordinates[offset2 + i], t);
-      }
-      closestPoint.length = stride;
-      return;
-    } else {
-      offset = offset1;
-    }
-  }
-  for (i = 0; i < stride; ++i) {
-    closestPoint[i] = flatCoordinates[offset + i];
-  }
-  closestPoint.length = stride;
+goog.structs.Set.prototype.contains = function(element) {
+  return this.map_.containsKey(goog.structs.Set.getKey_(element));
 };
 
 
 /**
- * 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.
+ * Tests whether this set contains all the values in a given collection.
+ * Repeated elements in the collection are ignored, e.g.  (new
+ * goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
+ * @param {goog.structs.Collection<T>|Object} col A collection-like object.
+ * @return {boolean} True if the set contains all elements.
  */
-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;
+goog.structs.Set.prototype.containsAll = function(col) {
+  return goog.structs.every(col, this.contains, this);
 };
 
 
 /**
- * @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.
+ * Finds all values that are present in both this set and the given collection.
+ * @param {Array<S>|Object<?,S>} col A collection.
+ * @return {!goog.structs.Set<T|S>} A new set containing all the values
+ *     (primitives or objects) present in both this set and the given
+ *     collection.
+ * @template S
  */
-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;
+goog.structs.Set.prototype.intersection = function(col) {
+  var result = new goog.structs.Set();
+
+  var values = goog.structs.getValues(col);
+  for (var i = 0; i < values.length; i++) {
+    var value = values[i];
+    if (this.contains(value)) {
+      result.add(value);
+    }
   }
-  return maxSquaredDelta;
+
+  return result;
 };
 
 
 /**
- * @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.
+ * Finds all values that are present in this set and not in the given
+ * collection.
+ * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection.
+ * @return {!goog.structs.Set} A new set containing all the values
+ *     (primitives or objects) present in this set but not in the given
+ *     collection.
  */
-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;
+goog.structs.Set.prototype.difference = function(col) {
+  var result = this.clone();
+  result.removeAll(col);
+  return result;
 };
 
 
 /**
- * @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.
+ * Returns an array containing all the elements in this set.
+ * @return {!Array<T>} An array containing all the elements in this set.
  */
-ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end,
-    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
-    opt_tmpPoint) {
-  if (offset == end) {
-    return minSquaredDistance;
-  }
-  var i, squaredDistance;
-  if (maxDelta === 0) {
-    // All points are identical, so just test the first point.
-    squaredDistance = ol.math.squaredDistance(
-        x, y, flatCoordinates[offset], flatCoordinates[offset + 1]);
-    if (squaredDistance < minSquaredDistance) {
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = flatCoordinates[offset + i];
-      }
-      closestPoint.length = stride;
-      return squaredDistance;
-    } else {
-      return minSquaredDistance;
-    }
-  }
-  goog.asserts.assert(maxDelta > 0);
-  var tmpPoint = goog.isDef(opt_tmpPoint) ? opt_tmpPoint : [NaN, NaN];
-  var index = offset + stride;
-  while (index < end) {
-    ol.geom.flat.closest.point(
-        flatCoordinates, index - stride, index, stride, x, y, tmpPoint);
-    squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]);
-    if (squaredDistance < minSquaredDistance) {
-      minSquaredDistance = squaredDistance;
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = tmpPoint[i];
-      }
-      closestPoint.length = stride;
-      index += stride;
-    } else {
-      // Skip ahead multiple points, because we know that all the skipped
-      // points cannot be any closer than the closest point we have found so
-      // far.  We know this because we know how close the current point is, how
-      // close the closest point we have found so far is, and the maximum
-      // distance between consecutive points.  For example, if we're currently
-      // at distance 10, the best we've found so far is 3, and that the maximum
-      // distance between consecutive points is 2, then we'll need to skip at
-      // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of
-      // finding a closer point.  We use Math.max(..., 1) to ensure that we
-      // always advance at least one point, to avoid an infinite loop.
-      index += stride * Math.max(
-          ((Math.sqrt(squaredDistance) -
-            Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1);
-    }
-  }
-  if (isRing) {
-    // Check the closing segment.
-    ol.geom.flat.closest.point(
-        flatCoordinates, end - stride, offset, stride, x, y, tmpPoint);
-    squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]);
-    if (squaredDistance < minSquaredDistance) {
-      minSquaredDistance = squaredDistance;
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = tmpPoint[i];
-      }
-      closestPoint.length = stride;
-    }
-  }
-  return minSquaredDistance;
+goog.structs.Set.prototype.getValues = function() {
+  return this.map_.getValues();
 };
 
 
 /**
- * @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.
+ * Creates a shallow clone of this set.
+ * @return {!goog.structs.Set<T>} A new set containing all the same elements as
+ *     this set.
  */
-ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends,
-    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
-    opt_tmpPoint) {
-  var tmpPoint = goog.isDef(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;
+goog.structs.Set.prototype.clone = function() {
+  return new goog.structs.Set(this);
 };
 
 
 /**
- * @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.
+ * Tests whether the given collection consists of the same elements as this set,
+ * regardless of order, without repetition.  Primitives are treated as equal if
+ * they have the same type and convert to the same string; objects are treated
+ * as equal if they are references to the same object.  This operation is O(n).
+ * @param {goog.structs.Collection<T>|Object} col A collection.
+ * @return {boolean} True if the given collection consists of the same elements
+ *     as this set, regardless of order, without repetition.
  */
-ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset,
-    endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
-    opt_tmpPoint) {
-  var tmpPoint = goog.isDef(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.structs.Set.prototype.equals = function(col) {
+  return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
 };
 
-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.
+ * Tests whether the given collection contains all the elements in this set.
+ * Primitives are treated as equal if they have the same type and convert to the
+ * same string; objects are treated as equal if they are references to the same
+ * object.  This operation is O(n).
+ * @param {goog.structs.Collection<T>|Object} col A collection.
+ * @return {boolean} True if this set is a subset of the given collection.
  */
-ol.geom.flat.inflate.coordinates =
-    function(flatCoordinates, offset, end, stride, opt_coordinates) {
-  var coordinates = goog.isDef(opt_coordinates) ? opt_coordinates : [];
-  var i = 0;
-  var j;
-  for (j = offset; j < end; j += stride) {
-    coordinates[i++] = flatCoordinates.slice(j, j + stride);
+goog.structs.Set.prototype.isSubsetOf = function(col) {
+  var colCount = goog.structs.getCount(col);
+  if (this.getCount() > colCount) {
+    return false;
   }
-  coordinates.length = i;
-  return coordinates;
+  // TODO(user) Find the minimal collection size where the conversion makes
+  // the contains() method faster.
+  if (!(col instanceof goog.structs.Set) && colCount > 5) {
+    // Convert to a goog.structs.Set so that goog.structs.contains runs in
+    // O(1) time instead of O(n) time.
+    col = new goog.structs.Set(col);
+  }
+  return goog.structs.every(this, function(value) {
+    return goog.structs.contains(col, value);
+  });
 };
 
 
 /**
- * @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.
+ * Returns an iterator that iterates over the elements in this set.
+ * @param {boolean=} opt_keys This argument is ignored.
+ * @return {!goog.iter.Iterator} An iterator over the elements in this set.
  */
-ol.geom.flat.inflate.coordinatess =
-    function(flatCoordinates, offset, ends, stride, opt_coordinatess) {
-  var coordinatess = goog.isDef(opt_coordinatess) ? 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;
+goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
+  return this.map_.__iterator__(false);
 };
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @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.
+ * @fileoverview Logging and debugging utilities.
+ *
+ * @see ../demos/debug.html
  */
-ol.geom.flat.inflate.coordinatesss =
-    function(flatCoordinates, offset, endss, stride, opt_coordinatesss) {
-  var coordinatesss = goog.isDef(opt_coordinatesss) ? 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;
-};
 
-goog.provide('ol.geom.flat.interpolate');
+goog.provide('goog.debug');
 
 goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.math');
+goog.require('goog.html.SafeHtml');
+goog.require('goog.html.SafeUrl');
+goog.require('goog.html.uncheckedconversions');
+goog.require('goog.string.Const');
+goog.require('goog.structs.Set');
+goog.require('goog.userAgent');
+
+
+/** @define {boolean} Whether logging should be enabled. */
+goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
 
 
 /**
- * @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.
+ * Catches onerror events fired by windows and similar objects.
+ * @param {function(Object)} logFunc The function to call with the error
+ *    information.
+ * @param {boolean=} opt_cancel Whether to stop the error from reaching the
+ *    browser.
+ * @param {Object=} opt_target Object that fires onerror events.
  */
-ol.geom.flat.interpolate.lineString =
-    function(flatCoordinates, offset, end, stride, fraction, opt_dest) {
-  // FIXME interpolate extra dimensions
-  goog.asserts.assert(0 <= fraction && fraction <= 1);
-  var pointX = NaN;
-  var pointY = NaN;
-  var n = (end - offset) / stride;
-  if (n === 0) {
-    goog.asserts.fail();
-  } else if (n == 1) {
-    pointX = flatCoordinates[offset];
-    pointY = flatCoordinates[offset + 1];
-  } else if (n == 2) {
-    pointX = (1 - fraction) * flatCoordinates[offset] +
-        fraction * flatCoordinates[offset + stride];
-    pointY = (1 - fraction) * flatCoordinates[offset + 1] +
-        fraction * flatCoordinates[offset + stride + 1];
-  } else {
-    var x1 = flatCoordinates[offset];
-    var y1 = flatCoordinates[offset + 1];
-    var length = 0;
-    var cumulativeLengths = [0];
-    var i;
-    for (i = offset + stride; i < end; i += stride) {
-      var x2 = flatCoordinates[i];
-      var y2 = flatCoordinates[i + 1];
-      length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
-      cumulativeLengths.push(length);
-      x1 = x2;
-      y1 = y2;
-    }
-    var target = fraction * length;
-    var index = goog.array.binarySearch(cumulativeLengths, target);
-    if (index < 0) {
-      var t = (target - cumulativeLengths[-index - 2]) /
-          (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]);
-      var o = offset + (-index - 2) * stride;
-      pointX = goog.math.lerp(
-          flatCoordinates[o], flatCoordinates[o + stride], t);
-      pointY = goog.math.lerp(
-          flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t);
-    } else {
-      pointX = flatCoordinates[offset + index * stride];
-      pointY = flatCoordinates[offset + index * stride + 1];
-    }
-  }
-  if (goog.isDefAndNotNull(opt_dest)) {
-    opt_dest[0] = pointX;
-    opt_dest[1] = pointY;
-    return opt_dest;
-  } else {
-    return [pointX, pointY];
+goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
+  var target = opt_target || goog.global;
+  var oldErrorHandler = target.onerror;
+  var retVal = !!opt_cancel;
+
+  // Chrome interprets onerror return value backwards (http://crbug.com/92062)
+  // until it was fixed in webkit revision r94061 (Webkit 535.3). This
+  // workaround still needs to be skipped in Safari after the webkit change
+  // gets pushed out in Safari.
+  // See https://bugs.webkit.org/show_bug.cgi?id=67119
+  if (goog.userAgent.WEBKIT &&
+      !goog.userAgent.isVersionOrHigher('535.3')) {
+    retVal = !retVal;
   }
+
+  /**
+   * New onerror handler for this target. This onerror handler follows the spec
+   * according to
+   * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
+   * The spec was changed in August 2013 to support receiving column information
+   * and an error object for all scripts on the same origin or cross origin
+   * scripts with the proper headers. See
+   * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
+   *
+   * @param {string} message The error message. For cross-origin errors, this
+   *     will be scrubbed to just "Script error.". For new browsers that have
+   *     updated to follow the latest spec, errors that come from origins that
+   *     have proper cross origin headers will not be scrubbed.
+   * @param {string} url The URL of the script that caused the error. The URL
+   *     will be scrubbed to "" for cross origin scripts unless the script has
+   *     proper cross origin headers and the browser has updated to the latest
+   *     spec.
+   * @param {number} line The line number in the script that the error
+   *     occurred on.
+   * @param {number=} opt_col The optional column number that the error
+   *     occurred on. Only browsers that have updated to the latest spec will
+   *     include this.
+   * @param {Error=} opt_error The optional actual error object for this
+   *     error that should include the stack. Only browsers that have updated
+   *     to the latest spec will inlude this parameter.
+   * @return {boolean} Whether to prevent the error from reaching the browser.
+   */
+  target.onerror = function(message, url, line, opt_col, opt_error) {
+    if (oldErrorHandler) {
+      oldErrorHandler(message, url, line, opt_col, opt_error);
+    }
+    logFunc({
+      message: message,
+      fileName: url,
+      line: line,
+      col: opt_col,
+      error: opt_error
+    });
+    return retVal;
+  };
 };
 
 
 /**
- * @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.
+ * Creates a string representing an object and all its properties.
+ * @param {Object|null|undefined} obj Object to expose.
+ * @param {boolean=} opt_showFn Show the functions as well as the properties,
+ *     default is false.
+ * @return {string} The string representation of {@code obj}.
  */
-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;
-    }
+goog.debug.expose = function(obj, opt_showFn) {
+  if (typeof obj == 'undefined') {
+    return 'undefined';
   }
-  // FIXME use O(1) search
-  if (m == flatCoordinates[offset + stride - 1]) {
-    return flatCoordinates.slice(offset, offset + stride);
+  if (obj == null) {
+    return 'NULL';
   }
-  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 str = [];
+
+  for (var x in obj) {
+    if (!opt_showFn && goog.isFunction(obj[x])) {
+      continue;
     }
+    var s = x + ' = ';
+    /** @preserveTry */
+    try {
+      s += obj[x];
+    } catch (e) {
+      s += '*** ' + e + ' ***';
+    }
+    str.push(s);
   }
-  var m0 = flatCoordinates[lo * stride - 1];
-  if (m == m0) {
-    return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride);
-  }
-  var m1 = flatCoordinates[(lo + 1) * stride - 1];
-  goog.asserts.assert(m0 < m);
-  goog.asserts.assert(m <= m1);
-  var t = (m - m0) / (m1 - m0);
-  coordinate = [];
-  var i;
-  for (i = 0; i < stride - 1; ++i) {
-    coordinate.push(goog.math.lerp(flatCoordinates[(lo - 1) * stride + i],
-        flatCoordinates[lo * stride + i], t));
-  }
-  coordinate.push(m);
-  goog.asserts.assert(coordinate.length == stride);
-  return coordinate;
+  return str.join('\n');
 };
 
 
 /**
- * @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.
+ * Creates a string representing a given primitive or object, and for an
+ * object, all its properties and nested objects.  WARNING: If an object is
+ * given, it and all its nested objects will be modified.  To detect reference
+ * cycles, this method identifies objects using goog.getUid() which mutates the
+ * object.
+ * @param {*} obj Object to expose.
+ * @param {boolean=} opt_showFn Also show properties that are functions (by
+ *     default, functions are omitted).
+ * @return {string} A string representation of {@code obj}.
  */
-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;
+goog.debug.deepExpose = function(obj, opt_showFn) {
+  var str = [];
+
+  var helper = function(obj, space, parentSeen) {
+    var nestspace = space + '  ';
+    var seen = new goog.structs.Set(parentSeen);
+
+    var indentMultiline = function(str) {
+      return str.replace(/\n/g, '\n' + space);
+    };
+
+    /** @preserveTry */
+    try {
+      if (!goog.isDef(obj)) {
+        str.push('undefined');
+      } else if (goog.isNull(obj)) {
+        str.push('NULL');
+      } else if (goog.isString(obj)) {
+        str.push('"' + indentMultiline(obj) + '"');
+      } else if (goog.isFunction(obj)) {
+        str.push(indentMultiline(String(obj)));
+      } else if (goog.isObject(obj)) {
+        if (seen.contains(obj)) {
+          str.push('*** reference loop detected ***');
+        } else {
+          seen.add(obj);
+          str.push('{');
+          for (var x in obj) {
+            if (!opt_showFn && goog.isFunction(obj[x])) {
+              continue;
+            }
+            str.push('\n');
+            str.push(nestspace);
+            str.push(x + ' = ');
+            helper(obj[x], nestspace, seen);
+          }
+          str.push('\n' + space + '}');
+        }
+      } else {
+        str.push(obj);
+      }
+    } catch (e) {
+      str.push('*** ' + e + ' ***');
     }
-  }
-  if (flatCoordinates[flatCoordinates.length - 1] < m) {
-    if (extrapolate) {
-      coordinate = flatCoordinates.slice(flatCoordinates.length - stride);
-      coordinate[stride - 1] = m;
-      return coordinate;
+  };
+
+  helper(obj, '', new goog.structs.Set());
+  return str.join('');
+};
+
+
+/**
+ * Recursively outputs a nested array as a string.
+ * @param {Array<?>} arr The array.
+ * @return {string} String representing nested array.
+ */
+goog.debug.exposeArray = function(arr) {
+  var str = [];
+  for (var i = 0; i < arr.length; i++) {
+    if (goog.isArray(arr[i])) {
+      str.push(goog.debug.exposeArray(arr[i]));
     } else {
-      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);
+      str.push(arr[i]);
     }
-    offset = end;
   }
-  goog.asserts.fail();
-  return null;
+  return '[ ' + str.join(', ') + ' ]';
 };
 
-goog.provide('ol.geom.flat.length');
+
+/**
+ * Exposes an exception that has been caught by a try...catch and outputs the
+ * error as HTML with a stack trace.
+ * @param {Object} err Error object or string.
+ * @param {Function=} opt_fn Optional function to start stack trace from.
+ * @return {string} Details of exception, as HTML.
+ */
+goog.debug.exposeException = function(err, opt_fn) {
+  var html = goog.debug.exposeExceptionAsHtml(err, opt_fn);
+  return goog.html.SafeHtml.unwrap(html);
+};
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {number} Length.
+ * Exposes an exception that has been caught by a try...catch and outputs the
+ * error with a stack trace.
+ * @param {Object} err Error object or string.
+ * @param {Function=} opt_fn Optional function to start stack trace from.
+ * @return {!goog.html.SafeHtml} Details of exception.
  */
-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;
+goog.debug.exposeExceptionAsHtml = function(err, opt_fn) {
+  /** @preserveTry */
+  try {
+    var e = goog.debug.normalizeErrorObject(err);
+    // Create the error message
+    var viewSourceUrl = goog.debug.createViewSourceUrl_(e.fileName);
+    var error = goog.html.SafeHtml.concat(
+        goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+            'Message: ' + e.message + '\nUrl: '),
+        goog.html.SafeHtml.create('a',
+            {href: viewSourceUrl, target: '_new'}, e.fileName),
+        goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+            '\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
+            e.stack + '-> ' + '[end]\n\nJS stack traversal:\n' +
+            goog.debug.getStacktrace(opt_fn) + '-> '));
+    return error;
+  } catch (e2) {
+    return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+        'Exception trying to expose exception! You win, we lose. ' + e2);
   }
-  return length;
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {number} Perimeter.
+ * @param {?string=} opt_fileName
+ * @return {!goog.html.SafeUrl} SafeUrl with view-source scheme, pointing at
+ *     fileName.
+ * @private
  */
-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.debug.createViewSourceUrl_ = function(opt_fileName) {
+  if (!goog.isDefAndNotNull(opt_fileName)) {
+    opt_fileName = '';
+  }
+  if (!/^https?:\/\//i.test(opt_fileName)) {
+    return goog.html.SafeUrl.fromConstant(
+        goog.string.Const.from('sanitizedviewsrc'));
+  }
+  var sanitizedFileName = goog.html.SafeUrl.sanitize(opt_fileName);
+  return goog.html.uncheckedconversions.
+      safeUrlFromStringKnownToSatisfyTypeContract(
+          goog.string.Const.from('view-source scheme plus HTTP/HTTPS URL'),
+          'view-source:' + goog.html.SafeUrl.unwrap(sanitizedFileName));
 };
 
-// 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.
+ * Normalizes the error/exception object between browsers.
+ * @param {Object} err Raw error object.
+ * @return {!Object} Normalized error object.
  */
-ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end,
-    stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) {
-  var simplifiedFlatCoordinates = goog.isDef(opt_simplifiedFlatCoordinates) ?
-      opt_simplifiedFlatCoordinates : [];
-  if (!highQuality) {
-    end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end,
-        stride, squaredTolerance,
-        simplifiedFlatCoordinates, 0);
-    flatCoordinates = simplifiedFlatCoordinates;
-    offset = 0;
-    stride = 2;
+goog.debug.normalizeErrorObject = function(err) {
+  var href = goog.getObjectByName('window.location.href');
+  if (goog.isString(err)) {
+    return {
+      'message': err,
+      'name': 'Unknown error',
+      'lineNumber': 'Not available',
+      'fileName': href,
+      'stack': 'Not available'
+    };
   }
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
-      flatCoordinates, offset, end, stride, squaredTolerance,
-      simplifiedFlatCoordinates, 0);
-  return simplifiedFlatCoordinates;
-};
 
+  var lineNumber, fileName;
+  var threwError = false;
 
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @return {number} Simplified offset.
- */
-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;
+  try {
+    lineNumber = err.lineNumber || err.line || 'Not available';
+  } catch (e) {
+    // Firefox 2 sometimes throws an error when accessing 'lineNumber':
+    // Message: Permission denied to get property UnnamedClass.lineNumber
+    lineNumber = 'Not available';
+    threwError = true;
   }
-  /** @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);
-      }
-    }
+
+  try {
+    fileName = err.fileName || err.filename || err.sourceURL ||
+        // $googDebugFname may be set before a call to eval to set the filename
+        // that the eval is supposed to present.
+        goog.global['$googDebugFname'] || href;
+  } catch (e) {
+    // Firefox 2 may also throw an error when accessing 'filename'.
+    fileName = 'Not available';
+    threwError = true;
   }
-  for (i = 0; i < n; ++i) {
-    if (markers[i]) {
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + i * stride];
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + i * stride + 1];
-    }
+
+  // The IE Error object contains only the name and the message.
+  // The Safari Error object uses the line and sourceURL fields.
+  if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
+      !err.message || !err.name) {
+    return {
+      'message': err.message || 'Not available',
+      'name': err.name || 'UnknownError',
+      'lineNumber': lineNumber,
+      'fileName': fileName,
+      'stack': err.stack || 'Not available'
+    };
   }
-  return simplifiedOffset;
+
+  // Standards error object
+  return err;
 };
 
 
 /**
- * @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.
+ * Converts an object to an Error if it's a String,
+ * adds a stacktrace if there isn't one,
+ * and optionally adds an extra message.
+ * @param {Error|string} err  the original thrown object or string.
+ * @param {string=} opt_message  optional additional message to add to the
+ *     error.
+ * @return {!Error} If err is a string, it is used to create a new Error,
+ *     which is enhanced and returned.  Otherwise err itself is enhanced
+ *     and returned.
  */
-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;
+goog.debug.enhanceError = function(err, opt_message) {
+  var error;
+  if (typeof err == 'string') {
+    error = Error(err);
+    if (Error.captureStackTrace) {
+      // Trim this function off the call stack, if we can.
+      Error.captureStackTrace(error, goog.debug.enhanceError);
+    }
+  } else {
+    error = err;
   }
-  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];
+  if (!error.stack) {
+    error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
   }
-  return simplifiedOffset;
+  if (opt_message) {
+    // find the first unoccupied 'messageX' property
+    var x = 0;
+    while (error['message' + x]) {
+      ++x;
+    }
+    error['message' + x] = String(opt_message);
+  }
+  return error;
 };
 
 
 /**
- * @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.
+ * Gets the current stack trace. Simple and iterative - doesn't worry about
+ * catching circular references or getting the args.
+ * @param {number=} opt_depth Optional maximum depth to trace back to.
+ * @return {string} A string with the function names of all functions in the
+ *     stack, separated by \n.
+ * @suppress {es5Strict}
  */
-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];
+goog.debug.getStacktraceSimple = function(opt_depth) {
+  if (goog.STRICT_MODE_COMPATIBLE) {
+    var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
+    if (stack) {
+      return stack;
     }
-    return simplifiedOffset;
+    // NOTE: browsers that have strict mode support also have native "stack"
+    // properties.  Fall-through for legacy browser support.
   }
-  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;
+
+  var sb = [];
+  var fn = arguments.callee.caller;
+  var depth = 0;
+
+  while (fn && (!opt_depth || depth < opt_depth)) {
+    sb.push(goog.debug.getFunctionName(fn));
+    sb.push('()\n');
+    /** @preserveTry */
+    try {
+      fn = fn.caller;
+    } catch (e) {
+      sb.push('[exception trying to get caller]\n');
+      break;
+    }
+    depth++;
+    if (depth >= goog.debug.MAX_STACK_DEPTH) {
+      sb.push('[...long stack...]');
+      break;
     }
   }
-  if (x2 != x1 || y2 != y1) {
-    // copy last point
-    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
+  if (opt_depth && depth >= opt_depth) {
+    sb.push('[...reached max depth limit...]');
+  } else {
+    sb.push('[end]');
   }
-  return simplifiedOffset;
+
+  return sb.join('');
 };
 
 
 /**
- * @param {number} value Value.
- * @param {number} tolerance Tolerance.
- * @return {number} Rounded value.
+ * Max length of stack to try and output
+ * @type {number}
  */
-ol.geom.flat.simplify.snap = function(value, tolerance) {
-  return tolerance * Math.round(value / tolerance);
-};
+goog.debug.MAX_STACK_DEPTH = 50;
 
 
 /**
- * Simplifies a line string using an algorithm designed by Tim Schaub.
- * Coordinates are snapped to the nearest value in a virtual grid and
- * consecutive duplicate coordinates are discarded.  This effectively preserves
- * topology as the simplification of any subsection of a line string is
- * independent of the rest of the line string.  This means that, for examples,
- * the common edge between two polygons will be simplified to the same line
- * string independently in both polygons.  This implementation uses a single
- * pass over the coordinates and eliminates intermediate collinear points.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} tolerance Tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @return {number} Simplified offset.
+ * @param {Function} fn The function to start getting the trace from.
+ * @return {?string}
+ * @private
  */
-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;
+goog.debug.getNativeStackTrace_ = function(fn) {
+  var tempErr = new Error();
+  if (Error.captureStackTrace) {
+    Error.captureStackTrace(tempErr, fn);
+    return String(tempErr.stack);
+  } else {
+    // IE10, only adds stack traces when an exception is thrown.
+    try {
+      throw tempErr;
+    } catch (e) {
+      tempErr = e;
     }
-    // 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;
+    var stack = tempErr.stack;
+    if (stack) {
+      return String(stack);
     }
-    // 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;
+  return null;
 };
 
 
 /**
- * @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.
+ * Gets the current stack trace, either starting from the caller or starting
+ * from a specified function that's currently on the call stack.
+ * @param {Function=} opt_fn Optional function to start getting the trace from.
+ *     If not provided, defaults to the function that called this.
+ * @return {string} Stack trace.
+ * @suppress {es5Strict}
  */
-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;
+goog.debug.getStacktrace = function(opt_fn) {
+  var stack;
+  if (goog.STRICT_MODE_COMPATIBLE) {
+    // Try to get the stack trace from the environment if it is available.
+    var contextFn = opt_fn || goog.debug.getStacktrace;
+    stack = goog.debug.getNativeStackTrace_(contextFn);
   }
-  return simplifiedOffset;
+  if (!stack) {
+    // NOTE: browsers that have strict mode support also have native "stack"
+    // properties. This function will throw in strict mode.
+    stack = goog.debug.getStacktraceHelper_(
+        opt_fn || arguments.callee.caller, []);
+  }
+  return stack;
 };
 
 
 /**
- * @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.
+ * Private helper for getStacktrace().
+ * @param {Function} fn Function to start getting the trace from.
+ * @param {Array<!Function>} visited List of functions visited so far.
+ * @return {string} Stack trace starting from function fn.
+ * @suppress {es5Strict}
+ * @private
  */
-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.LineString');
-
-goog.require('goog.asserts');
-goog.require('ol.array');
-goog.require('ol.extent');
-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.length');
-goog.require('ol.geom.flat.simplify');
-
-
-
-/**
- * @classdesc
- * Linestring geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.LineString = function(coordinates, opt_layout) {
+goog.debug.getStacktraceHelper_ = function(fn, visited) {
+  var sb = [];
 
-  goog.base(this);
+  // Circular reference, certain functions like bind seem to cause a recursive
+  // loop so we need to catch circular references
+  if (goog.array.contains(visited, fn)) {
+    sb.push('[...circular reference...]');
 
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.flatMidpoint_ = null;
+  // Traverse the call stack until function not found or max depth is reached
+  } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
+    sb.push(goog.debug.getFunctionName(fn) + '(');
+    var args = fn.arguments;
+    // Args may be null for some special functions such as host objects or eval.
+    for (var i = 0; args && i < args.length; i++) {
+      if (i > 0) {
+        sb.push(', ');
+      }
+      var argDesc;
+      var arg = args[i];
+      switch (typeof arg) {
+        case 'object':
+          argDesc = arg ? 'object' : 'null';
+          break;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.flatMidpointRevision_ = -1;
+        case 'string':
+          argDesc = arg;
+          break;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
+        case 'number':
+          argDesc = String(arg);
+          break;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
+        case 'boolean':
+          argDesc = arg ? 'true' : 'false';
+          break;
 
-  this.setCoordinates(coordinates,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
+        case 'function':
+          argDesc = goog.debug.getFunctionName(arg);
+          argDesc = argDesc ? argDesc : '[fn]';
+          break;
 
-};
-goog.inherits(ol.geom.LineString, ol.geom.SimpleGeometry);
+        case 'undefined':
+        default:
+          argDesc = typeof arg;
+          break;
+      }
 
+      if (argDesc.length > 40) {
+        argDesc = argDesc.substr(0, 40) + '...';
+      }
+      sb.push(argDesc);
+    }
+    visited.push(fn);
+    sb.push(')\n');
+    /** @preserveTry */
+    try {
+      sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
+    } catch (e) {
+      sb.push('[exception trying to get caller]\n');
+    }
 
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @api stable
- */
-ol.geom.LineString.prototype.appendCoordinate = function(coordinate) {
-  goog.asserts.assert(coordinate.length == this.stride);
-  if (goog.isNull(this.flatCoordinates)) {
-    this.flatCoordinates = coordinate.slice();
+  } else if (fn) {
+    sb.push('[...long stack...]');
   } else {
-    ol.array.safeExtend(this.flatCoordinates, coordinate);
+    sb.push('[end]');
   }
-  this.dispatchChangeEvent();
+  return sb.join('');
 };
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.LineString} Clone.
- * @api stable
+ * Set a custom function name resolver.
+ * @param {function(Function): string} resolver Resolves functions to their
+ *     names.
  */
-ol.geom.LineString.prototype.clone = function() {
-  var lineString = new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return lineString;
+goog.debug.setFunctionResolver = function(resolver) {
+  goog.debug.fnNameResolver_ = resolver;
 };
 
 
 /**
- * @inheritDoc
+ * Gets a function name
+ * @param {Function} fn Function to get name of.
+ * @return {string} Function's name.
  */
-ol.geom.LineString.prototype.closestPointXY =
-    function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
+goog.debug.getFunctionName = function(fn) {
+  if (goog.debug.fnNameCache_[fn]) {
+    return goog.debug.fnNameCache_[fn];
   }
-  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();
+  if (goog.debug.fnNameResolver_) {
+    var name = goog.debug.fnNameResolver_(fn);
+    if (name) {
+      goog.debug.fnNameCache_[fn] = name;
+      return name;
+    }
   }
-  return ol.geom.flat.closest.getClosestPoint(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
+
+  // Heuristically determine function name based on code.
+  var functionSource = String(fn);
+  if (!goog.debug.fnNameCache_[functionSource]) {
+    var matches = /function ([^\(]+)/.exec(functionSource);
+    if (matches) {
+      var method = matches[1];
+      goog.debug.fnNameCache_[functionSource] = method;
+    } else {
+      goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
+    }
+  }
+
+  return goog.debug.fnNameCache_[functionSource];
 };
 
 
 /**
- * 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.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
+ * Makes whitespace visible by replacing it with printable characters.
+ * This is useful in finding diffrences between the expected and the actual
+ * output strings of a testcase.
+ * @param {string} string whose whitespace needs to be made visible.
+ * @return {string} string whose whitespace is made visible.
  */
-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 = goog.isDef(opt_extrapolate) ? opt_extrapolate : false;
-  return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0,
-      this.flatCoordinates.length, this.stride, m, extrapolate);
+goog.debug.makeWhitespaceVisible = function(string) {
+  return string.replace(/ /g, '[_]')
+      .replace(/\f/g, '[f]')
+      .replace(/\n/g, '[n]\n')
+      .replace(/\r/g, '[r]')
+      .replace(/\t/g, '[t]');
 };
 
 
 /**
- * @return {Array.<ol.Coordinate>} Coordinates.
- * @api stable
+ * Hash map for storing function names that have already been looked up.
+ * @type {Object}
+ * @private
  */
-ol.geom.LineString.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
+goog.debug.fnNameCache_ = {};
 
 
 /**
- * @return {number} Length (on projected plane).
- * @api stable
+ * Resolves functions to their names.  Resolved function names will be cached.
+ * @type {function(Function):string}
+ * @private
  */
-ol.geom.LineString.prototype.getLength = function() {
-  return ol.geom.flat.length.lineString(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
+goog.debug.fnNameResolver_;
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @return {Array.<number>} Flat midpoint.
+ * @fileoverview Definition of the LogRecord class. Please minimize
+ * dependencies this file has on other closure classes as any dependency it
+ * takes won't be able to use the logging infrastructure.
+ *
  */
-ol.geom.LineString.prototype.getFlatMidpoint = function() {
-  if (this.flatMidpointRevision_ != this.getRevision()) {
-    this.flatMidpoint_ = ol.geom.flat.interpolate.lineString(
-        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-        0.5, this.flatMidpoint_);
-    this.flatMidpointRevision_ = this.getRevision();
-  }
-  return this.flatMidpoint_;
-};
+
+goog.provide('goog.debug.LogRecord');
+
 
 
 /**
- * @inheritDoc
+ * LogRecord objects are used to pass logging requests between
+ * the logging framework and individual log Handlers.
+ * @constructor
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @param {number=} opt_time Time this log record was created if other than now.
+ *     If 0, we use #goog.now.
+ * @param {number=} opt_sequenceNumber Sequence number of this log record. This
+ *     should only be passed in when restoring a log record from persistence.
  */
-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;
+goog.debug.LogRecord = function(level, msg, loggerName,
+    opt_time, opt_sequenceNumber) {
+  this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Time the LogRecord was created.
+ * @type {number}
+ * @private
  */
-ol.geom.LineString.prototype.getType = function() {
-  return ol.geom.GeometryType.LINE_STRING;
-};
+goog.debug.LogRecord.prototype.time_;
 
 
 /**
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * Level of the LogRecord
+ * @type {goog.debug.Logger.Level}
+ * @private
  */
-ol.geom.LineString.prototype.setCoordinates =
-    function(coordinates, opt_layout) {
-  if (goog.isNull(coordinates)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 1);
-    if (goog.isNull(this.flatCoordinates)) {
-      this.flatCoordinates = [];
-    }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.dispatchChangeEvent();
-  }
-};
+goog.debug.LogRecord.prototype.level_;
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * Message associated with the record
+ * @type {string}
+ * @private
  */
-ol.geom.LineString.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.dispatchChangeEvent();
-};
-
-goog.provide('ol.geom.MultiLineString');
+goog.debug.LogRecord.prototype.msg_;
 
-goog.require('goog.asserts');
-goog.require('ol.array');
-goog.require('ol.extent');
-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.simplify');
 
+/**
+ * Name of the logger that created the record.
+ * @type {string}
+ * @private
+ */
+goog.debug.LogRecord.prototype.loggerName_;
 
 
 /**
- * @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
+ * Sequence number for the LogRecord. Each record has a unique sequence number
+ * that is greater than all log records created before it.
+ * @type {number}
+ * @private
  */
-ol.geom.MultiLineString = function(coordinates, opt_layout) {
+goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
 
-  goog.base(this);
 
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.ends_ = [];
+/**
+ * Exception associated with the record
+ * @type {Object}
+ * @private
+ */
+goog.debug.LogRecord.prototype.exception_ = null;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
+/**
+ * @define {boolean} Whether to enable log sequence numbers.
+ */
+goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
 
-  this.setCoordinates(coordinates,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
 
-};
-goog.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry);
+/**
+ * A sequence counter for assigning increasing sequence numbers to LogRecord
+ * objects.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogRecord.nextSequenceNumber_ = 0;
 
 
 /**
- * @param {ol.geom.LineString} lineString LineString.
- * @api stable
+ * Sets all fields of the log record.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @param {number=} opt_time Time this log record was created if other than now.
+ *     If 0, we use #goog.now.
+ * @param {number=} opt_sequenceNumber Sequence number of this log record. This
+ *     should only be passed in when restoring a log record from persistence.
  */
-ol.geom.MultiLineString.prototype.appendLineString = function(lineString) {
-  goog.asserts.assert(lineString.getLayout() == this.layout);
-  if (goog.isNull(this.flatCoordinates)) {
-    this.flatCoordinates = lineString.getFlatCoordinates().slice();
-  } else {
-    ol.array.safeExtend(
-        this.flatCoordinates, lineString.getFlatCoordinates().slice());
+goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
+    opt_time, opt_sequenceNumber) {
+  if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
+    this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
+        opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
   }
-  this.ends_.push(this.flatCoordinates.length);
-  this.dispatchChangeEvent();
+
+  this.time_ = opt_time || goog.now();
+  this.level_ = level;
+  this.msg_ = msg;
+  this.loggerName_ = loggerName;
+  delete this.exception_;
 };
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.MultiLineString} Clone.
- * @api stable
+ * Get the source Logger's name.
+ *
+ * @return {string} source logger name (may be null).
  */
-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;
+goog.debug.LogRecord.prototype.getLoggerName = function() {
+  return this.loggerName_;
 };
 
 
 /**
- * @inheritDoc
+ * Get the exception that is part of the log record.
+ *
+ * @return {Object} the exception.
  */
-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);
+goog.debug.LogRecord.prototype.getException = function() {
+  return this.exception_;
 };
 
 
 /**
- * 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.
+ * Set the exception that is part of the log record.
  *
- * `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 {Object} exception the exception.
+ */
+goog.debug.LogRecord.prototype.setException = function(exception) {
+  this.exception_ = exception;
+};
+
+
+/**
+ * Get the source Logger's name.
  *
- * @param {number} m M.
- * @param {boolean=} opt_extrapolate Extrapolate.
- * @param {boolean=} opt_interpolate Interpolate.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
+ * @param {string} loggerName source logger name (may be null).
  */
-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 = goog.isDef(opt_extrapolate) ? opt_extrapolate : false;
-  var interpolate = goog.isDef(opt_interpolate) ? opt_interpolate : false;
-  return ol.geom.flat.lineStringsCoordinateAtM(this.flatCoordinates, 0,
-      this.ends_, this.stride, m, extrapolate, interpolate);
+goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
+  this.loggerName_ = loggerName;
 };
 
 
 /**
- * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
- * @api stable
+ * Get the logging message level, for example Level.SEVERE.
+ * @return {goog.debug.Logger.Level} the logging message level.
  */
-ol.geom.MultiLineString.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinatess(
-      this.flatCoordinates, 0, this.ends_, this.stride);
+goog.debug.LogRecord.prototype.getLevel = function() {
+  return this.level_;
 };
 
 
 /**
- * @return {Array.<number>} Ends.
+ * Set the logging message level, for example Level.SEVERE.
+ * @param {goog.debug.Logger.Level} level the logging message level.
  */
-ol.geom.MultiLineString.prototype.getEnds = function() {
-  return this.ends_;
+goog.debug.LogRecord.prototype.setLevel = function(level) {
+  this.level_ = level;
 };
 
 
 /**
- * @param {number} index Index.
- * @return {ol.geom.LineString} LineString.
- * @api stable
+ * Get the "raw" log message, before localization or formatting.
+ *
+ * @return {string} the raw message string.
  */
-ol.geom.MultiLineString.prototype.getLineString = function(index) {
-  goog.asserts.assert(0 <= index && index < this.ends_.length);
-  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;
+goog.debug.LogRecord.prototype.getMessage = function() {
+  return this.msg_;
 };
 
 
 /**
- * @return {Array.<ol.geom.LineString>} LineStrings.
- * @api stable
+ * Set the "raw" log message, before localization or formatting.
+ *
+ * @param {string} msg the raw message string.
  */
-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;
+goog.debug.LogRecord.prototype.setMessage = function(msg) {
+  this.msg_ = msg;
 };
 
 
 /**
- * @return {Array.<number>} Flat midpoints.
+ * Get event time in milliseconds since 1970.
+ *
+ * @return {number} event time in millis since 1970.
  */
-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.safeExtend(midpoints, midpoint);
-    offset = end;
-  }
-  return midpoints;
+goog.debug.LogRecord.prototype.getMillis = function() {
+  return this.time_;
 };
 
 
 /**
- * @inheritDoc
+ * Set event time in milliseconds since 1970.
+ *
+ * @param {number} time event time in millis since 1970.
  */
-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;
+goog.debug.LogRecord.prototype.setMillis = function(time) {
+  this.time_ = time;
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Get the sequence number.
+ * <p>
+ * Sequence numbers are normally assigned in the LogRecord
+ * constructor, which assigns unique sequence numbers to
+ * each new LogRecord in increasing order.
+ * @return {number} the sequence number.
  */
-ol.geom.MultiLineString.prototype.getType = function() {
-  return ol.geom.GeometryType.MULTI_LINE_STRING;
+goog.debug.LogRecord.prototype.getSequenceNumber = function() {
+  return this.sequenceNumber_;
 };
 
 
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 /**
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * @fileoverview A buffer for log records. The purpose of this is to improve
+ * logging performance by re-using old objects when the buffer becomes full and
+ * to eliminate the need for each app to implement their own log buffer. The
+ * disadvantage to doing this is that log handlers cannot maintain references to
+ * log records and expect that they are not overwriten at a later point.
+ *
+ * @author agrieve@google.com (Andrew Grieve)
  */
-ol.geom.MultiLineString.prototype.setCoordinates =
-    function(coordinates, opt_layout) {
-  if (goog.isNull(coordinates)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
-  } else {
-    this.setLayout(opt_layout, coordinates, 2);
-    if (goog.isNull(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.dispatchChangeEvent();
-  }
-};
+
+goog.provide('goog.debug.LogBuffer');
+
+goog.require('goog.asserts');
+goog.require('goog.debug.LogRecord');
+
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<number>} ends Ends.
+ * Creates the log buffer.
+ * @constructor
+ * @final
  */
-ol.geom.MultiLineString.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates, ends) {
-  if (goog.isNull(flatCoordinates)) {
-    goog.asserts.assert(!goog.isNull(ends) && ends.length === 0);
-  } else if (ends.length === 0) {
-    goog.asserts.assert(flatCoordinates.length === 0);
-  } else {
-    goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]);
-  }
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.ends_ = ends;
-  this.dispatchChangeEvent();
+goog.debug.LogBuffer = function() {
+  goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
+      'Cannot use goog.debug.LogBuffer without defining ' +
+      'goog.debug.LogBuffer.CAPACITY.');
+  this.clear();
 };
 
 
 /**
- * @param {Array.<ol.geom.LineString>} lineStrings LineStrings.
+ * A static method that always returns the same instance of LogBuffer.
+ * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
  */
-ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) {
-  var layout = ol.geom.GeometryLayout.XY;
-  var flatCoordinates = [];
-  var ends = [];
-  var i, ii;
-  for (i = 0, ii = lineStrings.length; i < ii; ++i) {
-    var lineString = lineStrings[i];
-    if (i === 0) {
-      layout = lineString.getLayout();
-    } else {
-      // FIXME better handle the case of non-matching layouts
-      goog.asserts.assert(lineString.getLayout() == layout);
-    }
-    ol.array.safeExtend(flatCoordinates, lineString.getFlatCoordinates());
-    ends.push(flatCoordinates.length);
+goog.debug.LogBuffer.getInstance = function() {
+  if (!goog.debug.LogBuffer.instance_) {
+    // This function is written with the return statement after the assignment
+    // to avoid the jscompiler StripCode bug described in http://b/2608064.
+    // After that bug is fixed this can be refactored.
+    goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
   }
-  this.setFlatCoordinates(layout, flatCoordinates, ends);
+  return goog.debug.LogBuffer.instance_;
 };
 
-goog.provide('ol.geom.Point');
-
-goog.require('goog.asserts');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.math');
 
+/**
+ * @define {number} The number of log records to buffer. 0 means disable
+ * buffering.
+ */
+goog.define('goog.debug.LogBuffer.CAPACITY', 0);
 
 
 /**
- * @classdesc
- * Point geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {ol.Coordinate} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * The array to store the records.
+ * @type {!Array<!goog.debug.LogRecord|undefined>}
+ * @private
  */
-ol.geom.Point = function(coordinates, opt_layout) {
-  goog.base(this);
-  this.setCoordinates(coordinates,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
-};
-goog.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
+goog.debug.LogBuffer.prototype.buffer_;
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Point} Clone.
- * @api stable
+ * The index of the most recently added record or -1 if there are no records.
+ * @type {number}
+ * @private
  */
-ol.geom.Point.prototype.clone = function() {
-  var point = new ol.geom.Point(null);
-  point.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return point;
-};
+goog.debug.LogBuffer.prototype.curIndex_;
 
 
 /**
- * @inheritDoc
+ * Whether the buffer is at capacity.
+ * @type {boolean}
+ * @private
  */
-ol.geom.Point.prototype.closestPointXY =
-    function(x, y, closestPoint, minSquaredDistance) {
-  var flatCoordinates = this.flatCoordinates;
-  var squaredDistance = ol.math.squaredDistance(
-      x, y, flatCoordinates[0], flatCoordinates[1]);
-  if (squaredDistance < minSquaredDistance) {
-    var stride = this.stride;
-    var i;
-    for (i = 0; i < stride; ++i) {
-      closestPoint[i] = flatCoordinates[i];
-    }
-    closestPoint.length = stride;
-    return squaredDistance;
-  } else {
-    return minSquaredDistance;
-  }
-};
+goog.debug.LogBuffer.prototype.isFull_;
 
 
 /**
- * @return {ol.Coordinate} Coordinates.
- * @api stable
+ * Adds a log record to the buffer, possibly overwriting the oldest record.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @return {!goog.debug.LogRecord} The log record.
  */
-ol.geom.Point.prototype.getCoordinates = function() {
-  return goog.isNull(this.flatCoordinates) ? [] : this.flatCoordinates.slice();
+goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
+  var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
+  this.curIndex_ = curIndex;
+  if (this.isFull_) {
+    var ret = this.buffer_[curIndex];
+    ret.reset(level, msg, loggerName);
+    return ret;
+  }
+  this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
+  return this.buffer_[curIndex] =
+      new goog.debug.LogRecord(level, msg, loggerName);
 };
 
 
 /**
- * @inheritDoc
+ * @return {boolean} Whether the log buffer is enabled.
  */
-ol.geom.Point.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    this.extent = ol.extent.createOrUpdateFromCoordinate(
-        this.flatCoordinates, this.extent);
-    this.extentRevision = this.getRevision();
-  }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
+goog.debug.LogBuffer.isBufferingEnabled = function() {
+  return goog.debug.LogBuffer.CAPACITY > 0;
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Removes all buffered log records.
  */
-ol.geom.Point.prototype.getType = function() {
-  return ol.geom.GeometryType.POINT;
+goog.debug.LogBuffer.prototype.clear = function() {
+  this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
+  this.curIndex_ = -1;
+  this.isFull_ = false;
 };
 
 
 /**
- * @param {ol.Coordinate} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * Calls the given function for each buffered log record, starting with the
+ * oldest one.
+ * @param {function(!goog.debug.LogRecord)} func The function to call.
  */
-ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (goog.isNull(coordinates)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 0);
-    if (goog.isNull(this.flatCoordinates)) {
-      this.flatCoordinates = [];
-    }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinate(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.dispatchChangeEvent();
+goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
+  var buffer = this.buffer_;
+  // Corner case: no records.
+  if (!buffer[0]) {
+    return;
   }
+  var curIndex = this.curIndex_;
+  var i = this.isFull_ ? curIndex : -1;
+  do {
+    i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
+    func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
+  } while (i != curIndex);
 };
 
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @fileoverview Definition of the Logger class. Please minimize dependencies
+ * this file has on other closure classes as any dependency it takes won't be
+ * able to use the logging infrastructure.
+ *
+ * @see ../demos/debug.html
  */
-ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.dispatchChangeEvent();
-};
 
-goog.provide('ol.geom.MultiPoint');
+goog.provide('goog.debug.LogManager');
+goog.provide('goog.debug.Loggable');
+goog.provide('goog.debug.Logger');
+goog.provide('goog.debug.Logger.Level');
 
+goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('ol.array');
-goog.require('ol.extent');
-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');
-
+goog.require('goog.debug');
+goog.require('goog.debug.LogBuffer');
+goog.require('goog.debug.LogRecord');
 
 
 /**
- * @classdesc
- * Multi-point geometry.
+ * A message value that can be handled by a Logger.
  *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * Functions are treated like callbacks, but are only called when the event's
+ * log level is enabled. This is useful for logging messages that are expensive
+ * to construct.
+ *
+ * @typedef {string|function(): string}
  */
-ol.geom.MultiPoint = function(coordinates, opt_layout) {
-  goog.base(this);
-  this.setCoordinates(coordinates,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
-};
-goog.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry);
-
+goog.debug.Loggable;
 
-/**
- * @param {ol.geom.Point} point Point.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.appendPoint = function(point) {
-  goog.asserts.assert(point.getLayout() == this.layout);
-  if (goog.isNull(this.flatCoordinates)) {
-    this.flatCoordinates = point.getFlatCoordinates().slice();
-  } else {
-    ol.array.safeExtend(this.flatCoordinates, point.getFlatCoordinates());
-  }
-  this.dispatchChangeEvent();
-};
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.MultiPoint} Clone.
- * @api stable
+ * The Logger is an object used for logging debug messages. Loggers are
+ * normally named, using a hierarchical dot-separated namespace. Logger names
+ * can be arbitrary strings, but they should normally be based on the package
+ * name or class name of the logged component, such as goog.net.BrowserChannel.
+ *
+ * The Logger object is loosely based on the java class
+ * java.util.logging.Logger. It supports different levels of filtering for
+ * different loggers.
+ *
+ * The logger object should never be instantiated by application code. It
+ * should always use the goog.debug.Logger.getLogger function.
+ *
+ * @constructor
+ * @param {string} name The name of the Logger.
+ * @final
  */
-ol.geom.MultiPoint.prototype.clone = function() {
-  var multiPoint = new ol.geom.MultiPoint(null);
-  multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return multiPoint;
-};
+goog.debug.Logger = function(name) {
+  /**
+   * Name of the Logger. Generally a dot-separated namespace
+   * @private {string}
+   */
+  this.name_ = name;
 
+  /**
+   * Parent Logger.
+   * @private {goog.debug.Logger}
+   */
+  this.parent_ = null;
 
-/**
- * @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;
-};
+  /**
+   * Level that this logger only filters above. Null indicates it should
+   * inherit from the parent.
+   * @private {goog.debug.Logger.Level}
+   */
+  this.level_ = null;
 
+  /**
+   * Map of children loggers. The keys are the leaf names of the children and
+   * the values are the child loggers.
+   * @private {Object}
+   */
+  this.children_ = null;
 
-/**
- * @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);
+  /**
+   * Handlers that are listening to this logger.
+   * @private {Array<Function>}
+   */
+  this.handlers_ = null;
 };
 
 
-/**
- * @param {number} index Index.
- * @return {ol.geom.Point} Point.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.getPoint = function(index) {
-  var n = goog.isNull(this.flatCoordinates) ?
-      0 : this.flatCoordinates.length / this.stride;
-  goog.asserts.assert(0 <= index && index < 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;
-};
+/** @const */
+goog.debug.Logger.ROOT_LOGGER_NAME = '';
 
 
 /**
- * @return {Array.<ol.geom.Point>} Points.
- * @api stable
+ * @define {boolean} Toggles whether loggers other than the root logger can have
+ *     log handlers attached to them and whether they can have their log level
+ *     set. Logging is a bit faster when this is set to false.
  */
-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;
-};
+goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
 
 
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.MultiPoint.prototype.getType = function() {
-  return ol.geom.GeometryType.MULTI_POINT;
-};
+if (!goog.debug.Logger.ENABLE_HIERARCHY) {
+  /**
+   * @type {!Array<Function>}
+   * @private
+   */
+  goog.debug.Logger.rootHandlers_ = [];
+
+
+  /**
+   * @type {goog.debug.Logger.Level}
+   * @private
+   */
+  goog.debug.Logger.rootLevel_;
+}
+
 
 
 /**
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * The Level class defines a set of standard logging levels that
+ * can be used to control logging output.  The logging Level objects
+ * are ordered and are specified by ordered integers.  Enabling logging
+ * at a given level also enables logging at all higher levels.
+ * <p>
+ * Clients should normally use the predefined Level constants such
+ * as Level.SEVERE.
+ * <p>
+ * The levels in descending order are:
+ * <ul>
+ * <li>SEVERE (highest value)
+ * <li>WARNING
+ * <li>INFO
+ * <li>CONFIG
+ * <li>FINE
+ * <li>FINER
+ * <li>FINEST  (lowest value)
+ * </ul>
+ * In addition there is a level OFF that can be used to turn
+ * off logging, and a level ALL that can be used to enable
+ * logging of all messages.
+ *
+ * @param {string} name The name of the level.
+ * @param {number} value The numeric value of the level.
+ * @constructor
+ * @final
  */
-ol.geom.MultiPoint.prototype.setCoordinates =
-    function(coordinates, opt_layout) {
-  if (goog.isNull(coordinates)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 1);
-    if (goog.isNull(this.flatCoordinates)) {
-      this.flatCoordinates = [];
-    }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.dispatchChangeEvent();
-  }
+goog.debug.Logger.Level = function(name, value) {
+  /**
+   * The name of the level
+   * @type {string}
+   */
+  this.name = name;
+
+  /**
+   * The numeric value of the level
+   * @type {number}
+   */
+  this.value = value;
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @return {string} String representation of the logger level.
+ * @override
  */
-ol.geom.MultiPoint.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.dispatchChangeEvent();
+goog.debug.Logger.Level.prototype.toString = function() {
+  return this.name;
 };
 
-goog.provide('ol.geom.flat.area');
+
+/**
+ * OFF is a special level that can be used to turn off logging.
+ * This level is initialized to <CODE>Infinity</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.OFF =
+    new goog.debug.Logger.Level('OFF', Infinity);
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {number} Area.
+ * SHOUT is a message level for extra debugging loudness.
+ * This level is initialized to <CODE>1200</CODE>.
+ * @type {!goog.debug.Logger.Level}
  */
-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;
-};
+goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @return {number} Area.
+ * SEVERE is a message level indicating a serious failure.
+ * This level is initialized to <CODE>1000</CODE>.
+ * @type {!goog.debug.Logger.Level}
  */
-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;
-};
+goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @return {number} Area.
+ * WARNING is a message level indicating a potential problem.
+ * This level is initialized to <CODE>900</CODE>.
+ * @type {!goog.debug.Logger.Level}
  */
-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.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
 
-goog.provide('ol.geom.LinearRing');
 
-goog.require('ol.extent');
-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');
+/**
+ * INFO is a message level for informational messages.
+ * This level is initialized to <CODE>800</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
 
 
+/**
+ * CONFIG is a message level for static configuration messages.
+ * This level is initialized to <CODE>700</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
+
 
 /**
- * @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
+ * FINE is a message level providing tracing information.
+ * This level is initialized to <CODE>500</CODE>.
+ * @type {!goog.debug.Logger.Level}
  */
-ol.geom.LinearRing = function(coordinates, opt_layout) {
+goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
 
-  goog.base(this);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
+/**
+ * FINER indicates a fairly detailed tracing message.
+ * This level is initialized to <CODE>400</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
+/**
+ * FINEST indicates a highly detailed tracing message.
+ * This level is initialized to <CODE>300</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
 
-  this.setCoordinates(coordinates,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
+goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
 
-};
-goog.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry);
+
+/**
+ * ALL indicates that all messages should be logged.
+ * This level is initialized to <CODE>0</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.LinearRing} Clone.
- * @api stable
+ * The predefined levels.
+ * @type {!Array<!goog.debug.Logger.Level>}
+ * @final
  */
-ol.geom.LinearRing.prototype.clone = function() {
-  var linearRing = new ol.geom.LinearRing(null);
-  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return linearRing;
-};
+goog.debug.Logger.Level.PREDEFINED_LEVELS = [
+  goog.debug.Logger.Level.OFF,
+  goog.debug.Logger.Level.SHOUT,
+  goog.debug.Logger.Level.SEVERE,
+  goog.debug.Logger.Level.WARNING,
+  goog.debug.Logger.Level.INFO,
+  goog.debug.Logger.Level.CONFIG,
+  goog.debug.Logger.Level.FINE,
+  goog.debug.Logger.Level.FINER,
+  goog.debug.Logger.Level.FINEST,
+  goog.debug.Logger.Level.ALL];
 
 
 /**
- * @inheritDoc
+ * A lookup map used to find the level object based on the name or value of
+ * the level object.
+ * @type {Object}
+ * @private
  */
-ol.geom.LinearRing.prototype.closestPointXY =
-    function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  if (this.maxDeltaRevision_ != this.getRevision()) {
-    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta(
-        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0));
-    this.maxDeltaRevision_ = this.getRevision();
-  }
-  return ol.geom.flat.closest.getClosestPoint(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
-};
+goog.debug.Logger.Level.predefinedLevelsCache_ = null;
 
 
 /**
- * @return {number} Area (on projected plane).
- * @api stable
+ * Creates the predefined levels cache and populates it.
+ * @private
  */
-ol.geom.LinearRing.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRing(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
+goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
+  goog.debug.Logger.Level.predefinedLevelsCache_ = {};
+  for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
+       i++) {
+    goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
+    goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
+  }
 };
 
 
 /**
- * @return {Array.<ol.Coordinate>} Coordinates.
- * @api stable
+ * Gets the predefined level with the given name.
+ * @param {string} name The name of the level.
+ * @return {goog.debug.Logger.Level} The level, or null if none found.
  */
-ol.geom.LinearRing.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
+goog.debug.Logger.Level.getPredefinedLevel = function(name) {
+  if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
+    goog.debug.Logger.Level.createPredefinedLevelsCache_();
+  }
+
+  return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
 };
 
 
 /**
- * @inheritDoc
+ * Gets the highest predefined level <= #value.
+ * @param {number} value Level value.
+ * @return {goog.debug.Logger.Level} The level, or null if none found.
  */
-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;
+goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
+  if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
+    goog.debug.Logger.Level.createPredefinedLevelsCache_();
+  }
+
+  if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
+    return goog.debug.Logger.Level.predefinedLevelsCache_[value];
+  }
+
+  for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
+    var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
+    if (level.value <= value) {
+      return level;
+    }
+  }
+  return null;
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Finds or creates a logger for a named subsystem. If a logger has already been
+ * created with the given name it is returned. Otherwise a new logger is
+ * created. If a new logger is created its log level will be configured based
+ * on the LogManager configuration and it will configured to also send logging
+ * output to its parent's handlers. It will be registered in the LogManager
+ * global namespace.
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of the
+ * subsystem, such as goog.net.BrowserChannel.
+ * @return {!goog.debug.Logger} The named logger.
+ * @deprecated use goog.log instead. http://go/goog-debug-logger-deprecated
  */
-ol.geom.LinearRing.prototype.getType = function() {
-  return ol.geom.GeometryType.LINEAR_RING;
+goog.debug.Logger.getLogger = function(name) {
+  return goog.debug.LogManager.getLogger(name);
 };
 
 
 /**
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * Logs a message to profiling tools, if available.
+ * {@see https://developers.google.com/web-toolkit/speedtracer/logging-api}
+ * {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
+ * @param {string} msg The message to log.
  */
-ol.geom.LinearRing.prototype.setCoordinates =
-    function(coordinates, opt_layout) {
-  if (goog.isNull(coordinates)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 1);
-    if (goog.isNull(this.flatCoordinates)) {
-      this.flatCoordinates = [];
+goog.debug.Logger.logToProfilers = function(msg) {
+  // Using goog.global, as loggers might be used in window-less contexts.
+  if (goog.global['console']) {
+    if (goog.global['console']['timeStamp']) {
+      // Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
+      goog.global['console']['timeStamp'](msg);
+    } else if (goog.global['console']['markTimeline']) {
+      // TODO(user): markTimeline is deprecated. Drop this else clause entirely
+      // after Chrome M14 hits stable.
+      goog.global['console']['markTimeline'](msg);
     }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.dispatchChangeEvent();
+  }
+
+  if (goog.global['msWriteProfilerMark']) {
+    // Logs a message to the Microsoft profiler
+    goog.global['msWriteProfilerMark'](msg);
   }
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * Gets the name of this logger.
+ * @return {string} The name of this logger.
  */
-ol.geom.LinearRing.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.dispatchChangeEvent();
+goog.debug.Logger.prototype.getName = function() {
+  return this.name_;
 };
 
-goog.provide('ol.geom.flat.contains');
-
-goog.require('goog.asserts');
-
 
 /**
- * @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).
+ * Adds a handler to the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {Function} handler Handler function to add.
  */
-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;
+goog.debug.Logger.prototype.addHandler = function(handler) {
+  if (goog.debug.LOGGING_ENABLED) {
+    if (goog.debug.Logger.ENABLE_HIERARCHY) {
+      if (!this.handlers_) {
+        this.handlers_ = [];
+      }
+      this.handlers_.push(handler);
+    } else {
+      goog.asserts.assert(!this.name_,
+          'Cannot call addHandler on a non-root logger when ' +
+          'goog.debug.Logger.ENABLE_HIERARCHY is false.');
+      goog.debug.Logger.rootHandlers_.push(handler);
     }
-    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).
+ * Removes a handler from the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {Function} handler Handler function to remove.
+ * @return {boolean} Whether the handler was removed.
  */
-ol.geom.flat.contains.linearRingsContainsXY =
-    function(flatCoordinates, offset, ends, stride, x, y) {
-  goog.asserts.assert(ends.length > 0);
-  if (ends.length === 0) {
-    return false;
-  }
-  if (!ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, ends[0], stride, x, y)) {
+goog.debug.Logger.prototype.removeHandler = function(handler) {
+  if (goog.debug.LOGGING_ENABLED) {
+    var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
+        goog.debug.Logger.rootHandlers_;
+    return !!handlers && goog.array.remove(handlers, handler);
+  } else {
     return false;
   }
-  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).
+ * Returns the parent of this logger.
+ * @return {goog.debug.Logger} The parent logger or null if this is the root.
  */
-ol.geom.flat.contains.linearRingssContainsXY =
-    function(flatCoordinates, offset, endss, stride, x, y) {
-  goog.asserts.assert(endss.length > 0);
-  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.debug.Logger.prototype.getParent = function() {
+  return this.parent_;
 };
 
-goog.provide('ol.geom.flat.interiorpoint');
 
-goog.require('goog.asserts');
-goog.require('ol.geom.flat.contains');
+/**
+ * Returns the children of this logger as a map of the child name to the logger.
+ * @return {!Object} The map where the keys are the child leaf names and the
+ *     values are the Logger objects.
+ */
+goog.debug.Logger.prototype.getChildren = function() {
+  if (!this.children_) {
+    this.children_ = {};
+  }
+  return this.children_;
+};
 
 
 /**
- * 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.
+ * Set the log level specifying which message levels will be logged by this
+ * logger. Message levels lower than this value will be discarded.
+ * The level value Level.OFF can be used to turn off logging. If the new level
+ * is null, it means that this node should inherit its level from its nearest
+ * ancestor with a specific (non-null) level value.
+ *
+ * @param {goog.debug.Logger.Level} level The new level.
  */
-ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
-    ends, stride, flatCenters, flatCentersOffset, opt_dest) {
-  var i, ii, x, x1, x2, y1, y2;
-  var y = flatCenters[flatCentersOffset + 1];
-  /** @type {Array.<number>} */
-  var intersections = [];
-  // Calculate intersections with the horizontal line
-  var end = ends[0];
-  x1 = flatCoordinates[end - stride];
-  y1 = flatCoordinates[end - stride + 1];
-  for (i = offset; i < end; i += stride) {
-    x2 = flatCoordinates[i];
-    y2 = flatCoordinates[i + 1];
-    if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) {
-      x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
-      intersections.push(x);
-    }
-    x1 = x2;
-    y1 = y2;
-  }
-  // Find the longest segment of the horizontal line that has its center point
-  // inside the linear ring.
-  var pointX = NaN;
-  var maxSegmentLength = -Infinity;
-  intersections.sort();
-  x1 = intersections[0];
-  for (i = 1, ii = intersections.length; i < ii; ++i) {
-    x2 = intersections[i];
-    var segmentLength = Math.abs(x2 - x1);
-    if (segmentLength > maxSegmentLength) {
-      x = (x1 + x2) / 2;
-      if (ol.geom.flat.contains.linearRingsContainsXY(
-          flatCoordinates, offset, ends, stride, x, y)) {
-        pointX = x;
-        maxSegmentLength = segmentLength;
-      }
+goog.debug.Logger.prototype.setLevel = function(level) {
+  if (goog.debug.LOGGING_ENABLED) {
+    if (goog.debug.Logger.ENABLE_HIERARCHY) {
+      this.level_ = level;
+    } else {
+      goog.asserts.assert(!this.name_,
+          'Cannot call setLevel() on a non-root logger when ' +
+          'goog.debug.Logger.ENABLE_HIERARCHY is false.');
+      goog.debug.Logger.rootLevel_ = level;
     }
-    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 (goog.isDef(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.
+ * Gets the log level specifying which message levels will be logged by this
+ * logger. Message levels lower than this value will be discarded.
+ * The level value Level.OFF can be used to turn off logging. If the level
+ * is null, it means that this node should inherit its level from its nearest
+ * ancestor with a specific (non-null) level value.
+ *
+ * @return {goog.debug.Logger.Level} The level.
  */
-ol.geom.flat.interiorpoint.linearRingss =
-    function(flatCoordinates, offset, endss, stride, flatCenters) {
-  goog.asserts.assert(2 * endss.length == 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.debug.Logger.prototype.getLevel = function() {
+  return goog.debug.LOGGING_ENABLED ?
+      this.level_ : goog.debug.Logger.Level.OFF;
 };
 
-goog.provide('ol.geom.flat.reverse');
-
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
+ * Returns the effective level of the logger based on its ancestors' levels.
+ * @return {goog.debug.Logger.Level} The level.
  */
-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.debug.Logger.prototype.getEffectiveLevel = function() {
+  if (!goog.debug.LOGGING_ENABLED) {
+    return goog.debug.Logger.Level.OFF;
   }
-};
-
-goog.provide('ol.geom.flat.orient');
 
-goog.require('ol.geom.flat.reverse');
+  if (!goog.debug.Logger.ENABLE_HIERARCHY) {
+    return goog.debug.Logger.rootLevel_;
+  }
+  if (this.level_) {
+    return this.level_;
+  }
+  if (this.parent_) {
+    return this.parent_.getEffectiveLevel();
+  }
+  goog.asserts.fail('Root logger has no level set.');
+  return null;
+};
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {boolean} Is clockwise.
+ * Checks if a message of the given level would actually be logged by this
+ * logger. This check is based on the Loggers effective level, which may be
+ * inherited from its parent.
+ * @param {goog.debug.Logger.Level} level The level to check.
+ * @return {boolean} Whether the message would be logged.
  */
-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;
+goog.debug.Logger.prototype.isLoggable = function(level) {
+  return goog.debug.LOGGING_ENABLED &&
+      level.value >= this.getEffectiveLevel().value;
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @return {boolean} `true` if all rings are correctly oriented, `false`
- *     otherwise.
+ * Logs a message. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ *     message.
  */
-ol.geom.flat.orient.linearRingsAreOriented =
-    function(flatCoordinates, offset, ends, stride) {
-  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 ? !isClockwise : isClockwise) {
-      return false;
+goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
+  // java caches the effective level, not sure it's necessary here
+  if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
+    // Message callbacks can be useful when a log message is expensive to build.
+    if (goog.isFunction(msg)) {
+      msg = msg();
     }
-    offset = end;
+
+    this.doLogRecord_(this.getLogRecord(level, msg, opt_exception));
   }
-  return true;
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @return {boolean} `true` if all rings are correctly oriented, `false`
- *     otherwise.
+ * Creates a new log record and adds the exception (if present) to it.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ *     message.
+ * @return {!goog.debug.LogRecord} A log record.
+ * @suppress {es5Strict}
  */
-ol.geom.flat.linearRingssAreOriented =
-    function(flatCoordinates, offset, endss, stride) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    if (!ol.geom.flat.orient.linearRingsAreOriented(
-        flatCoordinates, offset, endss[i], stride)) {
-      return false;
-    }
+goog.debug.Logger.prototype.getLogRecord = function(
+    level, msg, opt_exception) {
+  if (goog.debug.LogBuffer.isBufferingEnabled()) {
+    var logRecord =
+        goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
+  } else {
+    logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
   }
-  return true;
+  if (opt_exception) {
+    logRecord.setException(opt_exception);
+  }
+  return logRecord;
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @return {number} End.
+ * Logs a message at the Logger.Level.SHOUT level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.geom.flat.orient.orientLinearRings =
-    function(flatCoordinates, offset, ends, stride) {
-  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 ? !isClockwise : isClockwise;
-    if (reverse) {
-      ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride);
-    }
-    offset = end;
+goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
   }
-  return offset;
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @return {number} End.
+ * Logs a message at the Logger.Level.SEVERE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.geom.flat.orient.orientLinearRingss =
-    function(flatCoordinates, offset, endss, stride) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    offset = ol.geom.flat.orient.orientLinearRings(
-        flatCoordinates, offset, endss[i], stride);
+goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
   }
-  return offset;
 };
 
-goog.provide('ol.geom.Polygon');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('ol.array');
-goog.require('ol.extent');
-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.orient');
-goog.require('ol.geom.flat.simplify');
-
-
 
 /**
- * @classdesc
- * Polygon geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * Logs a message at the Logger.Level.WARNING level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.geom.Polygon = function(coordinates, opt_layout) {
-
-  goog.base(this);
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.ends_ = [];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.flatInteriorPointRevision_ = -1;
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.flatInteriorPoint_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.orientedRevision_ = -1;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.orientedFlatCoordinates_ = null;
-
-  this.setCoordinates(coordinates,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
-
+goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
+  }
 };
-goog.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
 
 
 /**
- * @param {ol.geom.LinearRing} linearRing Linear ring.
- * @api stable
+ * Logs a message at the Logger.Level.INFO level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
-  goog.asserts.assert(linearRing.getLayout() == this.layout);
-  if (goog.isNull(this.flatCoordinates)) {
-    this.flatCoordinates = linearRing.getFlatCoordinates().slice();
-  } else {
-    ol.array.safeExtend(this.flatCoordinates, linearRing.getFlatCoordinates());
+goog.debug.Logger.prototype.info = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
   }
-  this.ends_.push(this.flatCoordinates.length);
-  this.dispatchChangeEvent();
 };
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Polygon} Clone.
- * @api stable
+ * Logs a message at the Logger.Level.CONFIG level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-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;
+goog.debug.Logger.prototype.config = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Logs a message at the Logger.Level.FINE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-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();
+goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
   }
-  return ol.geom.flat.closest.getsClosestPoint(
-      this.flatCoordinates, 0, this.ends_, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
 };
 
 
 /**
- * @inheritDoc
+ * Logs a message at the Logger.Level.FINER level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.geom.Polygon.prototype.containsXY = function(x, y) {
-  return ol.geom.flat.contains.linearRingsContainsXY(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
+goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
+  }
 };
 
 
 /**
- * @return {number} Area (on projected plane).
- * @api stable
+ * Logs a message at the Logger.Level.FINEST level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.geom.Polygon.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRings(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
+goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
+  if (goog.debug.LOGGING_ENABLED) {
+    this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
+  }
 };
 
 
 /**
- * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
- * @api stable
+ * Logs a LogRecord. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.debug.LogRecord} logRecord A log record to log.
  */
-ol.geom.Polygon.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinatess(
-      this.flatCoordinates, 0, this.ends_, this.stride);
+goog.debug.Logger.prototype.logRecord = function(logRecord) {
+  if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
+    this.doLogRecord_(logRecord);
+  }
 };
 
 
 /**
- * @return {Array.<number>} Ends.
+ * Logs a LogRecord.
+ * @param {goog.debug.LogRecord} logRecord A log record to log.
+ * @private
  */
-ol.geom.Polygon.prototype.getEnds = function() {
-  return this.ends_;
+goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
+  goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
+  if (goog.debug.Logger.ENABLE_HIERARCHY) {
+    var target = this;
+    while (target) {
+      target.callPublish_(logRecord);
+      target = target.getParent();
+    }
+  } else {
+    for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
+      handler(logRecord);
+    }
+  }
 };
 
 
 /**
- * @return {Array.<number>} Interior point.
+ * Calls the handlers for publish.
+ * @param {goog.debug.LogRecord} logRecord The log record to publish.
+ * @private
  */
-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();
+goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
+  if (this.handlers_) {
+    for (var i = 0, handler; handler = this.handlers_[i]; i++) {
+      handler(logRecord);
+    }
   }
-  return this.flatInteriorPoint_;
 };
 
 
 /**
- * @return {ol.geom.Point} Interior point.
- * @api stable
+ * Sets the parent of this logger. This is used for setting up the logger tree.
+ * @param {goog.debug.Logger} parent The parent logger.
+ * @private
  */
-ol.geom.Polygon.prototype.getInteriorPoint = function() {
-  return new ol.geom.Point(this.getFlatInteriorPoint());
+goog.debug.Logger.prototype.setParent_ = function(parent) {
+  this.parent_ = parent;
 };
 
 
 /**
- * Return the number of rings of the polygon,  this includes the exterior
- * ring and any interior rings.
- *
- * @return {number} Number of rings.
- * @api
+ * Adds a child to this logger. This is used for setting up the logger tree.
+ * @param {string} name The leaf name of the child.
+ * @param {goog.debug.Logger} logger The child logger.
+ * @private
  */
-ol.geom.Polygon.prototype.getLinearRingCount = function() {
-  return this.ends_.length;
+goog.debug.Logger.prototype.addChild_ = function(name, logger) {
+  this.getChildren()[name] = logger;
 };
 
 
 /**
- * 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.
+ * There is a single global LogManager object that is used to maintain a set of
+ * shared state about Loggers and log services. This is loosely based on the
+ * java class java.util.logging.LogManager.
+ * @const
+ */
+goog.debug.LogManager = {};
+
+
+/**
+ * Map of logger names to logger objects.
  *
- * @param {number} index Index.
- * @return {ol.geom.LinearRing} Linear ring.
- * @api stable
+ * @type {!Object<string, !goog.debug.Logger>}
+ * @private
  */
-ol.geom.Polygon.prototype.getLinearRing = function(index) {
-  goog.asserts.assert(0 <= index && index < this.ends_.length);
-  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;
-};
+goog.debug.LogManager.loggers_ = {};
 
 
 /**
- * @return {Array.<ol.geom.LinearRing>} Linear rings.
- * @api stable
+ * The root logger which is the root of the logger tree.
+ * @type {goog.debug.Logger}
+ * @private
  */
-ol.geom.Polygon.prototype.getLinearRings = function() {
-  var layout = this.layout;
-  var flatCoordinates = this.flatCoordinates;
-  var ends = this.ends_;
-  var linearRings = [];
-  var offset = 0;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var linearRing = new ol.geom.LinearRing(null);
-    linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end));
-    linearRings.push(linearRing);
-    offset = end;
-  }
-  return linearRings;
-};
+goog.debug.LogManager.rootLogger_ = null;
 
 
 /**
- * @return {Array.<number>} Oriented flat coordinates.
+ * Initializes the LogManager if not already initialized.
  */
-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();
+goog.debug.LogManager.initialize = function() {
+  if (!goog.debug.LogManager.rootLogger_) {
+    goog.debug.LogManager.rootLogger_ = new goog.debug.Logger(
+        goog.debug.Logger.ROOT_LOGGER_NAME);
+    goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] =
+        goog.debug.LogManager.rootLogger_;
+    goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
   }
-  return this.orientedFlatCoordinates_;
 };
 
 
 /**
- * @inheritDoc
+ * Returns all the loggers.
+ * @return {!Object<string, !goog.debug.Logger>} Map of logger names to logger
+ *     objects.
  */
-ol.geom.Polygon.prototype.getSimplifiedGeometryInternal =
-    function(squaredTolerance) {
-  var simplifiedFlatCoordinates = [];
-  var simplifiedEnds = [];
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes(
-      this.flatCoordinates, 0, this.ends_, this.stride,
-      Math.sqrt(squaredTolerance),
-      simplifiedFlatCoordinates, 0, simplifiedEnds);
-  var simplifiedPolygon = new ol.geom.Polygon(null);
-  simplifiedPolygon.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds);
-  return simplifiedPolygon;
+goog.debug.LogManager.getLoggers = function() {
+  return goog.debug.LogManager.loggers_;
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Returns the root of the logger tree namespace, the logger with the empty
+ * string as its name.
+ *
+ * @return {!goog.debug.Logger} The root logger.
  */
-ol.geom.Polygon.prototype.getType = function() {
-  return ol.geom.GeometryType.POLYGON;
+goog.debug.LogManager.getRoot = function() {
+  goog.debug.LogManager.initialize();
+  return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
 };
 
 
 /**
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * Finds a named logger.
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of the
+ * subsystem, such as goog.net.BrowserChannel.
+ * @return {!goog.debug.Logger} The named logger.
  */
-ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (goog.isNull(coordinates)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
-  } else {
-    this.setLayout(opt_layout, coordinates, 2);
-    if (goog.isNull(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.dispatchChangeEvent();
-  }
+goog.debug.LogManager.getLogger = function(name) {
+  goog.debug.LogManager.initialize();
+  var ret = goog.debug.LogManager.loggers_[name];
+  return ret || goog.debug.LogManager.createLogger_(name);
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<number>} ends Ends.
+ * Creates a function that can be passed to goog.debug.catchErrors. The function
+ * will log all reported errors using the given logger.
+ * @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
+ *     Defaults to the root logger.
+ * @return {function(Object)} The created function.
  */
-ol.geom.Polygon.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates, ends) {
-  if (goog.isNull(flatCoordinates)) {
-    goog.asserts.assert(!goog.isNull(ends) && ends.length === 0);
-  } else if (ends.length === 0) {
-    goog.asserts.assert(flatCoordinates.length === 0);
-  } else {
-    goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]);
-  }
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.ends_ = ends;
-  this.dispatchChangeEvent();
+goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
+  return function(info) {
+    var logger = opt_logger || goog.debug.LogManager.getRoot();
+    logger.severe('Error: ' + info.message + ' (' + info.fileName +
+                  ' @ Line: ' + info.line + ')');
+  };
 };
 
 
 /**
- * Create an approximation of a circle on the surface of a sphere.
- * @param {ol.Sphere} sphere The sphere.
- * @param {ol.Coordinate} center Center.
- * @param {number} radius Radius.
- * @param {number=} opt_n Optional number of points.  Default is `32`.
- * @return {ol.geom.Polygon} Circle geometry.
- * @api stable
+ * Creates the named logger. Will also create the parents of the named logger
+ * if they don't yet exist.
+ * @param {string} name The name of the logger.
+ * @return {!goog.debug.Logger} The named logger.
+ * @private
  */
-ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) {
-  var n = goog.isDef(opt_n) ? opt_n : 32;
-  /** @type {Array.<number>} */
-  var flatCoordinates = [];
-  var i;
-  for (i = 0; i < n; ++i) {
-    goog.array.extend(
-        flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n));
-  }
-  flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
-  var polygon = new ol.geom.Polygon(null);
-  polygon.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
-  return polygon;
-};
+goog.debug.LogManager.createLogger_ = function(name) {
+  // find parent logger
+  var logger = new goog.debug.Logger(name);
+  if (goog.debug.Logger.ENABLE_HIERARCHY) {
+    var lastDotIndex = name.lastIndexOf('.');
+    var parentName = name.substr(0, lastDotIndex);
+    var leafName = name.substr(lastDotIndex + 1);
+    var parentLogger = goog.debug.LogManager.getLogger(parentName);
 
-goog.provide('ol.geom.flat.center');
+    // tell the parent about the child and the child about the parent
+    parentLogger.addChild_(leafName, logger);
+    logger.setParent_(parentLogger);
+  }
 
-goog.require('ol.extent');
+  goog.debug.LogManager.loggers_[name] = logger;
+  return logger;
+};
 
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @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.
+ * @fileoverview Definition the goog.debug.RelativeTimeProvider class.
+ *
  */
-ol.geom.flat.center.linearRingss =
-    function(flatCoordinates, offset, endss, stride) {
-  var flatCenters = [];
-  var i, ii;
-  var extent = ol.extent.createEmpty();
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    extent = ol.extent.createOrUpdateFromFlatCoordinates(
-        flatCoordinates, offset, ends[0], stride);
-    flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2);
-    offset = ends[ends.length - 1];
-  }
-  return flatCenters;
-};
-
-goog.provide('ol.geom.MultiPolygon');
 
-goog.require('goog.asserts');
-goog.require('ol.array');
-goog.require('ol.extent');
-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.orient');
-goog.require('ol.geom.flat.simplify');
+goog.provide('goog.debug.RelativeTimeProvider');
 
 
 
 /**
- * @classdesc
- * Multi-polygon geometry.
+ * A simple object to keep track of a timestamp considered the start of
+ * something. The main use is for the logger system to maintain a start time
+ * that is occasionally reset. For example, in Gmail, we reset this relative
+ * time at the start of a user action so that timings are offset from the
+ * beginning of the action. This class also provides a singleton as the default
+ * behavior for most use cases is to share the same start time.
  *
  * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * @final
  */
-ol.geom.MultiPolygon = function(coordinates, opt_layout) {
-
-  goog.base(this);
-
-  /**
-   * @type {Array.<Array.<number>>}
-   * @private
-   */
-  this.endss_ = [];
-
+goog.debug.RelativeTimeProvider = function() {
   /**
-   * @private
+   * The start time.
    * @type {number}
-   */
-  this.flatInteriorPointsRevision_ = -1;
-
-  /**
    * @private
-   * @type {Array.<number>}
    */
-  this.flatInteriorPoints_ = null;
+  this.relativeTimeStart_ = goog.now();
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
+/**
+ * Default instance.
+ * @type {goog.debug.RelativeTimeProvider}
+ * @private
+ */
+goog.debug.RelativeTimeProvider.defaultInstance_ =
+    new goog.debug.RelativeTimeProvider();
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.orientedRevision_ = -1;
 
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.orientedFlatCoordinates_ = null;
+/**
+ * Sets the start time to the specified time.
+ * @param {number} timeStamp The start time.
+ */
+goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
+  this.relativeTimeStart_ = timeStamp;
+};
 
-  this.setCoordinates(coordinates,
-      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
 
+/**
+ * Resets the start time to now.
+ */
+goog.debug.RelativeTimeProvider.prototype.reset = function() {
+  this.set(goog.now());
 };
-goog.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry);
 
 
 /**
- * @param {ol.geom.Polygon} polygon Polygon.
- * @api stable
+ * @return {number} The start time.
  */
-ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) {
-  goog.asserts.assert(polygon.getLayout() == this.layout);
-  /** @type {Array.<number>} */
-  var ends;
-  if (goog.isNull(this.flatCoordinates)) {
-    this.flatCoordinates = polygon.getFlatCoordinates().slice();
-    ends = polygon.getEnds().slice();
-    this.endss_.push();
-  } else {
-    var offset = this.flatCoordinates.length;
-    ol.array.safeExtend(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.dispatchChangeEvent();
+goog.debug.RelativeTimeProvider.prototype.get = function() {
+  return this.relativeTimeStart_;
 };
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.MultiPolygon} Clone.
- * @api stable
+ * @return {goog.debug.RelativeTimeProvider} The default instance.
  */
-ol.geom.MultiPolygon.prototype.clone = function() {
-  var multiPolygon = new ol.geom.MultiPolygon(null);
-  multiPolygon.setFlatCoordinates(
-      this.layout, this.flatCoordinates.slice(), this.endss_.slice());
-  return multiPolygon;
+goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
+  return goog.debug.RelativeTimeProvider.defaultInstance_;
 };
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @inheritDoc
+ * @fileoverview Definition of various formatters for logging. Please minimize
+ * dependencies this file has on other closure classes as any dependency it
+ * takes won't be able to use the logging infrastructure.
+ *
  */
-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);
-};
+
+goog.provide('goog.debug.Formatter');
+goog.provide('goog.debug.HtmlFormatter');
+goog.provide('goog.debug.TextFormatter');
+
+goog.require('goog.debug');
+goog.require('goog.debug.Logger');
+goog.require('goog.debug.RelativeTimeProvider');
+goog.require('goog.html.SafeHtml');
+
 
 
 /**
- * @inheritDoc
+ * Base class for Formatters. A Formatter is used to format a LogRecord into
+ * something that can be displayed to the user.
+ *
+ * @param {string=} opt_prefix The prefix to place before text records.
+ * @constructor
  */
-ol.geom.MultiPolygon.prototype.containsXY = function(x, y) {
-  return ol.geom.flat.contains.linearRingssContainsXY(
-      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y);
+goog.debug.Formatter = function(opt_prefix) {
+  this.prefix_ = opt_prefix || '';
+
+  /**
+   * A provider that returns the relative start time.
+   * @type {goog.debug.RelativeTimeProvider}
+   * @private
+   */
+  this.startTimeProvider_ =
+      goog.debug.RelativeTimeProvider.getDefaultInstance();
 };
 
 
 /**
- * @return {number} Area (on projected plane).
- * @api stable
+ * Whether to append newlines to the end of formatted log records.
+ * @type {boolean}
  */
-ol.geom.MultiPolygon.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRingss(
-      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride);
-};
+goog.debug.Formatter.prototype.appendNewline = true;
 
 
 /**
- * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates.
- * @api stable
+ * Whether to show absolute time in the DebugWindow.
+ * @type {boolean}
  */
-ol.geom.MultiPolygon.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinatesss(
-      this.flatCoordinates, 0, this.endss_, this.stride);
-};
+goog.debug.Formatter.prototype.showAbsoluteTime = true;
 
 
 /**
- * @return {Array.<Array.<number>>} Endss.
+ * Whether to show relative time in the DebugWindow.
+ * @type {boolean}
  */
-ol.geom.MultiPolygon.prototype.getEndss = function() {
-  return this.endss_;
-};
+goog.debug.Formatter.prototype.showRelativeTime = true;
 
 
 /**
- * @return {Array.<number>} Flat interior points.
+ * Whether to show the logger name in the DebugWindow.
+ * @type {boolean}
  */
-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_;
-};
+goog.debug.Formatter.prototype.showLoggerName = true;
 
 
 /**
- * @return {ol.geom.MultiPoint} Interior points.
- * @api stable
+ * Whether to show the logger exception text.
+ * @type {boolean}
  */
-ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
-  var interiorPoints = new ol.geom.MultiPoint(null);
-  interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY,
-      this.getFlatInteriorPoints().slice());
-  return interiorPoints;
-};
+goog.debug.Formatter.prototype.showExceptionText = false;
 
 
 /**
- * @return {Array.<number>} Oriented flat coordinates.
+ * Whether to show the severity level.
+ * @type {boolean}
  */
-ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() {
-  if (this.orientedRevision_ != this.getRevision()) {
-    var flatCoordinates = this.flatCoordinates;
-    if (ol.geom.flat.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_;
+goog.debug.Formatter.prototype.showSeverityLevel = false;
+
+
+/**
+ * Formats a record.
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {string} The formatted string.
+ */
+goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
+
+
+/**
+ * Sets the start time provider. By default, this is the default instance
+ * but can be changed.
+ * @param {goog.debug.RelativeTimeProvider} provider The provider to use.
+ */
+goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
+  this.startTimeProvider_ = provider;
 };
 
 
 /**
- * @inheritDoc
+ * Returns the start time provider. By default, this is the default instance
+ * but can be changed.
+ * @return {goog.debug.RelativeTimeProvider} The start time provider.
  */
-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;
+goog.debug.Formatter.prototype.getStartTimeProvider = function() {
+  return this.startTimeProvider_;
 };
 
 
 /**
- * @param {number} index Index.
- * @return {ol.geom.Polygon} Polygon.
- * @api stable
+ * Resets the start relative time.
  */
-ol.geom.MultiPolygon.prototype.getPolygon = function(index) {
-  goog.asserts.assert(0 <= index && index < this.endss_.length);
-  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;
+goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
+  this.startTimeProvider_.reset();
 };
 
 
 /**
- * @return {Array.<ol.geom.Polygon>} Polygons.
- * @api stable
+ * Returns a string for the time/date of the LogRecord.
+ * @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
+ * @return {string} A string representation of the time/date of the LogRecord.
+ * @private
  */
-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;
+goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
+  var time = new Date(logRecord.getMillis());
+  return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
+         goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
+         goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
+         goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
+         goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
+         goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
+         goog.debug.Formatter.getTwoDigitString_(
+             Math.floor(time.getMilliseconds() / 10));
 };
 
 
 /**
- * @inheritDoc
- * @api stable
+ * Returns the number as a two-digit string, meaning it prepends a 0 if the
+ * number if less than 10.
+ * @param {number} n The number to format.
+ * @return {string} A two-digit string representation of {@code n}.
+ * @private
  */
-ol.geom.MultiPolygon.prototype.getType = function() {
-  return ol.geom.GeometryType.MULTI_POLYGON;
+goog.debug.Formatter.getTwoDigitString_ = function(n) {
+  if (n < 10) {
+    return '0' + n;
+  }
+  return String(n);
 };
 
 
 /**
- * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
+ * Returns a string for the number of seconds relative to the start time.
+ * Prepads with spaces so that anything less than 1000 seconds takes up the
+ * same number of characters for better formatting.
+ * @param {goog.debug.LogRecord} logRecord The log to compare time to.
+ * @param {number} relativeTimeStart The start time to compare to.
+ * @return {string} The number of seconds of the LogRecord relative to the
+ *     start time.
+ * @private
  */
-ol.geom.MultiPolygon.prototype.setCoordinates =
-    function(coordinates, opt_layout) {
-  if (goog.isNull(coordinates)) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_);
+goog.debug.Formatter.getRelativeTime_ = function(logRecord,
+                                                 relativeTimeStart) {
+  var ms = logRecord.getMillis() - relativeTimeStart;
+  var sec = ms / 1000;
+  var str = sec.toFixed(3);
+
+  var spacesToPrepend = 0;
+  if (sec < 1) {
+    spacesToPrepend = 2;
   } else {
-    this.setLayout(opt_layout, coordinates, 3);
-    if (goog.isNull(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];
+    while (sec < 100) {
+      spacesToPrepend++;
+      sec *= 10;
     }
-    this.dispatchChangeEvent();
   }
+  while (spacesToPrepend-- > 0) {
+    str = ' ' + str;
+  }
+  return str;
 };
 
 
+
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<Array.<number>>} endss Endss.
+ * Formatter that returns formatted html. See formatRecord for the classes
+ * it uses for various types of formatted output.
+ *
+ * @param {string=} opt_prefix The prefix to place before text records.
+ * @constructor
+ * @extends {goog.debug.Formatter}
  */
-ol.geom.MultiPolygon.prototype.setFlatCoordinates =
-    function(layout, flatCoordinates, endss) {
-  goog.asserts.assert(!goog.isNull(endss));
-  if (goog.isNull(flatCoordinates) || flatCoordinates.length === 0) {
-    goog.asserts.assert(endss.length === 0);
-  } else {
-    goog.asserts.assert(endss.length > 0);
-    var ends = endss[endss.length - 1];
-    goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]);
-  }
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.endss_ = endss;
-  this.dispatchChangeEvent();
+goog.debug.HtmlFormatter = function(opt_prefix) {
+  goog.debug.Formatter.call(this, opt_prefix);
 };
+goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
 
 
 /**
- * @param {Array.<ol.geom.Polygon>} polygons Polygons.
+ * Whether to show the logger exception text
+ * @type {boolean}
+ * @override
  */
-ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) {
-  var layout = ol.geom.GeometryLayout.XY;
-  var flatCoordinates = [];
-  var endss = [];
-  var i, ii, ends;
-  for (i = 0, ii = polygons.length; i < ii; ++i) {
-    var polygon = polygons[i];
-    if (i === 0) {
-      layout = polygon.getLayout();
-    } else {
-      // FIXME better handle the case of non-matching layouts
-      goog.asserts.assert(polygon.getLayout() == layout);
-    }
-    var offset = flatCoordinates.length;
-    ends = polygon.getEnds();
-    var j, jj;
-    for (j = 0, jj = ends.length; j < jj; ++j) {
-      ends[j] += offset;
-    }
-    ol.array.safeExtend(flatCoordinates, polygon.getFlatCoordinates());
-    endss.push(ends);
-  }
-  this.setFlatCoordinates(layout, flatCoordinates, endss);
-};
-
-goog.provide('ol.render.IReplayGroup');
-
-goog.require('goog.functions');
-goog.require('ol.render.IVectorContext');
+goog.debug.HtmlFormatter.prototype.showExceptionText = true;
 
 
 /**
- * @enum {string}
+ * Formats a record
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {string} The formatted string as html.
+ * @override
  */
-ol.render.ReplayType = {
-  IMAGE: 'Image',
-  LINE_STRING: 'LineString',
-  POLYGON: 'Polygon',
-  TEXT: 'Text'
+goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
+  if (!logRecord) {
+    return '';
+  }
+  // OK not to use goog.html.SafeHtml.unwrap() here.
+  return this.formatRecordAsHtml(logRecord).getTypedStringValue();
 };
 
 
 /**
- * @const
- * @type {Array.<ol.render.ReplayType>}
+ * Formats a record.
+ * @param {!goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {!goog.html.SafeHtml} The formatted string as SafeHtml.
  */
-ol.render.REPLAY_ORDER = [
-  ol.render.ReplayType.POLYGON,
-  ol.render.ReplayType.LINE_STRING,
-  ol.render.ReplayType.IMAGE,
-  ol.render.ReplayType.TEXT
-];
+goog.debug.HtmlFormatter.prototype.formatRecordAsHtml = function(logRecord) {
+  var className;
+  switch (logRecord.getLevel().value) {
+    case goog.debug.Logger.Level.SHOUT.value:
+      className = 'dbg-sh';
+      break;
+    case goog.debug.Logger.Level.SEVERE.value:
+      className = 'dbg-sev';
+      break;
+    case goog.debug.Logger.Level.WARNING.value:
+      className = 'dbg-w';
+      break;
+    case goog.debug.Logger.Level.INFO.value:
+      className = 'dbg-i';
+      break;
+    case goog.debug.Logger.Level.FINE.value:
+    default:
+      className = 'dbg-f';
+      break;
+  }
+
+  // HTML for user defined prefix, time, logger name, and severity.
+  var sb = [];
+  sb.push(this.prefix_, ' ');
+  if (this.showAbsoluteTime) {
+    sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
+  }
+  if (this.showRelativeTime) {
+    sb.push('[',
+        goog.debug.Formatter.getRelativeTime_(
+            logRecord, this.startTimeProvider_.get()),
+        's] ');
+  }
+  if (this.showLoggerName) {
+    sb.push('[', logRecord.getLoggerName(), '] ');
+  }
+  if (this.showSeverityLevel) {
+    sb.push('[', logRecord.getLevel().name, '] ');
+  }
+  var fullPrefixHtml =
+      goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(sb.join(''));
 
+  // HTML for exception text and log record.
+  var exceptionHtml = goog.html.SafeHtml.EMPTY;
+  if (this.showExceptionText && logRecord.getException()) {
+    exceptionHtml = goog.html.SafeHtml.concat(
+        goog.html.SafeHtml.create('br'),
+        goog.debug.exposeExceptionAsHtml(logRecord.getException()));
+  }
+  var logRecordHtml = goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
+      logRecord.getMessage());
+  var recordAndExceptionHtml = goog.html.SafeHtml.create(
+      'span',
+      {'class': className},
+      goog.html.SafeHtml.concat(logRecordHtml, exceptionHtml));
 
 
-/**
- * @interface
- */
-ol.render.IReplayGroup = function() {
+  // Combine both pieces of HTML and, if needed, append a final newline.
+  var html;
+  if (this.appendNewline) {
+    html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml,
+        goog.html.SafeHtml.create('br'));
+  } else {
+    html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml);
+  }
+  return html;
 };
 
 
+
 /**
- * FIXME empty description for jsdoc
+ * Formatter that returns formatted plain text
+ *
+ * @param {string=} opt_prefix The prefix to place before text records.
+ * @constructor
+ * @extends {goog.debug.Formatter}
+ * @final
  */
-ol.render.IReplayGroup.prototype.finish = function() {
+goog.debug.TextFormatter = function(opt_prefix) {
+  goog.debug.Formatter.call(this, opt_prefix);
 };
+goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);
 
 
 /**
- * @param {number|undefined} zIndex Z index.
- * @param {ol.render.ReplayType} replayType Replay type.
- * @return {ol.render.IVectorContext} Replay.
+ * Formats a record as text
+ * @param {goog.debug.LogRecord} logRecord the logRecord to format.
+ * @return {string} The formatted string.
+ * @override
  */
-ol.render.IReplayGroup.prototype.getReplay = function(zIndex, replayType) {
+goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
+  // Build message html
+  var sb = [];
+  sb.push(this.prefix_, ' ');
+  if (this.showAbsoluteTime) {
+    sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
+  }
+  if (this.showRelativeTime) {
+    sb.push('[', goog.debug.Formatter.getRelativeTime_(logRecord,
+        this.startTimeProvider_.get()), 's] ');
+  }
+
+  if (this.showLoggerName) {
+    sb.push('[', logRecord.getLoggerName(), '] ');
+  }
+  if (this.showSeverityLevel) {
+    sb.push('[', logRecord.getLevel().name, '] ');
+  }
+  sb.push(logRecord.getMessage());
+  if (this.showExceptionText) {
+    var exception = logRecord.getException();
+    if (exception) {
+      var exceptionText = exception instanceof Error ?
+          exception.message :
+          exception.toString();
+      sb.push('\n', exceptionText);
+    }
+  }
+  if (this.appendNewline) {
+    sb.push('\n');
+  }
+  return sb.join('');
 };
 
+// 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.
 
 /**
- * @return {boolean} Is empty.
+ * @fileoverview Simple logger that logs to the window console if available.
+ *
+ * Has an autoInstall option which can be put into initialization code, which
+ * will start logging if "Debug=true" is in document.location.href
+ *
  */
-ol.render.IReplayGroup.prototype.isEmpty = function() {
-};
 
-goog.provide('ol.renderer.vector');
+goog.provide('goog.debug.Console');
+
+goog.require('goog.debug.LogManager');
+goog.require('goog.debug.Logger');
+goog.require('goog.debug.TextFormatter');
 
-goog.require('goog.asserts');
-goog.require('ol.geom.Circle');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.render.IReplayGroup');
-goog.require('ol.style.ImageState');
-goog.require('ol.style.Style');
 
 
 /**
- * @param {ol.Feature} feature1 Feature 1.
- * @param {ol.Feature} feature2 Feature 2.
- * @return {number} Order.
+ * Create and install a log handler that logs to window.console if available
+ * @constructor
  */
-ol.renderer.vector.defaultOrder = function(feature1, feature2) {
-  return goog.getUid(feature1) - goog.getUid(feature2);
-};
+goog.debug.Console = function() {
+  this.publishHandler_ = goog.bind(this.addLogRecord, this);
 
+  /**
+   * Formatter for formatted output.
+   * @type {!goog.debug.TextFormatter}
+   * @private
+   */
+  this.formatter_ = new goog.debug.TextFormatter();
+  this.formatter_.showAbsoluteTime = false;
+  this.formatter_.showExceptionText = false;
+  // The console logging methods automatically append a newline.
+  this.formatter_.appendNewline = false;
 
-/**
- * @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;
+  this.isCapturing_ = false;
+  this.logBuffer_ = '';
+
+  /**
+   * Loggers that we shouldn't output.
+   * @type {!Object<boolean>}
+   * @private
+   */
+  this.filteredLoggers_ = {};
 };
 
 
 /**
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @return {number} Pixel tolerance.
+ * Returns the text formatter used by this console
+ * @return {!goog.debug.TextFormatter} The text formatter.
  */
-ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
-  return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
+goog.debug.Console.prototype.getFormatter = function() {
+  return this.formatter_;
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
- * @private
+ * Sets whether we are currently capturing logger output.
+ * @param {boolean} capturing Whether to capture logger output.
  */
-ol.renderer.vector.renderCircleGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Circle);
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.drawCircleGeometry(geometry, data);
-  }
-  var textStyle = style.getText();
-  if (!goog.isNull(textStyle)) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, data);
+goog.debug.Console.prototype.setCapturing = function(capturing) {
+  if (capturing == this.isCapturing_) {
+    return;
   }
-};
-
 
-/**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Object} data Opaque data object.
- * @param {function(this: T, goog.events.Event)} listener Listener function.
- * @param {T} thisArg Value to use as `this` when executing `listener`.
- * @return {boolean} `true` if style is loading.
- * @template T
- */
-ol.renderer.vector.renderFeature = function(
-    replayGroup, feature, style, squaredTolerance, data, listener, thisArg) {
-  var loading = false;
-  var imageStyle, imageState;
-  imageStyle = style.getImage();
-  if (goog.isNull(imageStyle)) {
-    ol.renderer.vector.renderFeature_(
-        replayGroup, feature, style, squaredTolerance, data);
+  // attach or detach handler from the root logger
+  var rootLogger = goog.debug.LogManager.getRoot();
+  if (capturing) {
+    rootLogger.addHandler(this.publishHandler_);
   } else {
-    imageState = imageStyle.getImageState();
-    if (imageState == ol.style.ImageState.LOADED ||
-        imageState == ol.style.ImageState.ERROR) {
-      imageStyle.unlistenImageChange(listener, thisArg);
-      if (imageState == ol.style.ImageState.LOADED) {
-        ol.renderer.vector.renderFeature_(
-            replayGroup, feature, style, squaredTolerance, data);
-      }
-    } else {
-      if (imageState == ol.style.ImageState.IDLE) {
-        imageStyle.load();
-      }
-      imageState = imageStyle.getImageState();
-      goog.asserts.assert(imageState == ol.style.ImageState.LOADING);
-      imageStyle.listenImageChange(listener, thisArg);
-      loading = true;
-    }
+    rootLogger.removeHandler(this.publishHandler_);
+    this.logBuffer = '';
   }
-  return loading;
+  this.isCapturing_ = capturing;
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Object} data Opaque data object.
- * @private
+ * Adds a log record.
+ * @param {goog.debug.LogRecord} logRecord The log entry.
  */
-ol.renderer.vector.renderFeature_ = function(
-    replayGroup, feature, style, squaredTolerance, data) {
-  var geometry = feature.getGeometry();
-  if (!goog.isDefAndNotNull(geometry)) {
+goog.debug.Console.prototype.addLogRecord = function(logRecord) {
+
+  // Check to see if the log record is filtered or not.
+  if (this.filteredLoggers_[logRecord.getLoggerName()]) {
     return;
   }
-  var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
-  var geometryRenderer =
-      ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
-  goog.asserts.assert(goog.isDef(geometryRenderer));
-  geometryRenderer(replayGroup, simplifiedGeometry, style, data);
+
+  var record = this.formatter_.formatRecord(logRecord);
+  var console = goog.debug.Console.console_;
+  if (console) {
+    switch (logRecord.getLevel()) {
+      case goog.debug.Logger.Level.SHOUT:
+        goog.debug.Console.logToConsole_(console, 'info', record);
+        break;
+      case goog.debug.Logger.Level.SEVERE:
+        goog.debug.Console.logToConsole_(console, 'error', record);
+        break;
+      case goog.debug.Logger.Level.WARNING:
+        goog.debug.Console.logToConsole_(console, 'warn', record);
+        break;
+      default:
+        goog.debug.Console.logToConsole_(console, 'debug', record);
+        break;
+    }
+  } else {
+    this.logBuffer_ += record;
+  }
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
- * @private
+ * Adds a logger name to be filtered.
+ * @param {string} loggerName the logger name to add.
  */
-ol.renderer.vector.renderGeometryCollectionGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection);
-  var geometries = geometry.getGeometriesArray();
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    var geometryRenderer =
-        ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()];
-    goog.asserts.assert(goog.isDef(geometryRenderer));
-    geometryRenderer(replayGroup, geometries[i], style, data);
-  }
+goog.debug.Console.prototype.addFilter = function(loggerName) {
+  this.filteredLoggers_[loggerName] = true;
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
- * @private
+ * Removes a logger name to be filtered.
+ * @param {string} loggerName the logger name to remove.
  */
-ol.renderer.vector.renderLineStringGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-  var strokeStyle = style.getStroke();
-  if (!goog.isNull(strokeStyle)) {
-    var lineStringReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
-    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
-    lineStringReplay.drawLineStringGeometry(geometry, data);
-  }
-  var textStyle = style.getText();
-  if (!goog.isNull(textStyle)) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, data);
-  }
+goog.debug.Console.prototype.removeFilter = function(loggerName) {
+  delete this.filteredLoggers_[loggerName];
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
- * @private
+ * Global console logger instance
+ * @type {goog.debug.Console}
  */
-ol.renderer.vector.renderMultiLineStringGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
-  var strokeStyle = style.getStroke();
-  if (!goog.isNull(strokeStyle)) {
-    var lineStringReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
-    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
-    lineStringReplay.drawMultiLineStringGeometry(geometry, data);
-  }
-  var textStyle = style.getText();
-  if (!goog.isNull(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, data);
-  }
-};
+goog.debug.Console.instance = null;
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
+ * The console to which to log.  This is a property so it can be mocked out in
+ * this unit test for goog.debug.Console. Using goog.global, as console might be
+ * used in window-less contexts.
+ * @type {Object}
  * @private
  */
-ol.renderer.vector.renderMultiPolygonGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon);
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (!goog.isNull(strokeStyle) || !goog.isNull(fillStyle)) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.drawMultiPolygonGeometry(geometry, data);
-  }
-  var textStyle = style.getText();
-  if (!goog.isNull(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, data);
-  }
+goog.debug.Console.console_ = goog.global['console'];
+
+
+/**
+ * Sets the console to which to log.
+ * @param {!Object} console The console to which to log.
+ */
+goog.debug.Console.setConsole = function(console) {
+  goog.debug.Console.console_ = console;
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
- * @private
+ * Install the console and start capturing if "Debug=true" is in the page URL
  */
-ol.renderer.vector.renderPointGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Point);
-  var imageStyle = style.getImage();
-  if (!goog.isNull(imageStyle)) {
-    var imageReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.IMAGE);
-    imageReplay.setImageStyle(imageStyle);
-    imageReplay.drawPointGeometry(geometry, data);
+goog.debug.Console.autoInstall = function() {
+  if (!goog.debug.Console.instance) {
+    goog.debug.Console.instance = new goog.debug.Console();
   }
-  var textStyle = style.getText();
-  if (!goog.isNull(textStyle)) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(geometry.getCoordinates(), 0, 2, 2, geometry, data);
+
+  if (goog.global.location &&
+      goog.global.location.href.indexOf('Debug=true') != -1) {
+    goog.debug.Console.instance.setCapturing(true);
   }
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
- * @private
+ * Show an alert with all of the captured debug information.
+ * Information is only captured if console is not available
  */
-ol.renderer.vector.renderMultiPointGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint);
-  var imageStyle = style.getImage();
-  if (!goog.isNull(imageStyle)) {
-    var imageReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.IMAGE);
-    imageReplay.setImageStyle(imageStyle);
-    imageReplay.drawMultiPointGeometry(geometry, data);
-  }
-  var textStyle = style.getText();
-  if (!goog.isNull(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, data);
-  }
+goog.debug.Console.show = function() {
+  alert(goog.debug.Console.instance.logBuffer_);
 };
 
 
 /**
- * @param {ol.render.IReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {Object} data Opaque data object.
+ * Logs the record to the console using the given function.  If the function is
+ * not available on the console object, the log function is used instead.
+ * @param {!Object} console The console object.
+ * @param {string} fnName The name of the function to use.
+ * @param {string} record The record to log.
  * @private
  */
-ol.renderer.vector.renderPolygonGeometry_ =
-    function(replayGroup, geometry, style, data) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.drawPolygonGeometry(geometry, data);
-  }
-  var textStyle = style.getText();
-  if (!goog.isNull(textStyle)) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(
-        geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, data);
+goog.debug.Console.logToConsole_ = function(console, fnName, record) {
+  if (console[fnName]) {
+    console[fnName](record);
+  } else {
+    console.log(record);
   }
 };
 
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @const
- * @private
- * @type {Object.<ol.geom.GeometryType,
- *                function(ol.render.IReplayGroup, ol.geom.Geometry,
- *                         ol.style.Style, Object)>}
+ * @fileoverview Utility class that monitors viewport size changes.
+ *
+ * @author attila@google.com (Attila Bodis)
+ * @see ../demos/viewportsizemonitor.html
  */
-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.FeatureOverlay');
+goog.provide('goog.dom.ViewportSizeMonitor');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
+goog.require('goog.dom');
 goog.require('goog.events');
+goog.require('goog.events.EventTarget');
 goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol.Collection');
-goog.require('ol.CollectionEventType');
-goog.require('ol.Feature');
-goog.require('ol.render.EventType');
-goog.require('ol.renderer.vector');
-goog.require('ol.style.Style');
+goog.require('goog.math.Size');
 
 
 
 /**
- * @classdesc
- * A mechanism for changing the style of a small number of features on a
- * temporary basis, for example highlighting. This is necessary with the Canvas
- * renderer, where, unlike in SVG, features cannot be individually referenced.
- * See examples/vector-layers for an example: create a FeatureOverlay with a
- * different style, copy the feature(s) you want rendered in this different
- * style into it, and then remove them again when you're finished.
+ * This class can be used to monitor changes in the viewport size.  Instances
+ * dispatch a {@link goog.events.EventType.RESIZE} event when the viewport size
+ * changes.  Handlers can call {@link goog.dom.ViewportSizeMonitor#getSize} to
+ * get the new viewport size.
+ *
+ * Use this class if you want to execute resize/reflow logic each time the
+ * user resizes the browser window.  This class is guaranteed to only dispatch
+ * {@code RESIZE} events when the pixel dimensions of the viewport change.
+ * (Internet Explorer fires resize events if any element on the page is resized,
+ * even if the viewport dimensions are unchanged, which can lead to infinite
+ * resize loops.)
+ *
+ * Example usage:
+ *  <pre>
+ *    var vsm = new goog.dom.ViewportSizeMonitor();
+ *    goog.events.listen(vsm, goog.events.EventType.RESIZE, function(e) {
+ *      alert('Viewport size changed to ' + vsm.getSize());
+ *    });
+ *  </pre>
+ *
+ * Manually verified on IE6, IE7, FF2, Opera 11, Safari 4 and Chrome.
  *
+ * @param {Window=} opt_window The window to monitor; defaults to the window in
+ *    which this code is executing.
  * @constructor
- * @param {olx.FeatureOverlayOptions=} opt_options Options.
- * @api
+ * @extends {goog.events.EventTarget}
  */
-ol.FeatureOverlay = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.Collection.<ol.Feature>}
-   */
-  this.features_ = null;
-
-  /**
-   * @private
-   * @type {Array.<goog.events.Key>}
-   */
-  this.featuresListenerKeys_ = null;
-
-  /**
-   * @private
-   * @type {Object.<string, goog.events.Key>}
-   */
-  this.featureChangeListenerKeys_ = null;
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = null;
-
-  /**
-   * @private
-   * @type {goog.events.Key}
-   */
-  this.postComposeListenerKey_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
-   */
-  this.style_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.StyleFunction|undefined}
-   */
-  this.styleFunction_ = undefined;
-
-  this.setStyle(goog.isDef(options.style) ?
-      options.style : ol.style.defaultStyleFunction);
+goog.dom.ViewportSizeMonitor = function(opt_window) {
+  goog.events.EventTarget.call(this);
 
-  if (goog.isDef(options.features)) {
-    if (goog.isArray(options.features)) {
-      this.setFeatures(new ol.Collection(goog.array.clone(options.features)));
-    } else {
-      goog.asserts.assertInstanceof(options.features, ol.Collection);
-      this.setFeatures(options.features);
-    }
-  } else {
-    this.setFeatures(new ol.Collection());
-  }
+  // Default the window to the current window if unspecified.
+  this.window_ = opt_window || window;
 
-  if (goog.isDef(options.map)) {
-    this.setMap(options.map);
-  }
+  // Listen for window resize events.
+  this.listenerKey_ = goog.events.listen(this.window_,
+      goog.events.EventType.RESIZE, this.handleResize_, false, this);
 
+  // Set the initial size.
+  this.size_ = goog.dom.getViewportSize(this.window_);
 };
+goog.inherits(goog.dom.ViewportSizeMonitor, goog.events.EventTarget);
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @api
+ * Returns a viewport size monitor for the given window.  A new one is created
+ * if it doesn't exist already.  This prevents the unnecessary creation of
+ * multiple spooling monitors for a window.
+ * @param {Window=} opt_window The window to monitor; defaults to the window in
+ *     which this code is executing.
+ * @return {!goog.dom.ViewportSizeMonitor} Monitor for the given window.
  */
-ol.FeatureOverlay.prototype.addFeature = function(feature) {
-  this.features_.push(feature);
-};
-
+goog.dom.ViewportSizeMonitor.getInstanceForWindow = function(opt_window) {
+  var currentWindow = opt_window || window;
+  var uid = goog.getUid(currentWindow);
 
-/**
- * @return {ol.Collection.<ol.Feature>} Features collection.
- * @api
- */
-ol.FeatureOverlay.prototype.getFeatures = function() {
-  return this.features_;
+  return goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid] =
+      goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid] ||
+      new goog.dom.ViewportSizeMonitor(currentWindow);
 };
 
 
 /**
- * @private
+ * Removes and disposes a viewport size monitor for the given window if one
+ * exists.
+ * @param {Window=} opt_window The window whose monitor should be removed;
+ *     defaults to the window in which this code is executing.
  */
-ol.FeatureOverlay.prototype.handleFeatureChange_ = function() {
-  this.render_();
+goog.dom.ViewportSizeMonitor.removeInstanceForWindow = function(opt_window) {
+  var uid = goog.getUid(opt_window || window);
+
+  goog.dispose(goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid]);
+  delete goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid];
 };
 
 
 /**
+ * Map of window hash code to viewport size monitor for that window, if
+ * created.
+ * @type {Object<number,goog.dom.ViewportSizeMonitor>}
  * @private
- * @param {ol.CollectionEvent} collectionEvent Collection event.
  */
-ol.FeatureOverlay.prototype.handleFeaturesAdd_ = function(collectionEvent) {
-  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
-  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
-  this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
-      goog.events.listen(feature, goog.events.EventType.CHANGE,
-      this.handleFeatureChange_, false, this);
-  this.render_();
-};
+goog.dom.ViewportSizeMonitor.windowInstanceMap_ = {};
 
 
 /**
+ * Event listener key for window the window resize handler, as returned by
+ * {@link goog.events.listen}.
+ * @type {goog.events.Key}
  * @private
- * @param {ol.CollectionEvent} collectionEvent Collection event.
  */
-ol.FeatureOverlay.prototype.handleFeaturesRemove_ = function(collectionEvent) {
-  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
-  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
-  var key = goog.getUid(feature).toString();
-  goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]);
-  delete this.featureChangeListenerKeys_[key];
-  this.render_();
-};
+goog.dom.ViewportSizeMonitor.prototype.listenerKey_ = null;
 
 
 /**
- * Handle changes in image style state.
- * @param {goog.events.Event} event Image style change event.
+ * The window to monitor.  Defaults to the window in which the code is running.
+ * @type {Window}
  * @private
  */
-ol.FeatureOverlay.prototype.handleImageChange_ = function(event) {
-  this.render_();
-};
+goog.dom.ViewportSizeMonitor.prototype.window_ = null;
 
 
 /**
- * @param {ol.render.Event} event Event.
+ * The most recently recorded size of the viewport, in pixels.
+ * @type {goog.math.Size?}
  * @private
  */
-ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) {
-  if (goog.isNull(this.features_)) {
-    return;
-  }
-  var styleFunction = this.styleFunction_;
-  if (!goog.isDef(styleFunction)) {
-    styleFunction = ol.style.defaultStyleFunction;
-  }
-  var replayGroup = /** @type {ol.render.IReplayGroup} */
-      (event.replayGroup);
-  goog.asserts.assert(goog.isDef(replayGroup));
-  var frameState = event.frameState;
-  var pixelRatio = frameState.pixelRatio;
-  var resolution = frameState.viewState.resolution;
-  var i, ii, styles, featureStyleFunction;
-  this.features_.forEach(function(feature) {
-    featureStyleFunction = feature.getStyleFunction();
-    styles = goog.isDef(featureStyleFunction) ?
-        featureStyleFunction.call(feature, resolution) :
-        styleFunction(feature, resolution);
-
-    if (!goog.isDefAndNotNull(styles)) {
-      return;
-    }
-    ii = styles.length;
-    for (i = 0; i < ii; ++i) {
-      ol.renderer.vector.renderFeature(replayGroup, feature, styles[i],
-          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-          feature, this.handleImageChange_, this);
-    }
-  }, this);
-};
+goog.dom.ViewportSizeMonitor.prototype.size_ = null;
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @api
+ * Returns the most recently recorded size of the viewport, in pixels.  May
+ * return null if no window resize event has been handled yet.
+ * @return {goog.math.Size} The viewport dimensions, in pixels.
  */
-ol.FeatureOverlay.prototype.removeFeature = function(feature) {
-  this.features_.remove(feature);
+goog.dom.ViewportSizeMonitor.prototype.getSize = function() {
+  // Return a clone instead of the original to preserve encapsulation.
+  return this.size_ ? this.size_.clone() : null;
 };
 
 
-/**
- * @private
- */
-ol.FeatureOverlay.prototype.render_ = function() {
-  if (!goog.isNull(this.map_)) {
-    this.map_.render();
-  }
-};
-
+/** @override */
+goog.dom.ViewportSizeMonitor.prototype.disposeInternal = function() {
+  goog.dom.ViewportSizeMonitor.superClass_.disposeInternal.call(this);
 
-/**
- * @param {ol.Collection.<ol.Feature>} features Features collection.
- * @api
- */
-ol.FeatureOverlay.prototype.setFeatures = function(features) {
-  if (!goog.isNull(this.featuresListenerKeys_)) {
-    goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey);
-    this.featuresListenerKeys_ = null;
-  }
-  if (!goog.isNull(this.featureChangeListenerKeys_)) {
-    goog.array.forEach(
-        goog.object.getValues(this.featureChangeListenerKeys_),
-        goog.events.unlistenByKey);
-    this.featureChangeListenerKeys_ = null;
-  }
-  this.features_ = features;
-  if (!goog.isNull(features)) {
-    this.featuresListenerKeys_ = [
-      goog.events.listen(features, ol.CollectionEventType.ADD,
-          this.handleFeaturesAdd_, false, this),
-      goog.events.listen(features, ol.CollectionEventType.REMOVE,
-          this.handleFeaturesRemove_, false, this)
-    ];
-    this.featureChangeListenerKeys_ = {};
-    features.forEach(function(feature) {
-      this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
-          goog.events.listen(feature, goog.events.EventType.CHANGE,
-          this.handleFeatureChange_, false, this);
-    }, this);
+  if (this.listenerKey_) {
+    goog.events.unlistenByKey(this.listenerKey_);
+    this.listenerKey_ = null;
   }
-  this.render_();
+
+  this.window_ = null;
+  this.size_ = null;
 };
 
 
 /**
- * @param {ol.Map} map Map.
- * @api
+ * Handles window resize events by measuring the dimensions of the
+ * viewport and dispatching a {@link goog.events.EventType.RESIZE} event if the
+ * current dimensions are different from the previous ones.
+ * @param {goog.events.Event} event The window resize event to handle.
+ * @private
  */
-ol.FeatureOverlay.prototype.setMap = function(map) {
-  if (!goog.isNull(this.postComposeListenerKey_)) {
-    goog.events.unlistenByKey(this.postComposeListenerKey_);
-    this.postComposeListenerKey_ = null;
-  }
-  this.render_();
-  this.map_ = map;
-  if (!goog.isNull(map)) {
-    this.postComposeListenerKey_ = goog.events.listen(
-        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
-        this);
-    map.render();
+goog.dom.ViewportSizeMonitor.prototype.handleResize_ = function(event) {
+  var size = goog.dom.getViewportSize(this.window_);
+  if (!goog.math.Size.equals(size, this.size_)) {
+    this.size_ = size;
+    this.dispatchEvent(goog.events.EventType.RESIZE);
   }
 };
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * 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.
- * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} style
- *     Overlay style.
- * @api
+ * @fileoverview Constant declarations for common key codes.
+ *
+ * @author eae@google.com (Emil A Eklund)
+ * @see ../demos/keyhandler.html
  */
-ol.FeatureOverlay.prototype.setStyle = function(style) {
-  this.style_ = style;
-  this.styleFunction_ = ol.style.createStyleFunction(style);
-  this.render_();
-};
 
+goog.provide('goog.events.KeyCodes');
 
-/**
- * Get the style for features.  This returns whatever was passed to the `style`
- * option at construction or to the `setStyle` method.
- * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
- *     Overlay style.
- * @api
- */
-ol.FeatureOverlay.prototype.getStyle = function() {
-  return this.style_;
-};
+goog.require('goog.userAgent');
 
 
 /**
- * Get the style function.
- * @return {ol.style.StyleFunction|undefined} Style function.
- * @api
- */
-ol.FeatureOverlay.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
-};
-
-goog.provide('ol.format.Feature');
-
-goog.require('goog.array');
-goog.require('ol.geom.Geometry');
-goog.require('ol.proj');
-
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for feature formats.
- * {ol.format.Feature} subclasses provide the ability to decode and encode
- * {@link ol.Feature} objects from a variety of commonly used geospatial
- * file formats.  See the documentation for each format for more details.
+ * Key codes for common characters.
  *
- * @constructor
- * @api stable
+ * This list is not localized and therefore some of the key codes are not
+ * correct for non US keyboard layouts. See comments below.
+ *
+ * @enum {number}
  */
-ol.format.Feature = function() {
+goog.events.KeyCodes = {
+  WIN_KEY_FF_LINUX: 0,
+  MAC_ENTER: 3,
+  BACKSPACE: 8,
+  TAB: 9,
+  NUM_CENTER: 12,  // NUMLOCK on FF/Safari Mac
+  ENTER: 13,
+  SHIFT: 16,
+  CTRL: 17,
+  ALT: 18,
+  PAUSE: 19,
+  CAPS_LOCK: 20,
+  ESC: 27,
+  SPACE: 32,
+  PAGE_UP: 33,     // also NUM_NORTH_EAST
+  PAGE_DOWN: 34,   // also NUM_SOUTH_EAST
+  END: 35,         // also NUM_SOUTH_WEST
+  HOME: 36,        // also NUM_NORTH_WEST
+  LEFT: 37,        // also NUM_WEST
+  UP: 38,          // also NUM_NORTH
+  RIGHT: 39,       // also NUM_EAST
+  DOWN: 40,        // also NUM_SOUTH
+  PRINT_SCREEN: 44,
+  INSERT: 45,      // also NUM_INSERT
+  DELETE: 46,      // also NUM_DELETE
+  ZERO: 48,
+  ONE: 49,
+  TWO: 50,
+  THREE: 51,
+  FOUR: 52,
+  FIVE: 53,
+  SIX: 54,
+  SEVEN: 55,
+  EIGHT: 56,
+  NINE: 57,
+  FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186
+  FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187
+  FF_DASH: 173, // Firefox (Gecko) fires this for dash instead of 189
+  QUESTION_MARK: 63, // needs localization
+  A: 65,
+  B: 66,
+  C: 67,
+  D: 68,
+  E: 69,
+  F: 70,
+  G: 71,
+  H: 72,
+  I: 73,
+  J: 74,
+  K: 75,
+  L: 76,
+  M: 77,
+  N: 78,
+  O: 79,
+  P: 80,
+  Q: 81,
+  R: 82,
+  S: 83,
+  T: 84,
+  U: 85,
+  V: 86,
+  W: 87,
+  X: 88,
+  Y: 89,
+  Z: 90,
+  META: 91, // WIN_KEY_LEFT
+  WIN_KEY_RIGHT: 92,
+  CONTEXT_MENU: 93,
+  NUM_ZERO: 96,
+  NUM_ONE: 97,
+  NUM_TWO: 98,
+  NUM_THREE: 99,
+  NUM_FOUR: 100,
+  NUM_FIVE: 101,
+  NUM_SIX: 102,
+  NUM_SEVEN: 103,
+  NUM_EIGHT: 104,
+  NUM_NINE: 105,
+  NUM_MULTIPLY: 106,
+  NUM_PLUS: 107,
+  NUM_MINUS: 109,
+  NUM_PERIOD: 110,
+  NUM_DIVISION: 111,
+  F1: 112,
+  F2: 113,
+  F3: 114,
+  F4: 115,
+  F5: 116,
+  F6: 117,
+  F7: 118,
+  F8: 119,
+  F9: 120,
+  F10: 121,
+  F11: 122,
+  F12: 123,
+  NUMLOCK: 144,
+  SCROLL_LOCK: 145,
 
-  /**
-   * @protected
-   * @type {ol.proj.Projection}
-   */
-  this.defaultDataProjection = null;
-};
+  // OS-specific media keys like volume controls and browser controls.
+  FIRST_MEDIA_KEY: 166,
+  LAST_MEDIA_KEY: 183,
 
+  SEMICOLON: 186,            // needs localization
+  DASH: 189,                 // needs localization
+  EQUALS: 187,               // needs localization
+  COMMA: 188,                // needs localization
+  PERIOD: 190,               // needs localization
+  SLASH: 191,                // needs localization
+  APOSTROPHE: 192,           // needs localization
+  TILDE: 192,                // needs localization
+  SINGLE_QUOTE: 222,         // needs localization
+  OPEN_SQUARE_BRACKET: 219,  // needs localization
+  BACKSLASH: 220,            // needs localization
+  CLOSE_SQUARE_BRACKET: 221, // needs localization
+  WIN_KEY: 224,
+  MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91
+  MAC_WK_CMD_LEFT: 91,  // WebKit Left Command key fired, same as META
+  MAC_WK_CMD_RIGHT: 93, // WebKit Right Command key fired, different from META
+  WIN_IME: 229,
 
-/**
- * @return {Array.<string>} Extensions.
- */
-ol.format.Feature.prototype.getExtensions = goog.abstractMethod;
+  // We've seen users whose machines fire this keycode at regular one
+  // second intervals. The common thread among these users is that
+  // they're all using Dell Inspiron laptops, so we suspect that this
+  // indicates a hardware/bios problem.
+  // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
+  PHANTOM: 255
+};
 
 
 /**
- * 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
+ * Returns true if the event contains a text modifying key.
+ * @param {goog.events.BrowserEvent} e A key event.
+ * @return {boolean} Whether it's a text modifying key.
  */
-ol.format.Feature.prototype.getReadOptions = function(
-    source, opt_options) {
-  var options;
-  if (goog.isDef(opt_options)) {
-    options = {
-      dataProjection: goog.isDef(opt_options.dataProjection) ?
-          opt_options.dataProjection : this.readProjection(source),
-      featureProjection: opt_options.featureProjection
-    };
+goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) {
+  if (e.altKey && !e.ctrlKey ||
+      e.metaKey ||
+      // Function keys don't generate text
+      e.keyCode >= goog.events.KeyCodes.F1 &&
+      e.keyCode <= goog.events.KeyCodes.F12) {
+    return false;
   }
-  return this.adaptOptions(options);
-};
 
-
-/**
- * Sets the `defaultDataProjection` on the options, if no `dataProjection`
- * is set.
- * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options
- *     Options.
- * @protected
- * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined}
- *     Updated options.
- */
-ol.format.Feature.prototype.adaptOptions = function(
-    options) {
-  var updatedOptions;
-  if (goog.isDef(options)) {
-    updatedOptions = {
-      featureProjection: options.featureProjection,
-      dataProjection: goog.isDefAndNotNull(options.dataProjection) ?
-          options.dataProjection : this.defaultDataProjection
-    };
+  // The following keys are quite harmless, even in combination with
+  // CTRL, ALT or SHIFT.
+  switch (e.keyCode) {
+    case goog.events.KeyCodes.ALT:
+    case goog.events.KeyCodes.CAPS_LOCK:
+    case goog.events.KeyCodes.CONTEXT_MENU:
+    case goog.events.KeyCodes.CTRL:
+    case goog.events.KeyCodes.DOWN:
+    case goog.events.KeyCodes.END:
+    case goog.events.KeyCodes.ESC:
+    case goog.events.KeyCodes.HOME:
+    case goog.events.KeyCodes.INSERT:
+    case goog.events.KeyCodes.LEFT:
+    case goog.events.KeyCodes.MAC_FF_META:
+    case goog.events.KeyCodes.META:
+    case goog.events.KeyCodes.NUMLOCK:
+    case goog.events.KeyCodes.NUM_CENTER:
+    case goog.events.KeyCodes.PAGE_DOWN:
+    case goog.events.KeyCodes.PAGE_UP:
+    case goog.events.KeyCodes.PAUSE:
+    case goog.events.KeyCodes.PHANTOM:
+    case goog.events.KeyCodes.PRINT_SCREEN:
+    case goog.events.KeyCodes.RIGHT:
+    case goog.events.KeyCodes.SCROLL_LOCK:
+    case goog.events.KeyCodes.SHIFT:
+    case goog.events.KeyCodes.UP:
+    case goog.events.KeyCodes.WIN_KEY:
+    case goog.events.KeyCodes.WIN_KEY_RIGHT:
+      return false;
+    case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
+      return !goog.userAgent.GECKO;
+    default:
+      return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY ||
+          e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY;
   }
-  return updatedOptions;
 };
 
 
 /**
- * @return {ol.format.FormatType} Format.
- */
-ol.format.Feature.prototype.getType = goog.abstractMethod;
-
-
-/**
- * Read a single feature from a source.
+ * Returns true if the key fires a keypress event in the current browser.
  *
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- */
-ol.format.Feature.prototype.readFeature = goog.abstractMethod;
-
-
-/**
- * Read all features from a source.
+ * Accoridng to MSDN [1] IE only fires keypress events for the following keys:
+ * - Letters: A - Z (uppercase and lowercase)
+ * - Numerals: 0 - 9
+ * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
+ * - System: ESC, SPACEBAR, ENTER
  *
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.Feature.prototype.readFeatures = goog.abstractMethod;
-
-
-/**
- * Read a single geometry from a source.
+ * That's not entirely correct though, for instance there's no distinction
+ * between upper and lower case letters.
  *
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.Feature.prototype.readGeometry = goog.abstractMethod;
-
-
-/**
- * Read the projection from a source.
+ * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx)
  *
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.Feature.prototype.readProjection = goog.abstractMethod;
-
-
-/**
- * Encode a feature in this format.
+ * Safari is similar to IE, but does not fire keypress for ESC.
  *
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {ArrayBuffer|Node|Object|string} Result.
- */
-ol.format.Feature.prototype.writeFeature = goog.abstractMethod;
-
-
-/**
- * Encode an array of features in this format.
+ * Additionally, IE6 does not fire keydown or keypress events for letters when
+ * the control or alt keys are held down and the shift key is not. IE7 does
+ * fire keydown in these cases, though, but not keypress.
  *
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {ArrayBuffer|Node|Object|string} Result.
+ * @param {number} keyCode A key code.
+ * @param {number=} opt_heldKeyCode Key code of a currently-held key.
+ * @param {boolean=} opt_shiftKey Whether the shift key is held down.
+ * @param {boolean=} opt_ctrlKey Whether the control key is held down.
+ * @param {boolean=} opt_altKey Whether the alt key is held down.
+ * @return {boolean} Whether it's a key that fires a keypress event.
  */
-ol.format.Feature.prototype.writeFeatures = goog.abstractMethod;
+goog.events.KeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode,
+    opt_shiftKey, opt_ctrlKey, opt_altKey) {
+  if (!goog.userAgent.IE &&
+      !(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) {
+    return true;
+  }
 
+  if (goog.userAgent.MAC && opt_altKey) {
+    return goog.events.KeyCodes.isCharacterKey(keyCode);
+  }
 
-/**
- * Write a single geometry in this format.
- *
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {ArrayBuffer|Node|Object|string} Node.
- */
-ol.format.Feature.prototype.writeGeometry = goog.abstractMethod;
+  // Alt but not AltGr which is represented as Alt+Ctrl.
+  if (opt_altKey && !opt_ctrlKey) {
+    return false;
+  }
 
+  // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress.
+  // Non-IE browsers and WebKit prior to 525 won't get this far so no need to
+  // check the user agent.
+  if (goog.isNumber(opt_heldKeyCode)) {
+    opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode);
+  }
+  if (!opt_shiftKey &&
+      (opt_heldKeyCode == goog.events.KeyCodes.CTRL ||
+       opt_heldKeyCode == goog.events.KeyCodes.ALT ||
+       goog.userAgent.MAC &&
+       opt_heldKeyCode == goog.events.KeyCodes.META)) {
+    return false;
+  }
 
-/**
- * @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 = goog.isDef(opt_options) ?
-      ol.proj.get(opt_options.featureProjection) : null;
-  var dataProjection = goog.isDef(opt_options) ?
-      ol.proj.get(opt_options.dataProjection) : null;
-  if (!goog.isNull(featureProjection) && !goog.isNull(dataProjection) &&
-      !ol.proj.equivalent(featureProjection, dataProjection)) {
-    if (geometry instanceof ol.geom.Geometry) {
-      return (write ? geometry.clone() : geometry).transform(
-          write ? featureProjection : dataProjection,
-          write ? dataProjection : featureProjection);
-    } else {
-      // FIXME this is necessary because ol.format.GML treats extents
-      // as geometries
-      return ol.proj.transformExtent(
-          write ? goog.array.clone(geometry) : geometry,
-          write ? featureProjection : dataProjection,
-          write ? dataProjection : featureProjection);
+  // Some keys with Ctrl/Shift do not issue keypress in WEBKIT.
+  if (goog.userAgent.WEBKIT && opt_ctrlKey && opt_shiftKey) {
+    switch (keyCode) {
+      case goog.events.KeyCodes.BACKSLASH:
+      case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
+      case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
+      case goog.events.KeyCodes.TILDE:
+      case goog.events.KeyCodes.SEMICOLON:
+      case goog.events.KeyCodes.DASH:
+      case goog.events.KeyCodes.EQUALS:
+      case goog.events.KeyCodes.COMMA:
+      case goog.events.KeyCodes.PERIOD:
+      case goog.events.KeyCodes.SLASH:
+      case goog.events.KeyCodes.APOSTROPHE:
+      case goog.events.KeyCodes.SINGLE_QUOTE:
+        return false;
     }
-  } else {
-    return geometry;
   }
-};
 
-goog.provide('ol.format.FormatType');
+  // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it
+  // continues to fire keydown events as the event repeats.
+  if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) {
+    return false;
+  }
 
+  switch (keyCode) {
+    case goog.events.KeyCodes.ENTER:
+      return true;
+    case goog.events.KeyCodes.ESC:
+      return !goog.userAgent.WEBKIT;
+  }
 
-/**
- * @enum {string}
- */
-ol.format.FormatType = {
-  BINARY: 'binary',
-  JSON: 'json',
-  TEXT: 'text',
-  XML: 'xml'
+  return goog.events.KeyCodes.isCharacterKey(keyCode);
 };
 
-goog.provide('ol.format.BinaryFeature');
-
-goog.require('goog.asserts');
-goog.require('ol.binary.Buffer');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-goog.require('ol.has');
-goog.require('ol.proj');
-
-
 
 /**
- * @constructor
- * @extends {ol.format.Feature}
+ * Returns true if the key produces a character.
+ * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).
+ *
+ * @param {number} keyCode A key code.
+ * @return {boolean} Whether it's a character key.
  */
-ol.format.BinaryFeature = function() {
-  goog.base(this);
-};
-goog.inherits(ol.format.BinaryFeature, ol.format.Feature);
+goog.events.KeyCodes.isCharacterKey = function(keyCode) {
+  if (keyCode >= goog.events.KeyCodes.ZERO &&
+      keyCode <= goog.events.KeyCodes.NINE) {
+    return true;
+  }
 
+  if (keyCode >= goog.events.KeyCodes.NUM_ZERO &&
+      keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) {
+    return true;
+  }
 
-/**
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @private
- * @return {ol.binary.Buffer} Buffer.
- */
-ol.format.BinaryFeature.getBuffer_ = function(source) {
-  if (ol.has.ARRAY_BUFFER && source instanceof ArrayBuffer) {
-    return new ol.binary.Buffer(source);
-  } else if (goog.isString(source)) {
-    return new ol.binary.Buffer(source);
-  } else {
-    goog.asserts.fail();
-    return null;
+  if (keyCode >= goog.events.KeyCodes.A &&
+      keyCode <= goog.events.KeyCodes.Z) {
+    return true;
   }
-};
 
+  // Safari sends zero key code for non-latin characters.
+  if (goog.userAgent.WEBKIT && keyCode == 0) {
+    return true;
+  }
 
-/**
- * @inheritDoc
- */
-ol.format.BinaryFeature.prototype.getType = function() {
-  return ol.format.FormatType.BINARY;
+  switch (keyCode) {
+    case goog.events.KeyCodes.SPACE:
+    case goog.events.KeyCodes.QUESTION_MARK:
+    case goog.events.KeyCodes.NUM_PLUS:
+    case goog.events.KeyCodes.NUM_MINUS:
+    case goog.events.KeyCodes.NUM_PERIOD:
+    case goog.events.KeyCodes.NUM_DIVISION:
+    case goog.events.KeyCodes.SEMICOLON:
+    case goog.events.KeyCodes.FF_SEMICOLON:
+    case goog.events.KeyCodes.DASH:
+    case goog.events.KeyCodes.EQUALS:
+    case goog.events.KeyCodes.FF_EQUALS:
+    case goog.events.KeyCodes.COMMA:
+    case goog.events.KeyCodes.PERIOD:
+    case goog.events.KeyCodes.SLASH:
+    case goog.events.KeyCodes.APOSTROPHE:
+    case goog.events.KeyCodes.SINGLE_QUOTE:
+    case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
+    case goog.events.KeyCodes.BACKSLASH:
+    case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
+      return true;
+    default:
+      return false;
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Normalizes key codes from OS/Browser-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
  */
-ol.format.BinaryFeature.prototype.readFeature = function(source) {
-  return this.readFeatureFromBuffer(ol.format.BinaryFeature.getBuffer_(source));
+goog.events.KeyCodes.normalizeKeyCode = function(keyCode) {
+  if (goog.userAgent.GECKO) {
+    return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode);
+  } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) {
+    return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode);
+  } else {
+    return keyCode;
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Normalizes key codes from their Gecko-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
  */
-ol.format.BinaryFeature.prototype.readFeatures = function(source) {
-  return this.readFeaturesFromBuffer(
-      ol.format.BinaryFeature.getBuffer_(source));
-};
-
-
-/**
- * @param {ol.binary.Buffer} buffer Buffer.
- * @protected
- * @return {ol.Feature} Feature.
- */
-ol.format.BinaryFeature.prototype.readFeatureFromBuffer = goog.abstractMethod;
-
-
-/**
- * @param {ol.binary.Buffer} buffer Buffer.
- * @protected
- * @return {Array.<ol.Feature>} Feature.
- */
-ol.format.BinaryFeature.prototype.readFeaturesFromBuffer = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
- */
-ol.format.BinaryFeature.prototype.readGeometry = function(source) {
-  return this.readGeometryFromBuffer(
-      ol.format.BinaryFeature.getBuffer_(source));
+goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) {
+  switch (keyCode) {
+    case goog.events.KeyCodes.FF_EQUALS:
+      return goog.events.KeyCodes.EQUALS;
+    case goog.events.KeyCodes.FF_SEMICOLON:
+      return goog.events.KeyCodes.SEMICOLON;
+    case goog.events.KeyCodes.FF_DASH:
+      return goog.events.KeyCodes.DASH;
+    case goog.events.KeyCodes.MAC_FF_META:
+      return goog.events.KeyCodes.META;
+    case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
+      return goog.events.KeyCodes.WIN_KEY;
+    default:
+      return keyCode;
+  }
 };
 
 
 /**
- * @param {ol.binary.Buffer} buffer Buffer.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.BinaryFeature.prototype.readGeometryFromBuffer = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
+ * Normalizes key codes from their Mac WebKit-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
  */
-ol.format.BinaryFeature.prototype.readProjection = function(source) {
-  return this.readProjectionFromBuffer(
-      ol.format.BinaryFeature.getBuffer_(source));
+goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) {
+  switch (keyCode) {
+    case goog.events.KeyCodes.MAC_WK_CMD_RIGHT:  // 93
+      return goog.events.KeyCodes.META;          // 91
+    default:
+      return keyCode;
+  }
 };
 
-
-/**
- * @param {ol.binary.Buffer} buffer Buffer.
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.BinaryFeature.prototype.readProjectionFromBuffer =
-    goog.abstractMethod;
-
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -42877,22231 +44277,23117 @@ ol.format.BinaryFeature.prototype.readProjectionFromBuffer =
 // limitations under the License.
 
 /**
- * @fileoverview JSON utility functions.
+ * @fileoverview This file contains a class for working with keyboard events
+ * that repeat consistently across browsers and platforms. It also unifies the
+ * key code so that it is the same in all browsers and platforms.
+ *
+ * Different web browsers have very different keyboard event handling. Most
+ * importantly is that only certain browsers repeat keydown events:
+ * IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
+ * FF/Mac and Safari 2 do not.
+ *
+ * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
+ * decided that they should try to match IE's key handling behavior.
+ * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
+ * Safari 2 behavior.
+ *
+ * Firefox, Safari, Opera prevent on keypress
+ *
+ * IE prevents on keydown
+ *
+ * Firefox does not fire keypress for shift, ctrl, alt
+ * Firefox does fire keydown for shift, ctrl, alt, meta
+ * Firefox does not repeat keydown for shift, ctrl, alt, meta
+ *
+ * Firefox does not fire keypress for up and down in an input
+ *
+ * Opera fires keypress for shift, ctrl, alt, meta
+ * Opera does not repeat keypress for shift, ctrl, alt, meta
+ *
+ * Safari 2 and 3 do not fire keypress for shift, ctrl, alt
+ * Safari 2 does not fire keydown for shift, ctrl, alt
+ * Safari 3 *does* fire keydown for shift, ctrl, alt
+ *
+ * IE provides the keycode for keyup/down events and the charcode (in the
+ * keycode field) for keypress.
+ *
+ * Mozilla provides the keycode for keyup/down and the charcode for keypress
+ * unless it's a non text modifying key in which case the keycode is provided.
+ *
+ * Safari 3 provides the keycode and charcode for all events.
+ *
+ * Opera provides the keycode for keyup/down event and either the charcode or
+ * the keycode (in the keycode field) for keypress events.
+ *
+ * Firefox x11 doesn't fire keydown events if a another key is already held down
+ * until the first key is released. This can cause a key event to be fired with
+ * a keyCode for the first key and a charCode for the second key.
+ *
+ * Safari in keypress
+ *
+ *        charCode keyCode which
+ * ENTER:       13      13    13
+ * F1:       63236   63236 63236
+ * F8:       63243   63243 63243
+ * ...
+ * p:          112     112   112
+ * P:           80      80    80
+ *
+ * Firefox, keypress:
+ *
+ *        charCode keyCode which
+ * ENTER:        0      13    13
+ * F1:           0     112     0
+ * F8:           0     119     0
+ * ...
+ * p:          112       0   112
+ * P:           80       0    80
+ *
+ * Opera, Mac+Win32, keypress:
+ *
+ *         charCode keyCode which
+ * ENTER: undefined      13    13
+ * F1:    undefined     112     0
+ * F8:    undefined     119     0
+ * ...
+ * p:     undefined     112   112
+ * P:     undefined      80    80
+ *
+ * IE7, keydown
+ *
+ *         charCode keyCode     which
+ * ENTER: undefined      13 undefined
+ * F1:    undefined     112 undefined
+ * F8:    undefined     119 undefined
+ * ...
+ * p:     undefined      80 undefined
+ * P:     undefined      80 undefined
+ *
  * @author arv@google.com (Erik Arvidsson)
+ * @author eae@google.com (Emil A Eklund)
+ * @see ../demos/keyhandler.html
  */
 
+goog.provide('goog.events.KeyEvent');
+goog.provide('goog.events.KeyHandler');
+goog.provide('goog.events.KeyHandler.EventType');
 
-goog.provide('goog.json');
-goog.provide('goog.json.Replacer');
-goog.provide('goog.json.Reviver');
-goog.provide('goog.json.Serializer');
-
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.userAgent');
 
-/**
- * @define {boolean} If true, use the native JSON parsing API.
- * NOTE(user): EXPERIMENTAL, handle with care.  Setting this to true might
- * break your code.  The default {@code goog.json.parse} implementation is able
- * to handle invalid JSON, such as JSPB.
- */
-goog.define('goog.json.USE_NATIVE_JSON', false);
 
 
 /**
- * Tests if a string is an invalid JSON string. This only ensures that we are
- * not using any invalid characters
- * @param {string} s The string to test.
- * @return {boolean} True if the input is a valid JSON string.
+ * A wrapper around an element that you want to listen to keyboard events on.
+ * @param {Element|Document=} opt_element The element or document to listen on.
+ * @param {boolean=} opt_capture Whether to listen for browser events in
+ *     capture phase (defaults to false).
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @final
  */
-goog.json.isValid = function(s) {
-  // All empty whitespace is not valid.
-  if (/^\s*$/.test(s)) {
-    return false;
-  }
-
-  // This is taken from http://www.json.org/json2.js which is released to the
-  // public domain.
-  // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
-  // inside strings.  We also treat \u2028 and \u2029 as whitespace which they
-  // are in the RFC but IE and Safari does not match \s to these so we need to
-  // include them in the reg exps in all places where whitespace is allowed.
-  // We allowed \x7f inside strings because some tools don't escape it,
-  // e.g. http://www.json.org/java/org/json/JSONObject.java
-
-  // Parsing happens in three stages. In the first stage, we run the text
-  // against regular expressions that look for non-JSON patterns. We are
-  // especially concerned with '()' and 'new' because they can cause invocation,
-  // and '=' because it can cause mutation. But just to be safe, we want to
-  // reject all unexpected forms.
-
-  // We split the first stage into 4 regexp operations in order to work around
-  // crippling inefficiencies in IE's and Safari's regexp engines. First we
-  // replace all backslash pairs with '@' (a non-JSON character). Second, we
-  // replace all simple value tokens with ']' characters. Third, we delete all
-  // open brackets that follow a colon or comma or that begin the text. Finally,
-  // we look to see that the remaining characters are only whitespace or ']' or
-  // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
-
-  // Don't make these static since they have the global flag.
-  var backslashesRe = /\\["\\\/bfnrtu]/g;
-  var simpleValuesRe =
-      /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
-  var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
-  var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
+goog.events.KeyHandler = function(opt_element, opt_capture) {
+  goog.events.EventTarget.call(this);
 
-  return remainderRe.test(s.replace(backslashesRe, '@').
-      replace(simpleValuesRe, ']').
-      replace(openBracketsRe, ''));
+  if (opt_element) {
+    this.attach(opt_element, opt_capture);
+  }
 };
+goog.inherits(goog.events.KeyHandler, goog.events.EventTarget);
 
 
 /**
- * Parses a JSON string and returns the result. This throws an exception if
- * the string is an invalid JSON string.
- *
- * Note that this is very slow on large strings. If you trust the source of
- * the string then you should use unsafeParse instead.
- *
- * @param {*} s The JSON string to parse.
- * @throws Error if s is invalid JSON.
- * @return {Object} The object generated from the JSON string, or null.
+ * This is the element that we will listen to the real keyboard events on.
+ * @type {Element|Document|null}
+ * @private
  */
-goog.json.parse = goog.json.USE_NATIVE_JSON ?
-    /** @type {function(*):Object} */ (goog.global['JSON']['parse']) :
-    function(s) {
-      var o = String(s);
-      if (goog.json.isValid(o)) {
-        /** @preserveTry */
-        try {
-          return /** @type {Object} */ (eval('(' + o + ')'));
-        } catch (ex) {
-        }
-      }
-      throw Error('Invalid JSON string: ' + o);
-    };
+goog.events.KeyHandler.prototype.element_ = null;
 
 
 /**
- * Parses a JSON string and returns the result. This uses eval so it is open
- * to security issues and it should only be used if you trust the source.
- *
- * @param {string} s The JSON string to parse.
- * @return {Object} The object generated from the JSON string.
+ * The key for the key press listener.
+ * @type {goog.events.Key}
+ * @private
  */
-goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ?
-    /** @type {function(string):Object} */ (goog.global['JSON']['parse']) :
-    function(s) {
-      return /** @type {Object} */ (eval('(' + s + ')'));
-    };
+goog.events.KeyHandler.prototype.keyPressKey_ = null;
 
 
 /**
- * JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
- * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
- *
- * TODO(nicksantos): Array should also be a valid replacer.
- *
- * @typedef {function(this:Object, string, *): *}
+ * The key for the key down listener.
+ * @type {goog.events.Key}
+ * @private
  */
-goog.json.Replacer;
+goog.events.KeyHandler.prototype.keyDownKey_ = null;
 
 
 /**
- * JSON reviver, as defined in Section 15.12.2 of the ES5 spec.
- * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
- *
- * @typedef {function(this:Object, string, *): *}
+ * The key for the key up listener.
+ * @type {goog.events.Key}
+ * @private
  */
-goog.json.Reviver;
+goog.events.KeyHandler.prototype.keyUpKey_ = null;
 
 
 /**
- * Serializes an object or a value to a JSON string.
- *
- * @param {*} object The object to serialize.
- * @param {?goog.json.Replacer=} opt_replacer A replacer function
- *     called for each (key, value) pair that determines how the value
- *     should be serialized. By defult, this just returns the value
- *     and allows default serialization to kick in.
- * @throws Error if there are loops in the object graph.
- * @return {string} A JSON string representation of the input.
+ * Used to detect keyboard repeat events.
+ * @private
+ * @type {number}
  */
-goog.json.serialize = goog.json.USE_NATIVE_JSON ?
-    /** @type {function(*, ?goog.json.Replacer=):string} */
-    (goog.global['JSON']['stringify']) :
-    function(object, opt_replacer) {
-      // NOTE(nicksantos): Currently, we never use JSON.stringify.
-      //
-      // The last time I evaluated this, JSON.stringify had subtle bugs and
-      // behavior differences on all browsers, and the performance win was not
-      // large enough to justify all the issues. This may change in the future
-      // as browser implementations get better.
-      //
-      // assertSerialize in json_test contains if branches for the cases
-      // that fail.
-      return new goog.json.Serializer(opt_replacer).serialize(object);
-    };
-
+goog.events.KeyHandler.prototype.lastKey_ = -1;
 
 
 /**
- * Class that is used to serialize JSON objects to a string.
- * @param {?goog.json.Replacer=} opt_replacer Replacer.
- * @constructor
+ * Keycode recorded for key down events. As most browsers don't report the
+ * keycode in the key press event we need to record it in the key down phase.
+ * @private
+ * @type {number}
  */
-goog.json.Serializer = function(opt_replacer) {
-  /**
-   * @type {goog.json.Replacer|null|undefined}
-   * @private
-   */
-  this.replacer_ = opt_replacer;
-};
+goog.events.KeyHandler.prototype.keyCode_ = -1;
 
 
 /**
- * Serializes an object or a value to a JSON string.
- *
- * @param {*} object The object to serialize.
- * @throws Error if there are loops in the object graph.
- * @return {string} A JSON string representation of the input.
+ * Alt key recorded for key down events. FF on Mac does not report the alt key
+ * flag in the key press event, we need to record it in the key down phase.
+ * @type {boolean}
+ * @private
  */
-goog.json.Serializer.prototype.serialize = function(object) {
-  var sb = [];
-  this.serializeInternal(object, sb);
-  return sb.join('');
-};
+goog.events.KeyHandler.prototype.altKey_ = false;
 
 
 /**
- * Serializes a generic value to a JSON string
- * @protected
- * @param {*} object The object to serialize.
- * @param {Array} sb Array used as a string builder.
- * @throws Error if there are loops in the object graph.
+ * Enum type for the events fired by the key handler
+ * @enum {string}
  */
-goog.json.Serializer.prototype.serializeInternal = function(object, sb) {
-  switch (typeof object) {
-    case 'string':
-      this.serializeString_(/** @type {string} */ (object), sb);
-      break;
-    case 'number':
-      this.serializeNumber_(/** @type {number} */ (object), sb);
-      break;
-    case 'boolean':
-      sb.push(object);
-      break;
-    case 'undefined':
-      sb.push('null');
-      break;
-    case 'object':
-      if (object == null) {
-        sb.push('null');
-        break;
-      }
-      if (goog.isArray(object)) {
-        this.serializeArray(/** @type {!Array} */ (object), sb);
-        break;
-      }
-      // should we allow new String, new Number and new Boolean to be treated
-      // as string, number and boolean? Most implementations do not and the
-      // need is not very big
-      this.serializeObject_(/** @type {Object} */ (object), sb);
-      break;
-    case 'function':
-      // Skip functions.
-      // TODO(user) Should we return something here?
-      break;
-    default:
-      throw Error('Unknown type: ' + typeof object);
-  }
+goog.events.KeyHandler.EventType = {
+  KEY: 'key'
 };
 
 
 /**
- * Character mappings used internally for goog.string.quote
+ * An enumeration of key codes that Safari 2 does incorrectly
+ * @type {Object}
  * @private
- * @type {!Object}
  */
-goog.json.Serializer.charToJsonCharCache_ = {
-  '\"': '\\"',
-  '\\': '\\\\',
-  '/': '\\/',
-  '\b': '\\b',
-  '\f': '\\f',
-  '\n': '\\n',
-  '\r': '\\r',
-  '\t': '\\t',
-
-  '\x0B': '\\u000b' // '\v' is not supported in JScript
+goog.events.KeyHandler.safariKey_ = {
+  '3': goog.events.KeyCodes.ENTER, // 13
+  '12': goog.events.KeyCodes.NUMLOCK, // 144
+  '63232': goog.events.KeyCodes.UP, // 38
+  '63233': goog.events.KeyCodes.DOWN, // 40
+  '63234': goog.events.KeyCodes.LEFT, // 37
+  '63235': goog.events.KeyCodes.RIGHT, // 39
+  '63236': goog.events.KeyCodes.F1, // 112
+  '63237': goog.events.KeyCodes.F2, // 113
+  '63238': goog.events.KeyCodes.F3, // 114
+  '63239': goog.events.KeyCodes.F4, // 115
+  '63240': goog.events.KeyCodes.F5, // 116
+  '63241': goog.events.KeyCodes.F6, // 117
+  '63242': goog.events.KeyCodes.F7, // 118
+  '63243': goog.events.KeyCodes.F8, // 119
+  '63244': goog.events.KeyCodes.F9, // 120
+  '63245': goog.events.KeyCodes.F10, // 121
+  '63246': goog.events.KeyCodes.F11, // 122
+  '63247': goog.events.KeyCodes.F12, // 123
+  '63248': goog.events.KeyCodes.PRINT_SCREEN, // 44
+  '63272': goog.events.KeyCodes.DELETE, // 46
+  '63273': goog.events.KeyCodes.HOME, // 36
+  '63275': goog.events.KeyCodes.END, // 35
+  '63276': goog.events.KeyCodes.PAGE_UP, // 33
+  '63277': goog.events.KeyCodes.PAGE_DOWN, // 34
+  '63289': goog.events.KeyCodes.NUMLOCK, // 144
+  '63302': goog.events.KeyCodes.INSERT // 45
 };
 
 
 /**
- * Regular expression used to match characters that need to be replaced.
- * The S60 browser has a bug where unicode characters are not matched by
- * regular expressions. The condition below detects such behaviour and
- * adjusts the regular expression accordingly.
+ * An enumeration of key identifiers currently part of the W3C draft for DOM3
+ * and their mappings to keyCodes.
+ * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
+ * This is currently supported in Safari and should be platform independent.
+ * @type {Object}
  * @private
- * @type {!RegExp}
  */
-goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
-    /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;
+goog.events.KeyHandler.keyIdentifier_ = {
+  'Up': goog.events.KeyCodes.UP, // 38
+  'Down': goog.events.KeyCodes.DOWN, // 40
+  'Left': goog.events.KeyCodes.LEFT, // 37
+  'Right': goog.events.KeyCodes.RIGHT, // 39
+  'Enter': goog.events.KeyCodes.ENTER, // 13
+  'F1': goog.events.KeyCodes.F1, // 112
+  'F2': goog.events.KeyCodes.F2, // 113
+  'F3': goog.events.KeyCodes.F3, // 114
+  'F4': goog.events.KeyCodes.F4, // 115
+  'F5': goog.events.KeyCodes.F5, // 116
+  'F6': goog.events.KeyCodes.F6, // 117
+  'F7': goog.events.KeyCodes.F7, // 118
+  'F8': goog.events.KeyCodes.F8, // 119
+  'F9': goog.events.KeyCodes.F9, // 120
+  'F10': goog.events.KeyCodes.F10, // 121
+  'F11': goog.events.KeyCodes.F11, // 122
+  'F12': goog.events.KeyCodes.F12, // 123
+  'U+007F': goog.events.KeyCodes.DELETE, // 46
+  'Home': goog.events.KeyCodes.HOME, // 36
+  'End': goog.events.KeyCodes.END, // 35
+  'PageUp': goog.events.KeyCodes.PAGE_UP, // 33
+  'PageDown': goog.events.KeyCodes.PAGE_DOWN, // 34
+  'Insert': goog.events.KeyCodes.INSERT // 45
+};
 
 
 /**
- * Serializes a string to a JSON string
+ * If true, the KeyEvent fires on keydown. Otherwise, it fires on keypress.
+ *
+ * @type {boolean}
  * @private
- * @param {string} s The string to serialize.
- * @param {Array} sb Array used as a string builder.
  */
-goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
-  // The official JSON implementation does not work with international
-  // characters.
-  sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
-    // caching the result improves performance by a factor 2-3
-    if (c in goog.json.Serializer.charToJsonCharCache_) {
-      return goog.json.Serializer.charToJsonCharCache_[c];
-    }
-
-    var cc = c.charCodeAt(0);
-    var rv = '\\u';
-    if (cc < 16) {
-      rv += '000';
-    } else if (cc < 256) {
-      rv += '00';
-    } else if (cc < 4096) { // \u1000
-      rv += '0';
-    }
-    return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16);
-  }), '"');
-};
+goog.events.KeyHandler.USES_KEYDOWN_ = goog.userAgent.IE ||
+    goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525');
 
 
 /**
- * Serializes a number to a JSON string
+ * If true, the alt key flag is saved during the key down and reused when
+ * handling the key press. FF on Mac does not set the alt flag in the key press
+ * event.
+ * @type {boolean}
  * @private
- * @param {number} n The number to serialize.
- * @param {Array} sb Array used as a string builder.
- */
-goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
-  sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
-};
-
-
-/**
- * Serializes an array to a JSON string
- * @param {Array} arr The array to serialize.
- * @param {Array} sb Array used as a string builder.
- * @protected
  */
-goog.json.Serializer.prototype.serializeArray = function(arr, sb) {
-  var l = arr.length;
-  sb.push('[');
-  var sep = '';
-  for (var i = 0; i < l; i++) {
-    sb.push(sep);
-
-    var value = arr[i];
-    this.serializeInternal(
-        this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
-        sb);
-
-    sep = ',';
-  }
-  sb.push(']');
-};
+goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_ = goog.userAgent.MAC &&
+    goog.userAgent.GECKO;
 
 
 /**
- * Serializes an object to a JSON string
+ * Records the keycode for browsers that only returns the keycode for key up/
+ * down events. For browser/key combinations that doesn't trigger a key pressed
+ * event it also fires the patched key event.
+ * @param {goog.events.BrowserEvent} e The key down event.
  * @private
- * @param {Object} obj The object to serialize.
- * @param {Array} sb Array used as a string builder.
  */
-goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
-  sb.push('{');
-  var sep = '';
-  for (var key in obj) {
-    if (Object.prototype.hasOwnProperty.call(obj, key)) {
-      var value = obj[key];
-      // Skip functions.
-      // TODO(ptucker) Should we return something for function properties?
-      if (typeof value != 'function') {
-        sb.push(sep);
-        this.serializeString_(key, sb);
-        sb.push(':');
+goog.events.KeyHandler.prototype.handleKeyDown_ = function(e) {
+  // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
+  // before we've caught a key-up event.  If the last-key was one of these we
+  // reset the state.
+  if (goog.userAgent.WEBKIT) {
+    if (this.lastKey_ == goog.events.KeyCodes.CTRL && !e.ctrlKey ||
+        this.lastKey_ == goog.events.KeyCodes.ALT && !e.altKey ||
+        goog.userAgent.MAC &&
+        this.lastKey_ == goog.events.KeyCodes.META && !e.metaKey) {
+      this.lastKey_ = -1;
+      this.keyCode_ = -1;
+    }
+  }
 
-        this.serializeInternal(
-            this.replacer_ ? this.replacer_.call(obj, key, value) : value,
-            sb);
+  if (this.lastKey_ == -1) {
+    if (e.ctrlKey && e.keyCode != goog.events.KeyCodes.CTRL) {
+      this.lastKey_ = goog.events.KeyCodes.CTRL;
+    } else if (e.altKey && e.keyCode != goog.events.KeyCodes.ALT) {
+      this.lastKey_ = goog.events.KeyCodes.ALT;
+    } else if (e.metaKey && e.keyCode != goog.events.KeyCodes.META) {
+      this.lastKey_ = goog.events.KeyCodes.META;
+    }
+  }
 
-        sep = ',';
-      }
+  if (goog.events.KeyHandler.USES_KEYDOWN_ &&
+      !goog.events.KeyCodes.firesKeyPressEvent(e.keyCode,
+          this.lastKey_, e.shiftKey, e.ctrlKey, e.altKey)) {
+    this.handleEvent(e);
+  } else {
+    this.keyCode_ = goog.events.KeyCodes.normalizeKeyCode(e.keyCode);
+    if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
+      this.altKey_ = e.altKey;
     }
   }
-  sb.push('}');
 };
 
-goog.provide('ol.format.JSONFeature');
-
-goog.require('goog.asserts');
-goog.require('goog.json');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-
-
 
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for JSON feature formats.
- *
- * @constructor
- * @extends {ol.format.Feature}
+ * Resets the stored previous values. Needed to be called for webkit which will
+ * not generate a key up for meta key operations. This should only be called
+ * when having finished with repeat key possiblities.
  */
-ol.format.JSONFeature = function() {
-  goog.base(this);
+goog.events.KeyHandler.prototype.resetState = function() {
+  this.lastKey_ = -1;
+  this.keyCode_ = -1;
 };
-goog.inherits(ol.format.JSONFeature, ol.format.Feature);
 
 
 /**
- * @param {Document|Node|Object|string} source Source.
+ * Clears the stored previous key value, resetting the key repeat status. Uses
+ * -1 because the Safari 3 Windows beta reports 0 for certain keys (like Home
+ * and End.)
+ * @param {goog.events.BrowserEvent} e The keyup event.
  * @private
- * @return {Object} Object.
  */
-ol.format.JSONFeature.prototype.getObject_ = function(source) {
-  if (goog.isObject(source)) {
-    return source;
-  } else if (goog.isString(source)) {
-    var object = goog.json.parse(source);
-    return goog.isDef(object) ? object : null;
-  } else {
-    goog.asserts.fail();
-    return null;
-  }
+goog.events.KeyHandler.prototype.handleKeyup_ = function(e) {
+  this.resetState();
+  this.altKey_ = e.altKey;
 };
 
 
 /**
- * @inheritDoc
+ * Handles the events on the element.
+ * @param {goog.events.BrowserEvent} e  The keyboard event sent from the
+ *     browser.
  */
-ol.format.JSONFeature.prototype.getType = function() {
-  return ol.format.FormatType.JSON;
-};
-
+goog.events.KeyHandler.prototype.handleEvent = function(e) {
+  var be = e.getBrowserEvent();
+  var keyCode, charCode;
+  var altKey = be.altKey;
 
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) {
-  return this.readFeatureFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
-};
+  // IE reports the character code in the keyCode field for keypress events.
+  // There are two exceptions however, Enter and Escape.
+  if (goog.userAgent.IE && e.type == goog.events.EventType.KEYPRESS) {
+    keyCode = this.keyCode_;
+    charCode = keyCode != goog.events.KeyCodes.ENTER &&
+        keyCode != goog.events.KeyCodes.ESC ?
+            be.keyCode : 0;
 
+  // Safari reports the character code in the keyCode field for keypress
+  // events but also has a charCode field.
+  } else if (goog.userAgent.WEBKIT &&
+      e.type == goog.events.EventType.KEYPRESS) {
+    keyCode = this.keyCode_;
+    charCode = be.charCode >= 0 && be.charCode < 63232 &&
+        goog.events.KeyCodes.isCharacterKey(keyCode) ?
+            be.charCode : 0;
 
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) {
-  return this.readFeaturesFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
-};
+  // Opera reports the keycode or the character code in the keyCode field.
+  } else if (goog.userAgent.OPERA) {
+    keyCode = this.keyCode_;
+    charCode = goog.events.KeyCodes.isCharacterKey(keyCode) ?
+        be.keyCode : 0;
 
+  // Mozilla reports the character code in the charCode field.
+  } else {
+    keyCode = be.keyCode || this.keyCode_;
+    charCode = be.charCode || 0;
+    if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
+      altKey = this.altKey_;
+    }
+    // On the Mac, shift-/ triggers a question mark char code and no key code
+    // (normalized to WIN_KEY), so we synthesize the latter.
+    if (goog.userAgent.MAC &&
+        charCode == goog.events.KeyCodes.QUESTION_MARK &&
+        keyCode == goog.events.KeyCodes.WIN_KEY) {
+      keyCode = goog.events.KeyCodes.SLASH;
+    }
+  }
 
-/**
- * @param {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.Feature} Feature.
- */
-ol.format.JSONFeature.prototype.readFeatureFromObject = goog.abstractMethod;
+  keyCode = goog.events.KeyCodes.normalizeKeyCode(keyCode);
+  var key = keyCode;
+  var keyIdentifier = be.keyIdentifier;
 
+  // Correct the key value for certain browser-specific quirks.
+  if (keyCode) {
+    if (keyCode >= 63232 && keyCode in goog.events.KeyHandler.safariKey_) {
+      // NOTE(nicksantos): Safari 3 has fixed this problem,
+      // this is only needed for Safari 2.
+      key = goog.events.KeyHandler.safariKey_[keyCode];
+    } else {
 
-/**
- * @param {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.JSONFeature.prototype.readFeaturesFromObject = goog.abstractMethod;
+      // Safari returns 25 for Shift+Tab instead of 9.
+      if (keyCode == 25 && e.shiftKey) {
+        key = 9;
+      }
+    }
+  } else if (keyIdentifier &&
+             keyIdentifier in goog.events.KeyHandler.keyIdentifier_) {
+    // This is needed for Safari Windows because it currently doesn't give a
+    // keyCode/which for non printable keys.
+    key = goog.events.KeyHandler.keyIdentifier_[keyIdentifier];
+  }
 
+  // If we get the same keycode as a keydown/keypress without having seen a
+  // keyup event, then this event was caused by key repeat.
+  var repeat = key == this.lastKey_;
+  this.lastKey_ = key;
 
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) {
-  return this.readGeometryFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
+  var event = new goog.events.KeyEvent(key, charCode, repeat, be);
+  event.altKey = altKey;
+  this.dispatchEvent(event);
 };
 
 
 /**
- * @param {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
+ * Returns the element listened on for the real keyboard events.
+ * @return {Element|Document|null} The element listened on for the real
+ *     keyboard events.
  */
-ol.format.JSONFeature.prototype.readGeometryFromObject = goog.abstractMethod;
+goog.events.KeyHandler.prototype.getElement = function() {
+  return this.element_;
+};
 
 
 /**
- * @inheritDoc
+ * Adds the proper key event listeners to the element.
+ * @param {Element|Document} element The element to listen on.
+ * @param {boolean=} opt_capture Whether to listen for browser events in
+ *     capture phase (defaults to false).
  */
-ol.format.JSONFeature.prototype.readProjection = function(source) {
-  return this.readProjectionFromObject(this.getObject_(source));
-};
+goog.events.KeyHandler.prototype.attach = function(element, opt_capture) {
+  if (this.keyUpKey_) {
+    this.detach();
+  }
 
+  this.element_ = element;
 
-/**
- * @param {Object} object Object.
- * @protected
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.JSONFeature.prototype.readProjectionFromObject = goog.abstractMethod;
+  this.keyPressKey_ = goog.events.listen(this.element_,
+                                         goog.events.EventType.KEYPRESS,
+                                         this,
+                                         opt_capture);
+
+  // Most browsers (Safari 2 being the notable exception) doesn't include the
+  // keyCode in keypress events (IE has the char code in the keyCode field and
+  // Mozilla only included the keyCode if there's no charCode). Thus we have to
+  // listen for keydown to capture the keycode.
+  this.keyDownKey_ = goog.events.listen(this.element_,
+                                        goog.events.EventType.KEYDOWN,
+                                        this.handleKeyDown_,
+                                        opt_capture,
+                                        this);
 
 
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) {
-  return this.writeFeatureObject(feature, this.adaptOptions(opt_options));
+  this.keyUpKey_ = goog.events.listen(this.element_,
+                                      goog.events.EventType.KEYUP,
+                                      this.handleKeyup_,
+                                      opt_capture,
+                                      this);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {Object} Object.
+ * Removes the listeners that may exist.
  */
-ol.format.JSONFeature.prototype.writeFeatureObject = goog.abstractMethod;
+goog.events.KeyHandler.prototype.detach = function() {
+  if (this.keyPressKey_) {
+    goog.events.unlistenByKey(this.keyPressKey_);
+    goog.events.unlistenByKey(this.keyDownKey_);
+    goog.events.unlistenByKey(this.keyUpKey_);
+    this.keyPressKey_ = null;
+    this.keyDownKey_ = null;
+    this.keyUpKey_ = null;
+  }
+  this.element_ = null;
+  this.lastKey_ = -1;
+  this.keyCode_ = -1;
+};
 
 
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.writeFeatures = function(
-    features, opt_options) {
-  return this.writeFeaturesObject(features, this.adaptOptions(opt_options));
+/** @override */
+goog.events.KeyHandler.prototype.disposeInternal = function() {
+  goog.events.KeyHandler.superClass_.disposeInternal.call(this);
+  this.detach();
 };
 
 
+
 /**
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {Object} Object.
+ * This class is used for the goog.events.KeyHandler.EventType.KEY event and
+ * it overrides the key code with the fixed key code.
+ * @param {number} keyCode The adjusted key code.
+ * @param {number} charCode The unicode character code.
+ * @param {boolean} repeat Whether this event was generated by keyboard repeat.
+ * @param {Event} browserEvent Browser event object.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
  */
-ol.format.JSONFeature.prototype.writeFeaturesObject = goog.abstractMethod;
+goog.events.KeyEvent = function(keyCode, charCode, repeat, browserEvent) {
+  goog.events.BrowserEvent.call(this, browserEvent);
+  this.type = goog.events.KeyHandler.EventType.KEY;
+
+  /**
+   * Keycode of key press.
+   * @type {number}
+   */
+  this.keyCode = keyCode;
 
+  /**
+   * Unicode character code.
+   * @type {number}
+   */
+  this.charCode = charCode;
 
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.writeGeometry = function(
-    geometry, opt_options) {
-  return this.writeGeometryObject(geometry, this.adaptOptions(opt_options));
+  /**
+   * True if this event was generated by keyboard auto-repeat (i.e., the user is
+   * holding the key down.)
+   * @type {boolean}
+   */
+  this.repeat = repeat;
 };
+goog.inherits(goog.events.KeyEvent, goog.events.BrowserEvent);
 
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {Object} Object.
+ * @fileoverview This event wrapper will dispatch an event when the user uses
+ * the mouse wheel to scroll an element. You can get the direction by checking
+ * the deltaX and deltaY properties of the event.
+ *
+ * This class aims to smooth out inconsistencies between browser platforms with
+ * regards to mousewheel events, but we do not cover every possible
+ * software/hardware combination out there, some of which occasionally produce
+ * very large deltas in mousewheel events. If your application wants to guard
+ * against extremely large deltas, use the setMaxDeltaX and setMaxDeltaY APIs
+ * to set maximum values that make sense for your application.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @see ../demos/mousewheelhandler.html
  */
-ol.format.JSONFeature.prototype.writeGeometryObject = goog.abstractMethod;
-
-// FIXME coordinate order
-// FIXME reprojection
 
-goog.provide('ol.format.GeoJSON');
+goog.provide('goog.events.MouseWheelEvent');
+goog.provide('goog.events.MouseWheelHandler');
+goog.provide('goog.events.MouseWheelHandler.EventType');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.JSONFeature');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.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.proj');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.math');
+goog.require('goog.style');
+goog.require('goog.userAgent');
 
 
 
 /**
- * @classdesc
- * Feature format for reading and writing data in the GeoJSON format.
- *
+ * This event handler allows you to catch mouse wheel events in a consistent
+ * manner.
+ * @param {Element|Document} element The element to listen to the mouse wheel
+ *     event on.
+ * @param {boolean=} opt_capture Whether to handle the mouse wheel event in
+ *     capture phase.
  * @constructor
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.GeoJSONOptions=} opt_options Options.
- * @api stable
+ * @extends {goog.events.EventTarget}
  */
-ol.format.GeoJSON = function(opt_options) {
+goog.events.MouseWheelHandler = function(element, opt_capture) {
+  goog.events.EventTarget.call(this);
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  /**
+   * This is the element that we will listen to the real mouse wheel events on.
+   * @type {Element|Document}
+   * @private
+   */
+  this.element_ = element;
 
-  goog.base(this);
+  var rtlElement = goog.dom.isElement(this.element_) ?
+      /** @type {Element} */ (this.element_) :
+      (this.element_ ? /** @type {Document} */ (this.element_).body : null);
 
   /**
-   * @inheritDoc
+   * True if the element exists and is RTL, false otherwise.
+   * @type {boolean}
+   * @private
    */
-  this.defaultDataProjection = ol.proj.get(
-      goog.isDefAndNotNull(options.defaultDataProjection) ?
-          options.defaultDataProjection : 'EPSG:4326');
+  this.isRtl_ = !!rtlElement && goog.style.isRightToLeft(rtlElement);
 
+  var type = goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel';
 
   /**
-   * Name of the geometry attribute for features.
-   * @type {string|undefined}
+   * The key returned from the goog.events.listen.
+   * @type {goog.events.Key}
    * @private
    */
-  this.geometryName_ = options.geometryName;
+  this.listenKey_ = goog.events.listen(this.element_, type, this, opt_capture);
+};
+goog.inherits(goog.events.MouseWheelHandler, goog.events.EventTarget);
 
+
+/**
+ * Enum type for the events fired by the mouse wheel handler.
+ * @enum {string}
+ */
+goog.events.MouseWheelHandler.EventType = {
+  MOUSEWHEEL: 'mousewheel'
 };
-goog.inherits(ol.format.GeoJSON, ol.format.JSONFeature);
 
 
 /**
- * @const
- * @type {Array.<string>}
+ * Optional maximum magnitude for x delta on each mousewheel event.
+ * @type {number|undefined}
  * @private
  */
-ol.format.GeoJSON.EXTENSIONS_ = ['.geojson'];
+goog.events.MouseWheelHandler.prototype.maxDeltaX_;
 
 
 /**
- * @param {GeoJSONObject} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
+ * Optional maximum magnitude for y delta on each mousewheel event.
+ * @type {number|undefined}
  * @private
- * @return {ol.geom.Geometry} Geometry.
  */
-ol.format.GeoJSON.readGeometry_ = function(object, opt_options) {
-  if (goog.isNull(object)) {
-    return null;
-  }
-  var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type];
-  goog.asserts.assert(goog.isDef(geometryReader));
-  return /** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(
-          geometryReader(object), false, opt_options));
-};
+goog.events.MouseWheelHandler.prototype.maxDeltaY_;
 
 
 /**
- * @param {GeoJSONGeometryCollection} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @private
- * @return {ol.geom.GeometryCollection} Geometry collection.
+ * @param {number} maxDeltaX Maximum magnitude for x delta on each mousewheel
+ *     event. Should be non-negative.
  */
-ol.format.GeoJSON.readGeometryCollectionGeometry_ = function(
-    object, opt_options) {
-  goog.asserts.assert(object.type == 'GeometryCollection');
-  var geometries = goog.array.map(object.geometries,
-      /**
-       * @param {GeoJSONObject} geometry Geometry.
-       * @return {ol.geom.Geometry} geometry Geometry.
-       */
-      function(geometry) {
-        return ol.format.GeoJSON.readGeometry_(geometry, opt_options);
-      });
-  return new ol.geom.GeometryCollection(geometries);
+goog.events.MouseWheelHandler.prototype.setMaxDeltaX = function(maxDeltaX) {
+  this.maxDeltaX_ = maxDeltaX;
 };
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Point} Point.
+ * @param {number} maxDeltaY Maximum magnitude for y delta on each mousewheel
+ *     event. Should be non-negative.
  */
-ol.format.GeoJSON.readPointGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'Point');
-  return new ol.geom.Point(object.coordinates);
+goog.events.MouseWheelHandler.prototype.setMaxDeltaY = function(maxDeltaY) {
+  this.maxDeltaY_ = maxDeltaY;
 };
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.LineString} LineString.
+ * Handles the events on the element.
+ * @param {goog.events.BrowserEvent} e The underlying browser event.
  */
-ol.format.GeoJSON.readLineStringGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'LineString');
-  return new ol.geom.LineString(object.coordinates);
-};
+goog.events.MouseWheelHandler.prototype.handleEvent = function(e) {
+  var deltaX = 0;
+  var deltaY = 0;
+  var detail = 0;
+  var be = e.getBrowserEvent();
+  if (be.type == 'mousewheel') {
+    var wheelDeltaScaleFactor = 1;
+    if (goog.userAgent.IE ||
+        goog.userAgent.WEBKIT &&
+        (goog.userAgent.WINDOWS || goog.userAgent.isVersionOrHigher('532.0'))) {
+      // In IE we get a multiple of 120; we adjust to a multiple of 3 to
+      // represent number of lines scrolled (like Gecko).
+      // Newer versions of Webkit match IE behavior, and WebKit on
+      // Windows also matches IE behavior.
+      // See bug https://bugs.webkit.org/show_bug.cgi?id=24368
+      wheelDeltaScaleFactor = 40;
+    }
 
+    detail = goog.events.MouseWheelHandler.smartScale_(
+        -be.wheelDelta, wheelDeltaScaleFactor);
+    if (goog.isDef(be.wheelDeltaX)) {
+      // Webkit has two properties to indicate directional scroll, and
+      // can scroll both directions at once.
+      deltaX = goog.events.MouseWheelHandler.smartScale_(
+          -be.wheelDeltaX, wheelDeltaScaleFactor);
+      deltaY = goog.events.MouseWheelHandler.smartScale_(
+          -be.wheelDeltaY, wheelDeltaScaleFactor);
+    } else {
+      deltaY = detail;
+    }
 
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiLineString} MultiLineString.
- */
-ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'MultiLineString');
-  return new ol.geom.MultiLineString(object.coordinates);
-};
+    // Historical note: Opera (pre 9.5) used to negate the detail value.
+  } else { // Gecko
+    // Gecko returns multiple of 3 (representing the number of lines scrolled)
+    detail = be.detail;
 
+    // Gecko sometimes returns really big values if the user changes settings to
+    // scroll a whole page per scroll
+    if (detail > 100) {
+      detail = 3;
+    } else if (detail < -100) {
+      detail = -3;
+    }
 
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiPoint} MultiPoint.
- */
-ol.format.GeoJSON.readMultiPointGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'MultiPoint');
-  return new ol.geom.MultiPoint(object.coordinates);
-};
+    // Firefox 3.1 adds an axis field to the event to indicate direction of
+    // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
+    if (goog.isDef(be.axis) && be.axis === be.HORIZONTAL_AXIS) {
+      deltaX = detail;
+    } else {
+      deltaY = detail;
+    }
+  }
 
+  if (goog.isNumber(this.maxDeltaX_)) {
+    deltaX = goog.math.clamp(deltaX, -this.maxDeltaX_, this.maxDeltaX_);
+  }
+  if (goog.isNumber(this.maxDeltaY_)) {
+    deltaY = goog.math.clamp(deltaY, -this.maxDeltaY_, this.maxDeltaY_);
+  }
+  // Don't clamp 'detail', since it could be ambiguous which axis it refers to
+  // and because it's informally deprecated anyways.
 
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiPolygon} MultiPolygon.
- */
-ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'MultiPolygon');
-  return new ol.geom.MultiPolygon(object.coordinates);
+  // For horizontal scrolling we need to flip the value for RTL grids.
+  if (this.isRtl_) {
+    deltaX = -deltaX;
+  }
+  var newEvent = new goog.events.MouseWheelEvent(detail, be, deltaX, deltaY);
+  this.dispatchEvent(newEvent);
 };
 
 
 /**
- * @param {GeoJSONGeometry} object Object.
+ * Helper for scaling down a mousewheel delta by a scale factor, if appropriate.
+ * @param {number} mouseWheelDelta Delta from a mouse wheel event. Expected to
+ *     be an integer.
+ * @param {number} scaleFactor Factor to scale the delta down by. Expected to
+ *     be an integer.
+ * @return {number} Scaled-down delta value, or the original delta if the
+ *     scaleFactor does not appear to be applicable.
  * @private
- * @return {ol.geom.Polygon} Polygon.
  */
-ol.format.GeoJSON.readPolygonGeometry_ = function(object) {
-  goog.asserts.assert(object.type == 'Polygon');
-  return new ol.geom.Polygon(object.coordinates);
+goog.events.MouseWheelHandler.smartScale_ = function(mouseWheelDelta,
+    scaleFactor) {
+  // The basic problem here is that in Webkit on Mac and Linux, we can get two
+  // very different types of mousewheel events: from continuous devices
+  // (touchpads, Mighty Mouse) or non-continuous devices (normal wheel mice).
+  //
+  // Non-continuous devices in Webkit get their wheel deltas scaled up to
+  // behave like IE. Continuous devices return much smaller unscaled values
+  // (which most of the time will not be cleanly divisible by the IE scale
+  // factor), so we should not try to normalize them down.
+  //
+  // Detailed discussion:
+  //   https://bugs.webkit.org/show_bug.cgi?id=29601
+  //   http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
+  if (goog.userAgent.WEBKIT &&
+      (goog.userAgent.MAC || goog.userAgent.LINUX) &&
+      (mouseWheelDelta % scaleFactor) != 0) {
+    return mouseWheelDelta;
+  } else {
+    return mouseWheelDelta / scaleFactor;
+  }
 };
 
 
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry.
- */
-ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) {
-  var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()];
-  goog.asserts.assert(goog.isDef(geometryWriter));
-  return geometryWriter(/** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(geometry, true, opt_options)));
+/** @override */
+goog.events.MouseWheelHandler.prototype.disposeInternal = function() {
+  goog.events.MouseWheelHandler.superClass_.disposeInternal.call(this);
+  goog.events.unlistenByKey(this.listenKey_);
+  this.listenKey_ = null;
 };
 
 
+
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection.
+ * A base class for mouse wheel events. This is used with the
+ * MouseWheelHandler.
+ *
+ * @param {number} detail The number of rows the user scrolled.
+ * @param {Event} browserEvent Browser event object.
+ * @param {number} deltaX The number of rows the user scrolled in the X
+ *     direction.
+ * @param {number} deltaY The number of rows the user scrolled in the Y
+ *     direction.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
  */
-ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) {
-  return /** @type {GeoJSONGeometryCollection} */ ({
-    'type': 'GeometryCollection',
-    'geometries': []
-  });
-};
+goog.events.MouseWheelEvent = function(detail, browserEvent, deltaX, deltaY) {
+  goog.events.BrowserEvent.call(this, browserEvent);
 
+  this.type = goog.events.MouseWheelHandler.EventType.MOUSEWHEEL;
 
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometryCollection} GeoJSON geometry collection.
- */
-ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function(
-    geometry, opt_options) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection);
-  var geometries = goog.array.map(
-      geometry.getGeometriesArray(), function(geometry) {
-        return ol.format.GeoJSON.writeGeometry_(geometry, opt_options);
-      });
-  return /** @type {GeoJSONGeometryCollection} */ ({
-    'type': 'GeometryCollection',
-    'geometries': geometries
-  });
-};
+  /**
+   * The number of lines the user scrolled
+   * @type {number}
+   * NOTE: Informally deprecated. Use deltaX and deltaY instead, they provide
+   * more information.
+   */
+  this.detail = detail;
 
+  /**
+   * The number of "lines" scrolled in the X direction.
+   *
+   * Note that not all browsers provide enough information to distinguish
+   * horizontal and vertical scroll events, so for these unsupported browsers,
+   * we will always have a deltaX of 0, even if the user scrolled their mouse
+   * wheel or trackpad sideways.
+   *
+   * Currently supported browsers are Webkit and Firefox 3.1 or later.
+   *
+   * @type {number}
+   */
+  this.deltaX = deltaX;
 
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
- */
-ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-  return /** @type {GeoJSONGeometry} */ ({
-    'type': 'LineString',
-    'coordinates': geometry.getCoordinates()
-  });
+  /**
+   * The number of lines scrolled in the Y direction.
+   * @type {number}
+   */
+  this.deltaY = deltaY;
 };
+goog.inherits(goog.events.MouseWheelEvent, goog.events.BrowserEvent);
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * @fileoverview Basic strippable logging definitions.
+ * @see http://go/closurelogging
+ *
+ * @author johnlenz@google.com (John Lenz)
  */
-ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
-  goog.asserts.assert(
-      geometry.getType() == ol.geom.GeometryType.MULTI_LINE_STRING);
-  return /** @type {GeoJSONGeometry} */ ({
-    'type': 'MultiLineString',
-    'coordinates': geometry.getCoordinates()
-  });
-};
+
+goog.provide('goog.log');
+goog.provide('goog.log.Level');
+goog.provide('goog.log.LogRecord');
+goog.provide('goog.log.Logger');
+
+goog.require('goog.debug');
+goog.require('goog.debug.LogManager');
+goog.require('goog.debug.LogRecord');
+goog.require('goog.debug.Logger');
+
+
+/** @define {boolean} Whether logging is enabled. */
+goog.define('goog.log.ENABLED', goog.debug.LOGGING_ENABLED);
+
+
+/** @const */
+goog.log.ROOT_LOGGER_NAME = goog.debug.Logger.ROOT_LOGGER_NAME;
+
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * @constructor
+ * @final
  */
-ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint);
-  return /** @type {GeoJSONGeometry} */ ({
-    'type': 'MultiPoint',
-    'coordinates': geometry.getCoordinates()
-  });
-};
+goog.log.Logger = goog.debug.Logger;
+
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * @constructor
+ * @final
  */
-ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon);
-  return /** @type {GeoJSONGeometry} */ ({
-    'type': 'MultiPolygon',
-    'coordinates': geometry.getCoordinates()
-  });
-};
+goog.log.Level = goog.debug.Logger.Level;
+
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * @constructor
+ * @final
  */
-ol.format.GeoJSON.writePointGeometry_ = function(geometry) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Point);
-  return /** @type {GeoJSONGeometry} */ ({
-    'type': 'Point',
-    'coordinates': geometry.getCoordinates()
-  });
-};
+goog.log.LogRecord = goog.debug.LogRecord;
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
+ * Finds or creates a logger for a named subsystem. If a logger has already been
+ * created with the given name it is returned. Otherwise a new logger is
+ * created. If a new logger is created its log level will be configured based
+ * on the goog.debug.LogManager configuration and it will configured to also
+ * send logging output to its parent's handlers.
+ * @see goog.debug.LogManager
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ *     name and should normally be based on the package name or class name of
+ *     the subsystem, such as goog.net.BrowserChannel.
+ * @param {goog.log.Level=} opt_level If provided, override the
+ *     default logging level with the provided level.
+ * @return {goog.log.Logger} The named logger or null if logging is disabled.
  */
-ol.format.GeoJSON.writePolygonGeometry_ = function(geometry) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
-  return /** @type {GeoJSONGeometry} */ ({
-    'type': 'Polygon',
-    'coordinates': geometry.getCoordinates()
-  });
+goog.log.getLogger = function(name, opt_level) {
+  if (goog.log.ENABLED) {
+    var logger = goog.debug.LogManager.getLogger(name);
+    if (opt_level && logger) {
+      logger.setLevel(opt_level);
+    }
+    return logger;
+  } else {
+    return null;
+  }
 };
 
 
+// TODO(johnlenz): try to tighten the types to these functions.
 /**
- * @const
- * @private
- * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>}
+ * Adds a handler to the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {goog.log.Logger} logger
+ * @param {Function} handler Handler function to add.
  */
-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_
+goog.log.addHandler = function(logger, handler) {
+  if (goog.log.ENABLED && logger) {
+    logger.addHandler(handler);
+  }
 };
 
 
 /**
- * @const
- * @private
- * @type {Object.<string, function(ol.geom.Geometry): (GeoJSONGeometry|GeoJSONGeometryCollection)>}
+ * Removes a handler from the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {goog.log.Logger} logger
+ * @param {Function} handler Handler function to remove.
+ * @return {boolean} Whether the handler was removed.
  */
-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_
+goog.log.removeHandler = function(logger, handler) {
+  if (goog.log.ENABLED && logger) {
+    return logger.removeHandler(handler);
+  } else {
+    return false;
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Logs a message. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.log.Level} level One of the level identifiers.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ *     message.
  */
-ol.format.GeoJSON.prototype.getExtensions = function() {
-  return ol.format.GeoJSON.EXTENSIONS_;
+goog.log.log = function(logger, level, msg, opt_exception) {
+  if (goog.log.ENABLED && logger) {
+    logger.log(level, msg, opt_exception);
+  }
 };
 
 
 /**
- * Read a feature from a GeoJSON 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 stable
+ * Logs a message at the Level.SEVERE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.format.GeoJSON.prototype.readFeature;
+goog.log.error = function(logger, msg, opt_exception) {
+  if (goog.log.ENABLED && logger) {
+    logger.severe(msg, opt_exception);
+  }
+};
 
 
 /**
- * Read all features from a GeoJSON 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 stable
+ * Logs a message at the Level.WARNING level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.format.GeoJSON.prototype.readFeatures;
+goog.log.warning = function(logger, msg, opt_exception) {
+  if (goog.log.ENABLED && logger) {
+    logger.warning(msg, opt_exception);
+  }
+};
 
 
 /**
- * @inheritDoc
+ * Logs a message at the Level.INFO level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.format.GeoJSON.prototype.readFeatureFromObject = function(
-    object, opt_options) {
-  var geoJSONFeature = /** @type {GeoJSONFeature} */ (object);
-  goog.asserts.assert(geoJSONFeature.type == 'Feature');
-  var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry,
-      opt_options);
-  var feature = new ol.Feature();
-  if (goog.isDef(this.geometryName_)) {
-    feature.setGeometryName(this.geometryName_);
-  }
-  feature.setGeometry(geometry);
-  if (goog.isDef(geoJSONFeature.id)) {
-    feature.setId(geoJSONFeature.id);
-  }
-  if (goog.isDef(geoJSONFeature.properties)) {
-    feature.setProperties(geoJSONFeature.properties);
+goog.log.info = function(logger, msg, opt_exception) {
+  if (goog.log.ENABLED && logger) {
+    logger.info(msg, opt_exception);
   }
-  return feature;
 };
 
 
 /**
- * @inheritDoc
+ * Logs a message at the Level.Fine level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
  */
-ol.format.GeoJSON.prototype.readFeaturesFromObject = function(
-    object, opt_options) {
-  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
-  if (geoJSONObject.type == 'Feature') {
-    return [this.readFeatureFromObject(object, opt_options)];
-  } else if (geoJSONObject.type == 'FeatureCollection') {
-    var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */
-        (object);
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    var geoJSONFeatures = geoJSONFeatureCollection.features;
-    var i, ii;
-    for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) {
-      features.push(this.readFeatureFromObject(geoJSONFeatures[i],
-          opt_options));
-    }
-    return features;
-  } else {
-    goog.asserts.fail();
-    return [];
+goog.log.fine = function(logger, msg, opt_exception) {
+  if (goog.log.ENABLED && logger) {
+    logger.fine(msg, opt_exception);
   }
 };
 
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.PointerEvent');
+
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.object');
 
-/**
- * Read a geometry from a GeoJSON source.
- *
- * @function
- * @param {ArrayBuffer|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
+ * A class for pointer events.
+ *
+ * This class is used as an abstraction for mouse events,
+ * touch events and even native pointer events.
+ *
+ * @constructor
+ * @extends {goog.events.Event}
+ * @param {string} type The type of the event to create.
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of
+ *    initial event properties.
  */
-ol.format.GeoJSON.prototype.readGeometryFromObject = function(
-    object, opt_options) {
-  return ol.format.GeoJSON.readGeometry_(
-      /** @type {GeoJSONGeometry} */ (object), opt_options);
+ol.pointer.PointerEvent = function(type, browserEvent, opt_eventDict) {
+  goog.base(this, type);
+
+  /**
+   * @const
+   * @type {goog.events.BrowserEvent}
+   */
+  this.browserEvent = browserEvent;
+
+  var eventDict = goog.isDef(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 = goog.object.get(eventDict, 'bubbles', false);
+
+  /**
+   * @type {boolean}
+   */
+  this.cancelable = goog.object.get(eventDict, 'cancelable', false);
+
+  /**
+   * @type {Object}
+   */
+  this.view = goog.object.get(eventDict, 'view', null);
+
+  /**
+   * @type {number}
+   */
+  this.detail = goog.object.get(eventDict, 'detail', null);
+
+  /**
+   * @type {number}
+   */
+  this.screenX = goog.object.get(eventDict, 'screenX', 0);
+
+  /**
+   * @type {number}
+   */
+  this.screenY = goog.object.get(eventDict, 'screenY', 0);
+
+  /**
+   * @type {number}
+   */
+  this.clientX = goog.object.get(eventDict, 'clientX', 0);
+
+  /**
+   * @type {number}
+   */
+  this.clientY = goog.object.get(eventDict, 'clientY', 0);
+
+  /**
+   * @type {boolean}
+   */
+  this.ctrlKey = goog.object.get(eventDict, 'ctrlKey', false);
+
+  /**
+   * @type {boolean}
+   */
+  this.altKey = goog.object.get(eventDict, 'altKey', false);
+
+  /**
+   * @type {boolean}
+   */
+  this.shiftKey = goog.object.get(eventDict, 'shiftKey', false);
+
+  /**
+   * @type {boolean}
+   */
+  this.metaKey = goog.object.get(eventDict, 'metaKey', false);
+
+  /**
+   * @type {number}
+   */
+  this.button = goog.object.get(eventDict, 'button', 0);
+
+  /**
+   * @type {Node}
+   */
+  this.relatedTarget = goog.object.get(eventDict, 'relatedTarget', null);
+
+  // PointerEvent related properties
+
+  /**
+   * @const
+   * @type {number}
+   */
+  this.pointerId = goog.object.get(eventDict, 'pointerId', 0);
+
+  /**
+   * @type {number}
+   */
+  this.width = goog.object.get(eventDict, 'width', 0);
+
+  /**
+   * @type {number}
+   */
+  this.height = goog.object.get(eventDict, 'height', 0);
+
+  /**
+   * @type {number}
+   */
+  this.tiltX = goog.object.get(eventDict, 'tiltX', 0);
+
+  /**
+   * @type {number}
+   */
+  this.tiltY = goog.object.get(eventDict, 'tiltY', 0);
+
+  /**
+   * @type {string}
+   */
+  this.pointerType = goog.object.get(eventDict, 'pointerType', '');
+
+  /**
+   * @type {number}
+   */
+  this.hwTimestamp = goog.object.get(eventDict, 'hwTimestamp', 0);
+
+  /**
+   * @type {boolean}
+   */
+  this.isPrimary = goog.object.get(eventDict, 'isPrimary', false);
+
+  // keep the semantics of preventDefault
+  if (browserEvent.preventDefault) {
+    this.preventDefault = function() {
+      browserEvent.preventDefault();
+    };
+  }
 };
+goog.inherits(ol.pointer.PointerEvent, goog.events.Event);
 
 
 /**
- * Read the projection from a GeoJSON source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * @private
+ * @param {Object.<string, ?>} eventDict
+ * @return {number}
  */
-ol.format.GeoJSON.prototype.readProjection;
+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;
+};
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {Object.<string, ?>} eventDict
+ * @param {number} buttons
+ * @return {number}
  */
-ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) {
-  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
-  var crs = geoJSONObject.crs;
-  if (goog.isDefAndNotNull(crs)) {
-    if (crs.type == 'name') {
-      return ol.proj.get(crs.properties.name);
-    } else if (crs.type == 'EPSG') {
-      // 'EPSG' is not part of the GeoJSON specification, but is generated by
-      // GeoServer.
-      // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996
-      // is fixed and widely deployed.
-      return ol.proj.get('EPSG:' + crs.properties.code);
-    } else {
-      goog.asserts.fail();
-      return null;
-    }
+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 {
-    return this.defaultDataProjection;
+    pressure = buttons ? 0.5 : 0;
   }
+  return pressure;
 };
 
 
 /**
- * Encode a feature as a GeoJSON Feature object.
- *
- * @function
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions} options Write options.
- * @return {GeoJSONFeature} GeoJSON.
- * @api stable
+ * Is the `buttons` property supported?
+ * @type {boolean}
  */
-ol.format.GeoJSON.prototype.writeFeature;
+ol.pointer.PointerEvent.HAS_BUTTONS = false;
 
 
 /**
- * @inheritDoc
+ * Checks if the `buttons` property is supported.
  */
-ol.format.GeoJSON.prototype.writeFeatureObject = function(
-    feature, opt_options) {
-  var object = {
-    'type': 'Feature'
-  };
-  var id = feature.getId();
-  if (goog.isDefAndNotNull(id)) {
-    goog.object.set(object, 'id', id);
-  }
-  var geometry = feature.getGeometry();
-  if (goog.isDefAndNotNull(geometry)) {
-    goog.object.set(
-        object, 'geometry',
-        ol.format.GeoJSON.writeGeometry_(geometry, opt_options));
-  }
-  var properties = feature.getProperties();
-  goog.object.remove(properties, 'geometry');
-  if (!goog.object.isEmpty(properties)) {
-    goog.object.set(object, 'properties', properties);
+(function() {
+  try {
+    var ev = new MouseEvent('click', {buttons: 1});
+    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
+  } catch (e) {
   }
-  return object;
-};
+})();
+
+goog.provide('ol.pointer.EventSource');
+
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.object');
+
 
 
 /**
- * Encode an array of features as GeoJSON.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions} options Write options.
- * @return {GeoJSONObject} GeoJSON.
- * @api stable
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @param {Object.<string, function(goog.events.BrowserEvent)>} mapping
+ * @constructor
  */
-ol.format.GeoJSON.prototype.writeFeatures;
+ol.pointer.EventSource = function(dispatcher, mapping) {
+  /**
+   * @type {ol.pointer.PointerEventHandler}
+   */
+  this.dispatcher = dispatcher;
+
+  /**
+   * @private
+   * @const
+   * @type {Object.<string, function(goog.events.BrowserEvent)>}
+   */
+  this.mapping_ = mapping;
+};
 
 
 /**
- * @inheritDoc
+ * List of events supported by this source.
+ * @return {Array.<string>} Event names
  */
-ol.format.GeoJSON.prototype.writeFeaturesObject =
-    function(features, 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
-  });
+ol.pointer.EventSource.prototype.getEvents = function() {
+  return goog.object.getKeys(this.mapping_);
 };
 
 
 /**
- * Encode a geometry as GeoJSON.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions} options Write options.
- * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON.
- * @api stable
+ * Returns a mapping between the supported event types and
+ * the handlers that should handle an event.
+ * @return {Object.<string, function(goog.events.BrowserEvent)>}
+ *         Event/Handler mapping
  */
-ol.format.GeoJSON.prototype.writeGeometry;
+ol.pointer.EventSource.prototype.getMapping = function() {
+  return this.mapping_;
+};
 
 
 /**
- * @inheritDoc
+ * Returns the handler that should handle a given event type.
+ * @param {string} eventType
+ * @return {function(goog.events.BrowserEvent)} Handler
  */
-ol.format.GeoJSON.prototype.writeGeometryObject =
-    ol.format.GeoJSON.writeGeometry_;
+ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
+  return this.mapping_[eventType];
+};
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
 //
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
 //
-//      http://www.apache.org/licenses/LICENSE-2.0
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
 //
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+// 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.
 
-/**
- * @fileoverview
- * XML utilities.
- *
- */
+goog.provide('ol.pointer.MouseSource');
 
-goog.provide('goog.dom.xml');
+goog.require('goog.object');
+goog.require('ol.pointer.EventSource');
 
-goog.require('goog.dom');
-goog.require('goog.dom.NodeType');
 
 
 /**
- * Max XML size for MSXML2.  Used to prevent potential DoS attacks.
- * @type {number}
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @constructor
+ * @extends {ol.pointer.EventSource}
  */
-goog.dom.xml.MAX_XML_SIZE_KB = 2 * 1024;  // In kB
+ol.pointer.MouseSource = function(dispatcher) {
+  var mapping = {
+    'mousedown': this.mousedown,
+    'mousemove': this.mousemove,
+    'mouseup': this.mouseup,
+    'mouseover': this.mouseover,
+    'mouseout': this.mouseout
+  };
+  goog.base(this, dispatcher, mapping);
+
+  /**
+   * @const
+   * @type {Object.<string, goog.events.BrowserEvent|Object>}
+   */
+  this.pointerMap = dispatcher.pointerMap;
+
+  /**
+   * @const
+   * @type {Array.<ol.Pixel>}
+   */
+  this.lastTouches = [];
+};
+goog.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
 
 
 /**
- * Max XML size for MSXML2.  Used to prevent potential DoS attacks.
+ * @const
  * @type {number}
  */
-goog.dom.xml.MAX_ELEMENT_DEPTH = 256; // Same default as MSXML6.
+ol.pointer.MouseSource.POINTER_ID = 1;
 
 
 /**
- * Creates an XML document appropriate for the current JS runtime
- * @param {string=} opt_rootTagName The root tag name.
- * @param {string=} opt_namespaceUri Namespace URI of the document element.
- * @return {Document} The new document.
+ * @const
+ * @type {string}
  */
-goog.dom.xml.createDocument = function(opt_rootTagName, opt_namespaceUri) {
-  if (opt_namespaceUri && !opt_rootTagName) {
-    throw Error("Can't create document with namespace and no root tag");
-  }
-  if (document.implementation && document.implementation.createDocument) {
-    return document.implementation.createDocument(opt_namespaceUri || '',
-                                                  opt_rootTagName || '',
-                                                  null);
-  } else if (typeof ActiveXObject != 'undefined') {
-    var doc = goog.dom.xml.createMsXmlDocument_();
-    if (doc) {
-      if (opt_rootTagName) {
-        doc.appendChild(doc.createNode(goog.dom.NodeType.ELEMENT,
-                                       opt_rootTagName,
-                                       opt_namespaceUri || ''));
-      }
-      return doc;
-    }
-  }
-  throw Error('Your browser does not support creating new documents');
-};
+ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
 
 
 /**
- * Creates an XML document from a string
- * @param {string} xml The text.
- * @return {Document} XML document from the text.
+ * Radius around touchend that swallows mouse events.
+ *
+ * @const
+ * @type {number}
  */
-goog.dom.xml.loadXml = function(xml) {
-  if (typeof DOMParser != 'undefined') {
-    return new DOMParser().parseFromString(xml, 'application/xml');
-  } else if (typeof ActiveXObject != 'undefined') {
-    var doc = goog.dom.xml.createMsXmlDocument_();
-    doc.loadXML(xml);
-    return doc;
-  }
-  throw Error('Your browser does not support loading xml documents');
-};
+ol.pointer.MouseSource.DEDUP_DIST = 25;
 
 
 /**
- * Serializes an XML document or subtree to string.
- * @param {Document|Element} xml The document or the root node of the subtree.
- * @return {string} The serialized XML.
- */
-goog.dom.xml.serialize = function(xml) {
-  // Compatible with Firefox, Opera and WebKit.
-  if (typeof XMLSerializer != 'undefined') {
-    return new XMLSerializer().serializeToString(xml);
-  }
-  // Compatible with Internet Explorer.
-  var text = xml.xml;
-  if (text) {
-    return text;
-  }
-  throw Error('Your browser does not support serializing XML documents');
-};
-
-
-/**
- * Selects a single node using an Xpath expression and a root node
- * @param {Node} node The root node.
- * @param {string} path Xpath selector.
- * @return {Node} The selected node, or null if no matching node.
+ * Detect if a mouse event was simulated from a touch by
+ * checking if previously there was a touch event at the
+ * same position.
+ *
+ * FIXME - Known problem with the native Android browser on
+ * Samsung GT-I9100 (Android 4.1.2):
+ * In case the page is scrolled, this function does not work
+ * correctly when a canvas is used (WebGL or canvas renderer).
+ * Mouse listeners on canvas elements (for this browser), create
+ * two mouse events: One 'good' and one 'bad' one (on other browsers or
+ * when a div is used, there is only one event). For the 'bad' one,
+ * clientX/clientY and also pageX/pageY are wrong when the page
+ * is scrolled. Because of that, this function can not detect if
+ * the events were simulated from a touch event. As result, a
+ * pointer event at a wrong position is dispatched, which confuses
+ * the map interactions.
+ * It is unclear, how one can get the correct position for the event
+ * or detect that the positions are invalid.
+ *
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent
+ * @return {boolean} True, if the event was generated by a touch.
  */
-goog.dom.xml.selectSingleNode = function(node, path) {
-  if (typeof node.selectSingleNode != 'undefined') {
-    var doc = goog.dom.getOwnerDocument(node);
-    if (typeof doc.setProperty != 'undefined') {
-      doc.setProperty('SelectionLanguage', 'XPath');
+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 node.selectSingleNode(path);
-  } else if (document.implementation.hasFeature('XPath', '3.0')) {
-    var doc = goog.dom.getOwnerDocument(node);
-    var resolver = doc.createNSResolver(doc.documentElement);
-    var result = doc.evaluate(path, node, resolver,
-        XPathResult.FIRST_ORDERED_NODE_TYPE, null);
-    return result.singleNodeValue;
   }
-  return null;
+  return false;
 };
 
 
 /**
- * Selects multiple nodes using an Xpath expression and a root node
- * @param {Node} node The root node.
- * @param {string} path Xpath selector.
- * @return {(NodeList,Array.<Node>)} The selected nodes, or empty array if no
- *     matching nodes.
+ * Creates a copy of the original event that will be used
+ * for the fake pointer event.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @return {Object}
  */
-goog.dom.xml.selectNodes = function(node, path) {
-  if (typeof node.selectNodes != 'undefined') {
-    var doc = goog.dom.getOwnerDocument(node);
-    if (typeof doc.setProperty != 'undefined') {
-      doc.setProperty('SelectionLanguage', 'XPath');
-    }
-    return node.selectNodes(path);
-  } else if (document.implementation.hasFeature('XPath', '3.0')) {
-    var doc = goog.dom.getOwnerDocument(node);
-    var resolver = doc.createNSResolver(doc.documentElement);
-    var nodes = doc.evaluate(path, node, resolver,
-        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
-    var results = [];
-    var count = nodes.snapshotLength;
-    for (var i = 0; i < count; i++) {
-      results.push(nodes.snapshotItem(i));
-    }
-    return results;
-  } else {
-    return [];
-  }
+ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
+  var e = dispatcher.cloneEvent(inEvent, inEvent.getBrowserEvent());
+
+  // forward mouse preventDefault
+  var pd = e.preventDefault;
+  e.preventDefault = function() {
+    inEvent.preventDefault();
+    pd();
+  };
+
+  e.pointerId = ol.pointer.MouseSource.POINTER_ID;
+  e.isPrimary = true;
+  e.pointerType = ol.pointer.MouseSource.POINTER_TYPE;
+
+  return e;
 };
 
 
 /**
- * Sets multiple attributes on an element. Differs from goog.dom.setProperties
- * in that it exclusively uses the element's setAttributes method. Use this
- * when you need to ensure that the exact property is available as an attribute
- * and can be read later by the native getAttribute method.
- * @param {!Element} element XML or DOM element to set attributes on.
- * @param {!Object.<string, string>} attributes Map of property:value pairs.
+ * Handler for `mousedown`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-goog.dom.xml.setAttributes = function(element, attributes) {
-  for (var key in attributes) {
-    if (attributes.hasOwnProperty(key)) {
-      element.setAttribute(key, attributes[key]);
+ol.pointer.MouseSource.prototype.mousedown = function(inEvent) {
+  if (!this.isEventSimulatedFromTouch_(inEvent)) {
+    var p = goog.object.containsKey(this.pointerMap,
+        ol.pointer.MouseSource.POINTER_ID.toString());
+    // TODO(dfreedman) workaround for some elements not sending mouseup
+    // http://crbug/149091
+    if (p) {
+      this.cancel(inEvent);
     }
+    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+    goog.object.set(this.pointerMap,
+        ol.pointer.MouseSource.POINTER_ID.toString(), inEvent);
+    this.dispatcher.down(e, inEvent);
   }
 };
 
 
 /**
- * Creates an instance of the MSXML2.DOMDocument.
- * @return {Document} The new document.
- * @private
+ * Handler for `mousemove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-goog.dom.xml.createMsXmlDocument_ = function() {
-  var doc = new ActiveXObject('MSXML2.DOMDocument');
-  if (doc) {
-    // Prevent potential vulnerabilities exposed by MSXML2, see
-    // http://b/1707300 and http://wiki/Main/ISETeamXMLAttacks for details.
-    doc.resolveExternals = false;
-    doc.validateOnParse = false;
-    // Add a try catch block because accessing these properties will throw an
-    // error on unsupported MSXML versions. This affects Windows machines
-    // running IE6 or IE7 that are on XP SP2 or earlier without MSXML updates.
-    // See http://msdn.microsoft.com/en-us/library/ms766391(VS.85).aspx for
-    // specific details on which MSXML versions support these properties.
-    try {
-      doc.setProperty('ProhibitDTD', true);
-      doc.setProperty('MaxXMLSize', goog.dom.xml.MAX_XML_SIZE_KB);
-      doc.setProperty('MaxElementDepth', goog.dom.xml.MAX_ELEMENT_DEPTH);
-    } catch (e) {
-      // No-op.
-    }
+ol.pointer.MouseSource.prototype.mousemove = function(inEvent) {
+  if (!this.isEventSimulatedFromTouch_(inEvent)) {
+    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+    this.dispatcher.move(e, inEvent);
   }
-  return doc;
 };
 
-// FIXME Remove ol.xml.makeParsersNS, and use ol.xml.makeStructureNS instead.
-
-goog.provide('ol.xml');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.dom.xml');
-goog.require('goog.object');
-goog.require('goog.userAgent');
-
 
 /**
- * When using {@link ol.xml.makeChildAppender} or
- * {@link ol.xml.makeSimpleNodeFactory}, the top `objectStack` item needs to
- * have this structure.
- * @typedef {{node:Node}}
+ * Handler for `mouseup`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.NodeStackItem;
-
+ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
+  if (!this.isEventSimulatedFromTouch_(inEvent)) {
+    var p = goog.object.get(this.pointerMap,
+        ol.pointer.MouseSource.POINTER_ID.toString());
 
-/**
- * @typedef {function(Node, Array.<*>)}
- */
-ol.xml.Parser;
+    if (p && p.button === inEvent.button) {
+      var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+      this.dispatcher.up(e, inEvent);
+      this.cleanupMouse();
+    }
+  }
+};
 
 
 /**
- * @typedef {function(Node, *, Array.<*>)}
+ * Handler for `mouseover`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.Serializer;
+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);
+  }
+};
 
 
 /**
- * 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}
+ * Handler for `mouseout`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.DOCUMENT = goog.dom.xml.createDocument();
+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);
+  }
+};
 
 
 /**
- * @param {string} namespaceURI Namespace URI.
- * @param {string} qualifiedName Qualified name.
- * @return {Node} Node.
- * @private
+ * Dispatches a `pointercancel` event.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.createElementNS_ = function(namespaceURI, qualifiedName) {
-  return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName);
+ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
+  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+  this.dispatcher.cancel(e, inEvent);
+  this.cleanupMouse();
 };
 
 
 /**
- * @param {string} namespaceURI Namespace URI.
- * @param {string} qualifiedName Qualified name.
- * @return {Node} Node.
- * @private
+ * Remove the mouse from the list of active pointers.
  */
-ol.xml.createElementNSActiveX_ = function(namespaceURI, qualifiedName) {
-  if (goog.isNull(namespaceURI)) {
-    namespaceURI = '';
-  }
-  return ol.xml.DOCUMENT.createNode(1, qualifiedName, namespaceURI);
+ol.pointer.MouseSource.prototype.cleanupMouse = function() {
+  goog.object.remove(this.pointerMap,
+      ol.pointer.MouseSource.POINTER_ID.toString());
 };
 
+// Based on https://github.com/Polymer/PointerEvents
 
-/**
- * @param {string} namespaceURI Namespace URI.
- * @param {string} qualifiedName Qualified name.
- * @return {Node} Node.
- */
-ol.xml.createElementNS =
-    (document.implementation && document.implementation.createDocument) ?
-        ol.xml.createElementNS_ : ol.xml.createElementNSActiveX_;
+// 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('goog.object');
+goog.require('ol.pointer.EventSource');
 
-/**
- * @param {Node} node Node.
- * @param {boolean} normalizeWhitespace Normalize whitespace.
- * @return {string} All text content.
- */
-ol.xml.getAllTextContent = function(node, normalizeWhitespace) {
-  return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join('');
-};
 
 
 /**
- * @param {Node} node Node.
- * @param {boolean} normalizeWhitespace Normalize whitespace.
- * @param {Array.<String|string>} accumulator Accumulator.
- * @private
- * @return {Array.<String|string>} Accumulator.
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @constructor
+ * @extends {ol.pointer.EventSource}
  */
-ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) {
-  if (node.nodeType == goog.dom.NodeType.CDATA_SECTION ||
-      node.nodeType == goog.dom.NodeType.TEXT) {
-    if (normalizeWhitespace) {
-      // FIXME understand why goog.dom.getTextContent_ uses String here
-      accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
-    } else {
-      accumulator.push(node.nodeValue);
-    }
-  } else {
-    var n;
-    for (n = node.firstChild; !goog.isNull(n); n = n.nextSibling) {
-      ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator);
-    }
-  }
-  return accumulator;
-};
+ol.pointer.MsSource = function(dispatcher) {
+  var mapping = {
+    'MSPointerDown': this.msPointerDown,
+    'MSPointerMove': this.msPointerMove,
+    'MSPointerUp': this.msPointerUp,
+    'MSPointerOut': this.msPointerOut,
+    'MSPointerOver': this.msPointerOver,
+    'MSPointerCancel': this.msPointerCancel,
+    'MSGotPointerCapture': this.msGotPointerCapture,
+    'MSLostPointerCapture': this.msLostPointerCapture
+  };
+  goog.base(this, dispatcher, mapping);
 
+  /**
+   * @const
+   * @type {Object.<string, goog.events.BrowserEvent|Object>}
+   */
+  this.pointerMap = dispatcher.pointerMap;
 
-/**
- * @param {Node} node Node.
- * @private
- * @return {string} Local name.
- */
-ol.xml.getLocalName_ = function(node) {
-  return node.localName;
+  /**
+   * @const
+   * @type {Array.<string>}
+   */
+  this.POINTER_TYPES = [
+    '',
+    'unavailable',
+    'touch',
+    'pen',
+    'mouse'
+  ];
 };
+goog.inherits(ol.pointer.MsSource, ol.pointer.EventSource);
 
 
 /**
- * @param {Node} node Node.
+ * Creates a copy of the original event that will be used
+ * for the fake pointer event.
+ *
  * @private
- * @return {string} Local name.
+ * @param {goog.events.BrowserEvent} inEvent
+ * @return {Object}
  */
-ol.xml.getLocalNameIE_ = function(node) {
-  var localName = node.localName;
-  if (goog.isDef(localName)) {
-    return localName;
+ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) {
+  var e = inEvent;
+  if (goog.isNumber(inEvent.getBrowserEvent().pointerType)) {
+    e = this.dispatcher.cloneEvent(inEvent, inEvent.getBrowserEvent());
+    e.pointerType = this.POINTER_TYPES[inEvent.getBrowserEvent().pointerType];
   }
-  var baseName = node.baseName;
-  goog.asserts.assert(goog.isDefAndNotNull(baseName));
-  return baseName;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {string} Local name.
- */
-ol.xml.getLocalName = goog.userAgent.IE ?
-    ol.xml.getLocalNameIE_ : ol.xml.getLocalName_;
 
-
-/**
- * @param {?} value Value.
- * @private
- * @return {boolean} Is document.
- */
-ol.xml.isDocument_ = function(value) {
-  return value instanceof Document;
+  return e;
 };
 
 
 /**
- * @param {?} value Value.
- * @private
- * @return {boolean} Is document.
+ * Remove this pointer from the list of active pointers.
+ * @param {number} pointerId
  */
-ol.xml.isDocumentIE_ = function(value) {
-  return goog.isObject(value) && value.nodeType == goog.dom.NodeType.DOCUMENT;
+ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
+  goog.object.remove(this.pointerMap, pointerId);
 };
 
 
 /**
- * @param {?} value Value.
- * @return {boolean} Is document.
+ * Handler for `msPointerDown`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.isDocument = goog.userAgent.IE ?
-    ol.xml.isDocumentIE_ : ol.xml.isDocument_;
+ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
+  goog.object.set(this.pointerMap,
+      inEvent.getBrowserEvent().pointerId, inEvent);
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.down(e, inEvent);
+};
 
 
 /**
- * @param {?} value Value.
- * @private
- * @return {boolean} Is node.
+ * Handler for `msPointerMove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.isNode_ = function(value) {
-  return value instanceof Node;
+ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.move(e, inEvent);
 };
 
 
 /**
- * @param {?} value Value.
- * @private
- * @return {boolean} Is node.
+ * Handler for `msPointerUp`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.isNodeIE_ = function(value) {
-  return goog.isObject(value) && goog.isDef(value.nodeType);
+ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.up(e, inEvent);
+  this.cleanup(inEvent.getBrowserEvent().pointerId);
 };
 
 
 /**
- * @param {?} value Value.
- * @return {boolean} Is node.
+ * Handler for `msPointerOut`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.isNode = goog.userAgent.IE ? ol.xml.isNodeIE_ : ol.xml.isNode_;
+ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.leaveOut(e, inEvent);
+};
 
 
 /**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @return {string} Value
- * @private
+ * Handler for `msPointerOver`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.getAttributeNS_ = function(node, namespaceURI, name) {
-  return node.getAttributeNS(namespaceURI, name) || '';
+ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.enterOver(e, inEvent);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @return {string} Value
- * @private
+ * Handler for `msPointerCancel`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.getAttributeNSActiveX_ = function(node, namespaceURI, name) {
-  var attributeValue = '';
-  var attributeNode = ol.xml.getAttributeNodeNS(node, namespaceURI, name);
-  if (goog.isDef(attributeNode)) {
-    attributeValue = attributeNode.nodeValue;
-  }
-  return attributeValue;
+ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.cancel(e, inEvent);
+  this.cleanup(inEvent.getBrowserEvent().pointerId);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @return {string} Value
+ * Handler for `msLostPointerCapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.getAttributeNS =
-    (document.implementation && document.implementation.createDocument) ?
-        ol.xml.getAttributeNS_ : ol.xml.getAttributeNSActiveX_;
+ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
+  var e = this.dispatcher.makeEvent('lostpointercapture',
+      inEvent.getBrowserEvent(), inEvent);
+  this.dispatcher.dispatchEvent(e);
+};
 
 
 /**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @return {?Node} Attribute node or null if none found.
- * @private
+ * Handler for `msGotPointerCapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.getAttributeNodeNS_ = function(node, namespaceURI, name) {
-  return node.getAttributeNodeNS(namespaceURI, name);
+ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
+  var e = this.dispatcher.makeEvent('gotpointercapture',
+      inEvent.getBrowserEvent(), inEvent);
+  this.dispatcher.dispatchEvent(e);
 };
 
+// Based on https://github.com/Polymer/PointerEvents
 
-/**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @return {?Node} Attribute node or null if none found.
- * @private
- */
-ol.xml.getAttributeNodeNSActiveX_ = function(node, namespaceURI, name) {
-  var attributeNode = null;
-  var attributes = node.attributes;
-  var potentialNode, fullName;
-  for (var i = 0, len = attributes.length; i < len; ++i) {
-    potentialNode = attributes[i];
-    if (potentialNode.namespaceURI == namespaceURI) {
-      fullName = (potentialNode.prefix) ?
-          (potentialNode.prefix + ':' + name) : name;
-      if (fullName == potentialNode.nodeName) {
-        attributeNode = potentialNode;
-        break;
-      }
-    }
-  }
-  return attributeNode;
-};
+// 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('goog.object');
+goog.require('ol.pointer.EventSource');
 
-/**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @return {?Node} Attribute node or null if none found.
- */
-ol.xml.getAttributeNodeNS =
-    (document.implementation && document.implementation.createDocument) ?
-        ol.xml.getAttributeNodeNS_ : ol.xml.getAttributeNodeNSActiveX_;
 
 
 /**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @param {string|number} value Value.
- * @private
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @constructor
+ * @extends {ol.pointer.EventSource}
  */
-ol.xml.setAttributeNS_ = function(node, namespaceURI, name, value) {
-  node.setAttributeNS(namespaceURI, name, value);
+ol.pointer.NativeSource = function(dispatcher) {
+  var mapping = {
+    'pointerdown': this.pointerDown,
+    'pointermove': this.pointerMove,
+    'pointerup': this.pointerUp,
+    'pointerout': this.pointerOut,
+    'pointerover': this.pointerOver,
+    'pointercancel': this.pointerCancel,
+    'gotpointercapture': this.gotPointerCapture,
+    'lostpointercapture': this.lostPointerCapture
+  };
+  goog.base(this, dispatcher, mapping);
 };
+goog.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
 
 
 /**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @param {string|number} value Value.
- * @private
+ * Handler for `pointerdown`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.setAttributeNSActiveX_ = function(node, namespaceURI, name, value) {
-  if (!goog.isNull(namespaceURI)) {
-    var attribute = node.ownerDocument.createNode(2, name, namespaceURI);
-    attribute.nodeValue = value;
-    node.setAttributeNode(attribute);
-  } else {
-    node.setAttribute(name, value);
-  }
+ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @param {string|number} value Value.
+ * Handler for `pointermove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.setAttributeNS =
-    (document.implementation && document.implementation.createDocument) ?
-        ol.xml.setAttributeNS_ : ol.xml.setAttributeNSActiveX_;
+ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
+};
 
 
 /**
- * @param {string} xml XML.
- * @return {Document} Document.
+ * Handler for `pointerup`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.load = function(xml) {
-  return new DOMParser().parseFromString(xml, 'application/xml');
+ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)}
- *     valueReader Value reader.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.xml.Parser} Parser.
- * @template T
+ * Handler for `pointerout`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-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 (goog.isDef(value)) {
-          goog.asserts.assert(goog.isArray(value));
-          var array = /** @type {Array.<*>} */
-              (objectStack[objectStack.length - 1]);
-          goog.asserts.assert(goog.isArray(array));
-          goog.array.extend(array, value);
-        }
-      });
+ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.xml.Parser} Parser.
- * @template T
+ * Handler for `pointerover`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-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, node, objectStack);
-        if (goog.isDef(value)) {
-          var array = objectStack[objectStack.length - 1];
-          goog.asserts.assert(goog.isArray(array));
-          array.push(value);
-        }
-      });
+ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.xml.Parser} Parser.
- * @template T
+ * Handler for `pointercancel`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-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, node, objectStack);
-        if (goog.isDef(value)) {
-          objectStack[objectStack.length - 1] = value;
-        }
-      });
+ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {string=} opt_property Property.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.xml.Parser} Parser.
- * @template T
+ * Handler for `lostpointercapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.makeObjectPropertyPusher =
-    function(valueReader, opt_property, opt_this) {
-  goog.asserts.assert(goog.isDef(valueReader));
-  return (
-      /**
-       * @param {Node} node Node.
-       * @param {Array.<*>} objectStack Object stack.
-       */
-      function(node, objectStack) {
-        var value = valueReader.call(opt_this, node, objectStack);
-        if (goog.isDef(value)) {
-          var object = /** @type {Object} */
-              (objectStack[objectStack.length - 1]);
-          var property = goog.isDef(opt_property) ?
-              opt_property : node.localName;
-          goog.asserts.assert(goog.isObject(object));
-          var array = goog.object.setIfUndefined(object, property, []);
-          array.push(value);
-        }
-      });
+ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {string=} opt_property Property.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.xml.Parser} Parser.
- * @template T
+ * Handler for `gotpointercapture`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.xml.makeObjectPropertySetter =
-    function(valueReader, opt_property, opt_this) {
-  goog.asserts.assert(goog.isDef(valueReader));
-  return (
-      /**
-       * @param {Node} node Node.
-       * @param {Array.<*>} objectStack Object stack.
-       */
-      function(node, objectStack) {
-        var value = valueReader.call(opt_this, node, objectStack);
-        if (goog.isDef(value)) {
-          var object = /** @type {Object} */
-              (objectStack[objectStack.length - 1]);
-          var property = goog.isDef(opt_property) ?
-              opt_property : node.localName;
-          goog.asserts.assert(goog.isObject(object));
-          goog.object.set(object, property, value);
-        }
-      });
+ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
+// Based on https://github.com/Polymer/PointerEvents
+
+// Copyright (c) 2013 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+goog.provide('ol.pointer.TouchSource');
+
+goog.require('goog.array');
+goog.require('goog.object');
+goog.require('ol.pointer.EventSource');
+goog.require('ol.pointer.MouseSource');
 
-/**
- * @param {Array.<string>} namespaceURIs Namespace URIs.
- * @param {Object.<string, ol.xml.Parser>} parsers Parsers.
- * @param {Object.<string, Object.<string, ol.xml.Parser>>=} opt_parsersNS
- *     ParsersNS.
- * @return {Object.<string, Object.<string, ol.xml.Parser>>} Parsers NS.
- */
-ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) {
-  return /** @type {Object.<string, Object.<string, ol.xml.Parser>>} */ (
-      ol.xml.makeStructureNS(namespaceURIs, parsers, opt_parsersNS));
-};
 
 
 /**
- * Creates a serializer that appends nodes written by its `nodeWriter` to its
- * designated parent. The parent is the `node` of the
- * {@link ol.xml.NodeStackItem} at the top of the `objectStack`.
- * @param {function(this: T, Node, V, Array.<*>)}
- *     nodeWriter Node writer.
- * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
- * @return {ol.xml.Serializer} Serializer.
- * @template T, V
+ * @constructor
+ * @param {ol.pointer.PointerEventHandler} dispatcher
+ * @param {ol.pointer.MouseSource} mouseSource
+ * @extends {ol.pointer.EventSource}
  */
-ol.xml.makeChildAppender = function(nodeWriter, opt_this) {
-  return function(node, value, objectStack) {
-    nodeWriter.call(opt_this, node, value, objectStack);
-    var parent = objectStack[objectStack.length - 1];
-    goog.asserts.assert(goog.isObject(parent));
-    var parentNode = parent.node;
-    goog.asserts.assert(ol.xml.isNode(parentNode) ||
-        ol.xml.isDocument(parentNode));
-    parentNode.appendChild(node);
+ol.pointer.TouchSource = function(dispatcher, mouseSource) {
+  var mapping = {
+    'touchstart': this.touchstart,
+    'touchmove': this.touchmove,
+    'touchend': this.touchend,
+    'touchcancel': this.touchcancel
   };
+  goog.base(this, dispatcher, mapping);
+
+  /**
+   * @const
+   * @type {Object.<string, goog.events.BrowserEvent|Object>}
+   */
+  this.pointerMap = dispatcher.pointerMap;
+
+  /**
+   * @const
+   * @type {ol.pointer.MouseSource}
+   */
+  this.mouseSource = mouseSource;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.firstTouchId_ = undefined;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.clickCount_ = 0;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.resetId_ = undefined;
 };
+goog.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);
 
 
 /**
- * Creates a serializer that calls the provided `nodeWriter` from
- * {@link ol.xml.serialize}. This can be used by the parent writer to have the
- * 'nodeWriter' called with an array of values when the `nodeWriter` was
- * designed to serialize a single item. An example would be a LineString
- * geometry writer, which could be reused for writing MultiLineString
- * geometries.
- * @param {function(this: T, Node, V, Array.<*>)}
- *     nodeWriter Node writer.
- * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
- * @return {ol.xml.Serializer} Serializer.
- * @template T, V
+ * Mouse event timeout: This should be long enough to
+ * ignore compat mouse events made by touch.
+ * @const
+ * @type {number}
  */
-ol.xml.makeArraySerializer = function(nodeWriter, opt_this) {
-  var serializersNS, nodeFactory;
-  return function(node, value, objectStack) {
-    if (!goog.isDef(serializersNS)) {
-      serializersNS = {};
-      var serializers = {};
-      goog.object.set(serializers, node.localName, nodeWriter);
-      goog.object.set(serializersNS, node.namespaceURI, serializers);
-      nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName);
-    }
-    ol.xml.serialize(serializersNS, nodeFactory, value, objectStack);
-  };
-};
+ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
 
 
 /**
- * Creates 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.
+ * @const
+ * @type {number}
  */
-ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) {
-  var fixedNodeName = opt_nodeName;
-  return (
-      /**
-       * @param {*} value Value.
-       * @param {Array.<*>} objectStack Object stack.
-       * @param {string=} opt_nodeName Node name.
-       * @return {Node} Node.
-       */
-      function(value, objectStack, opt_nodeName) {
-        var context = objectStack[objectStack.length - 1];
-        var node = context.node;
-        goog.asserts.assert(ol.xml.isNode(node) || ol.xml.isDocument(node));
-        var nodeName = fixedNodeName;
-        if (!goog.isDef(nodeName)) {
-          nodeName = opt_nodeName;
-        }
-        var namespaceURI = opt_namespaceURI;
-        if (!goog.isDef(opt_namespaceURI)) {
-          namespaceURI = node.namespaceURI;
-        }
-        goog.asserts.assert(goog.isDef(nodeName));
-        return ol.xml.createElementNS(namespaceURI, nodeName);
-      }
-  );
-};
+ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
 
 
 /**
- * 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)}
+ * @type {string}
  */
-ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory();
+ol.pointer.TouchSource.POINTER_TYPE = 'touch';
 
 
 /**
- * Creates 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
+ * @private
+ * @param {Touch} inTouch
+ * @return {boolean} True, if this is the primary touch.
  */
-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;
+ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
+  return this.firstTouchId_ === inTouch.identifier;
 };
 
 
 /**
- * Creates 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
+ * Set primary touch if there are no pointers, or the only pointer is the mouse.
+ * @param {Touch} inTouch
+ * @private
  */
-ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) {
-  /**
-   * @type {Object.<string, *>}
-   */
-  var structureNS = goog.isDef(opt_structureNS) ? opt_structureNS : {};
-  var i, ii;
-  for (i = 0, ii = namespaceURIs.length; i < ii; ++i) {
-    structureNS[namespaceURIs[i]] = structure;
+ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) {
+  var count = goog.object.getCount(this.pointerMap);
+  if (count === 0 || (count === 1 && goog.object.containsKey(this.pointerMap,
+      ol.pointer.MouseSource.POINTER_ID.toString()))) {
+    this.firstTouchId_ = inTouch.identifier;
+    this.cancelResetClickCount_();
   }
-  return structureNS;
 };
 
 
 /**
- * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS
- *     Parsers by namespace.
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @param {*=} opt_this The object to use as `this`.
+ * @private
+ * @param {Object} inPointer
  */
-ol.xml.parse = function(parsersNS, node, objectStack, opt_this) {
-  var n;
-  for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) {
-    var parsers = parsersNS[n.namespaceURI];
-    if (goog.isDef(parsers)) {
-      var parser = parsers[n.localName];
-      if (goog.isDef(parser)) {
-        parser.call(opt_this, n, objectStack);
-      }
-    }
+ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
+  if (inPointer.isPrimary) {
+    this.firstTouchId_ = undefined;
+    this.resetClickCount_();
   }
 };
 
 
 /**
- * @param {T} object Object.
- * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS
- *     Parsers by namespace.
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @param {*=} opt_this The object to use as `this`.
- * @return {T|undefined} Object.
- * @template T
+ * @private
  */
-ol.xml.pushParseAndPop = function(
-    object, parsersNS, node, objectStack, opt_this) {
-  objectStack.push(object);
-  ol.xml.parse(parsersNS, node, objectStack, opt_this);
-  return objectStack.pop();
+ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
+  this.resetId_ = goog.global.setTimeout(
+      goog.bind(this.resetClickCountHandler_, this),
+      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
 };
 
 
 /**
- * Walks through an array of `values` and calls a serializer for each value.
- * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS
- *     Namespaced serializers.
- * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
- *     Node factory. The `nodeFactory` creates the node whose namespace and name
- *     will be used to choose a node writer from `serializersNS`. This
- *     separation allows us to decide what kind of node to create, depending on
- *     the value we want to serialize. An example for this would be different
- *     geometry writers based on the geometry type.
- * @param {Array.<*>} values Values to serialize. An example would be an array
- *     of {@link ol.Feature} instances.
- * @param {Array.<*>} objectStack Node stack.
- * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
- *     `nodeFactory`. This is used for serializing object literals where the
- *     node name relates to the property key. The array length of `opt_keys` has
- *     to match the length of `values`. For serializing a sequence, `opt_keys`
- *     determines the order of the sequence.
- * @param {T=} opt_this The object to use as `this` for the node factory and
- *     serializers.
- * @template T
+ * @private
  */
-ol.xml.serialize = function(
-    serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
-  var length = (goog.isDef(opt_keys) ? opt_keys : values).length;
-  var value, node;
-  for (var i = 0; i < length; ++i) {
-    value = values[i];
-    if (goog.isDef(value)) {
-      node = nodeFactory.call(opt_this, value, objectStack,
-          goog.isDef(opt_keys) ? opt_keys[i] : undefined);
-      if (goog.isDef(node)) {
-        serializersNS[node.namespaceURI][node.localName]
-            .call(opt_this, node, value, objectStack);
-      }
-    }
-  }
+ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
+  this.clickCount_ = 0;
+  this.resetId_ = undefined;
 };
 
 
 /**
- * @param {O} object Object.
- * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS
- *     Namespaced serializers.
- * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
- *     Node factory. The `nodeFactory` creates the node whose namespace and name
- *     will be used to choose a node writer from `serializersNS`. This
- *     separation allows us to decide what kind of node to create, depending on
- *     the value we want to serialize. An example for this would be different
- *     geometry writers based on the geometry type.
- * @param {Array.<*>} values Values to serialize. An example would be an array
- *     of {@link ol.Feature} instances.
- * @param {Array.<*>} objectStack Node stack.
- * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
- *     `nodeFactory`. This is used for serializing object literals where the
- *     node name relates to the property key. The array length of `opt_keys` has
- *     to match the length of `values`. For serializing a sequence, `opt_keys`
- *     determines the order of the sequence.
- * @param {T=} opt_this The object to use as `this` for the node factory and
- *     serializers.
- * @return {O|undefined} Object.
- * @template O, T
+ * @private
  */
-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();
+ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
+  if (goog.isDef(this.resetId_)) {
+    goog.global.clearTimeout(this.resetId_);
+  }
 };
 
-goog.provide('ol.format.XMLFeature');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
 
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for XML feature formats.
- *
- * @constructor
- * @extends {ol.format.Feature}
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent Browser event
+ * @param {Touch} inTouch Touch event
+ * @return {Object}
  */
-ol.format.XMLFeature = function() {
-  goog.base(this);
-};
-goog.inherits(ol.format.XMLFeature, ol.format.Feature);
+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 neccessary?
+  //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;
 
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.getType = function() {
-  return ol.format.FormatType.XML;
+  return e;
 };
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent Touch event
+ * @param {function(goog.events.BrowserEvent, Object)} inFunction
  */
-ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFeatureFromDocument(
-        /** @type {Document} */ (source), opt_options);
-  } else if (ol.xml.isNode(source)) {
-    return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options);
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readFeatureFromDocument(doc, opt_options);
-  } else {
-    goog.asserts.fail();
-    return null;
+ol.pointer.TouchSource.prototype.processTouches_ =
+    function(inEvent, inFunction) {
+  var touches = Array.prototype.slice.call(
+      inEvent.getBrowserEvent().changedTouches);
+  var count = touches.length;
+  function preventDefault() {
+    inEvent.preventDefault();
   }
-};
-
-
-/**
- * @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;
+  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);
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {ol.Feature} Feature.
- */
-ol.format.XMLFeature.prototype.readFeatureFromNode = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
+ * @private
+ * @param {TouchList} touchList
+ * @param {number} searchId
+ * @return {boolean} True, if the `Touch` with the given id is in the list.
  */
-ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFeaturesFromDocument(
-        /** @type {Document} */ (source), opt_options);
-  } else if (ol.xml.isNode(source)) {
-    return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options);
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readFeaturesFromDocument(doc, opt_options);
-  } else {
-    goog.asserts.fail();
-    return [];
+ol.pointer.TouchSource.prototype.findTouch_ = function(touchList, searchId) {
+  var l = touchList.length;
+  var touch;
+  for (var i = 0; i < l; i++) {
+    touch = touchList[i];
+    if (touch.identifier === searchId) {
+      return true;
+    }
   }
+  return false;
 };
 
 
 /**
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
+ * In some instances, a touchstart can happen without a touchend. This
+ * leaves the pointermap in a broken state.
+ * Therefore, on every touchstart, we remove the touches that did not fire a
+ * touchend event.
+ * To keep state globally consistent, we fire a pointercancel for
+ * this "abandoned" touch
+ *
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.format.XMLFeature.prototype.readFeaturesFromDocument = function(
-    doc, opt_options) {
-  /** @type {Array.<ol.Feature>} */
-  var features = [];
-  var n;
-  for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
-    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
-      goog.array.extend(features, this.readFeaturesFromNode(n, opt_options));
+ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) {
+  var touchList = inEvent.getBrowserEvent().touches;
+  // pointerMap.getCount() should be < touchList.length here,
+  // as the touchstart has not been processed yet.
+  var keys = goog.object.getKeys(this.pointerMap);
+  var count = keys.length;
+  if (count >= touchList.length) {
+    var d = [];
+    var i, key, value;
+    for (i = 0; i < count; ++i) {
+      key = keys[i];
+      value = this.pointerMap[key];
+      // Never remove pointerId == 1, which is mouse.
+      // Touch identifiers are 2 smaller than their pointerId, which is the
+      // index in pointermap.
+      if (key != ol.pointer.MouseSource.POINTER_ID &&
+          !this.findTouch_(touchList, key - 2)) {
+        d.push(value.out);
+      }
+    }
+    for (i = 0; i < d.length; ++i) {
+      this.cancelOut_(inEvent, d[i]);
     }
   }
-  return features;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.XMLFeature.prototype.readFeaturesFromNode = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
+ * Handler for `touchstart`, triggers `pointerover`,
+ * `pointerenter` and `pointerdown` events.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) {
-  if (ol.xml.isDocument(source)) {
-    return this.readGeometryFromDocument(
-        /** @type {Document} */ (source), opt_options);
-  } else if (ol.xml.isNode(source)) {
-    return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options);
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readGeometryFromDocument(doc, opt_options);
-  } else {
-    goog.asserts.fail();
-    return null;
-  }
+ol.pointer.TouchSource.prototype.touchstart = function(inEvent) {
+  this.vacuumTouches_(inEvent);
+  this.setPrimaryTouch_(inEvent.getBrowserEvent().changedTouches[0]);
+  this.dedupSynthMouse_(inEvent);
+  this.clickCount_++;
+  this.processTouches_(inEvent, this.overDown_);
 };
 
 
 /**
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
  */
-ol.format.XMLFeature.prototype.readGeometryFromDocument = goog.abstractMethod;
+ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) {
+  goog.object.set(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);
+};
 
 
 /**
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
+ * Handler for `touchmove`.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.format.XMLFeature.prototype.readGeometryFromNode = goog.abstractMethod;
+ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
+  inEvent.preventDefault();
+  this.processTouches_(inEvent, this.moveOverOut_);
+};
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
  */
-ol.format.XMLFeature.prototype.readProjection = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readProjectionFromDocument(/** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readProjectionFromNode(/** @type {Node} */ (source));
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readProjectionFromDocument(doc);
-  } else {
-    goog.asserts.fail();
-    return null;
+ol.pointer.TouchSource.prototype.moveOverOut_ =
+    function(browserEvent, inPointer) {
+  var event = inPointer;
+  var pointer = goog.object.get(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;
 };
 
 
 /**
- * @param {Document} doc Document.
- * @protected
- * @return {ol.proj.Projection} Projection.
+ * Handler for `touchend`, triggers `pointerup`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.format.XMLFeature.prototype.readProjectionFromDocument = goog.abstractMethod;
+ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
+  this.dedupSynthMouse_(inEvent);
+  this.processTouches_(inEvent, this.upOut_);
+};
 
 
 /**
- * @param {Node} node Node.
- * @protected
- * @return {ol.proj.Projection} Projection.
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
  */
-ol.format.XMLFeature.prototype.readProjectionFromNode = goog.abstractMethod;
+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);
+};
 
 
 /**
- * @inheritDoc
+ * Handler for `touchcancel`, triggers `pointercancel`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) {
-  return this.writeFeatureNode(feature, this.adaptOptions(opt_options));
+ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
+  this.processTouches_(inEvent, this.cancelOut_);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @protected
- * @return {Node} Node.
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @param {Object} inPointer
  */
-ol.format.XMLFeature.prototype.writeFeatureNode = goog.abstractMethod;
+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);
+};
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {Object} inPointer
  */
-ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) {
-  return this.writeFeaturesNode(features, this.adaptOptions(opt_options));
+ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
+  goog.object.remove(this.pointerMap, inPointer.pointerId);
+  this.removePrimaryPointer_(inPointer);
 };
 
 
 /**
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @protected
- * @return {Node} Node.
+ * Prevent synth mouse events from creating pointer events.
+ *
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent
  */
-ol.format.XMLFeature.prototype.writeFeaturesNode = goog.abstractMethod;
-
+ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) {
+  var lts = this.mouseSource.lastTouches;
+  var t = inEvent.getBrowserEvent().changedTouches[0];
+  // only the primary finger will synth mouse events
+  if (this.isPrimaryTouch_(t)) {
+    // remember x/y of last touch
+    var lt = /** @type {ol.Pixel} */ ([t.clientX, t.clientY]);
+    lts.push(lt);
 
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) {
-  return this.writeGeometryNode(geometry, this.adaptOptions(opt_options));
+    goog.global.setTimeout(function() {
+      // remove touch after timeout
+      goog.array.remove(lts, lt);
+    }, ol.pointer.TouchSource.DEDUP_TIMEOUT);
+  }
 };
 
+// Based on https://github.com/Polymer/PointerEvents
 
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @protected
- * @return {Node} Node.
- */
-ol.format.XMLFeature.prototype.writeGeometryNode = goog.abstractMethod;
+// 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.format.XSD');
+goog.provide('ol.pointer.PointerEventHandler');
 
-goog.require('goog.asserts');
-goog.require('goog.string');
-goog.require('ol.xml');
+goog.require('goog.array');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventTarget');
 
+goog.require('ol.has');
+goog.require('ol.pointer.MouseSource');
+goog.require('ol.pointer.MsSource');
+goog.require('ol.pointer.NativeSource');
+goog.require('ol.pointer.PointerEvent');
+goog.require('ol.pointer.TouchSource');
 
-/**
- * @const
- * @type {string}
- */
-ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema';
 
 
 /**
- * @param {Node} node Node.
- * @return {boolean|undefined} Boolean.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @param {Element|HTMLDocument} element Viewport element.
  */
-ol.format.XSD.readBoolean = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readBooleanString(s);
-};
-
+ol.pointer.PointerEventHandler = function(element) {
+  goog.base(this);
 
-/**
- * @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 goog.isDef(m[1]) || false;
-  } else {
-    return undefined;
-  }
+  /**
+   * @const
+   * @private
+   * @type {Element|HTMLDocument}
+   */
+  this.element_ = element;
+
+  /**
+   * @const
+   * @type {Object.<string, goog.events.BrowserEvent|Object>}
+   */
+  this.pointerMap = {};
+
+  /**
+   * @type {Object.<string, function(goog.events.BrowserEvent)>}
+   * @private
+   */
+  this.eventMap_ = {};
+
+  /**
+   * @type {Array.<ol.pointer.EventSource>}
+   * @private
+   */
+  this.eventSourceList_ = [];
+
+  this.registerSources();
 };
+goog.inherits(ol.pointer.PointerEventHandler, goog.events.EventTarget);
 
 
 /**
- * @param {Node} node Node.
- * @return {number|undefined} DateTime in seconds.
+ * Set up the event sources (mouse, touch and native pointers)
+ * that generate pointer events.
  */
-ol.format.XSD.readDateTime = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  var re =
-      /^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/;
-  var m = re.exec(s);
-  if (m) {
-    var year = parseInt(m[1], 10);
-    var month = parseInt(m[2], 10) - 1;
-    var day = parseInt(m[3], 10);
-    var hour = parseInt(m[4], 10);
-    var minute = parseInt(m[5], 10);
-    var second = parseInt(m[6], 10);
-    var dateTime = Date.UTC(year, month, day, hour, minute, second) / 1000;
-    if (m[7] != 'Z') {
-      var sign = m[8] == '-' ? -1 : 1;
-      dateTime += sign * 60 * parseInt(m[9], 10);
-      if (goog.isDef(m[10])) {
-        dateTime += sign * 60 * 60 * parseInt(m[10], 10);
-      }
-    }
-    return dateTime;
+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 {
-    return undefined;
+    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_();
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {number|undefined} Decimal.
+ * Add a new event source that will generate pointer events.
+ *
+ * @param {string} name A name for the event source
+ * @param {ol.pointer.EventSource} source
  */
-ol.format.XSD.readDecimal = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readDecimalString(s);
+ol.pointer.PointerEventHandler.prototype.registerSource =
+    function(name, source) {
+  var s = source;
+  var newEvents = s.getEvents();
+
+  if (newEvents) {
+    goog.array.forEach(newEvents, function(e) {
+      var handler = s.getHandlerForEvent(e);
+
+      if (handler) {
+        this.eventMap_[e] = goog.bind(handler, s);
+      }
+    }, this);
+    this.eventSourceList_.push(s);
+  }
 };
 
 
 /**
- * @param {string} string String.
- * @return {number|undefined} Decimal.
+ * Set up the events for all registered event sources.
+ * @private
  */
-ol.format.XSD.readDecimalString = function(string) {
-  // FIXME check spec
-  var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string);
-  if (m) {
-    return parseFloat(m[1]);
-  } else {
-    return undefined;
+ol.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());
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {number|undefined} Non negative integer.
+ * Remove all registered events.
+ * @private
  */
-ol.format.XSD.readNonNegativeInteger = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readNonNegativeIntegerString(s);
+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());
+  }
 };
 
 
 /**
- * @param {string} string String.
- * @return {number|undefined} Non negative integer.
+ * Calls the right handler for a new event.
+ * @private
+ * @param {goog.events.BrowserEvent} inEvent Browser event.
  */
-ol.format.XSD.readNonNegativeIntegerString = function(string) {
-  var m = /^\s*(\d+)\s*$/.exec(string);
-  if (m) {
-    return parseInt(m[1], 10);
-  } else {
-    return undefined;
+ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
+  var type = inEvent.type;
+  var handler = this.eventMap_[type];
+  if (handler) {
+    handler(inEvent);
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {string|undefined} String.
+ * Setup listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
  */
-ol.format.XSD.readString = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return goog.string.trim(s);
+ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
+  goog.array.forEach(events, function(eventName) {
+    goog.events.listen(this.element_, eventName,
+        this.eventHandler_, false, this);
+  }, this);
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the boolean to.
- * @param {boolean} bool Boolean.
+ * Unregister listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
  */
-ol.format.XSD.writeBooleanTextNode = function(node, bool) {
-  ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0');
+ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
+  goog.array.forEach(events, function(e) {
+    goog.events.unlisten(this.element_, e,
+        this.eventHandler_, false, this);
+  }, this);
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the dateTime to.
- * @param {number} dateTime DateTime in seconds.
+ * Returns a snapshot of inEvent, with writable properties.
+ *
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @param {Event|Touch} inEvent An event that contains
+ *    properties to copy.
+ * @return {Object} An object containing shallow copies of
+ *    `inEvent`'s properties.
  */
-ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) {
-  var date = new Date(dateTime * 1000);
-  var string = date.getUTCFullYear() + '-' +
-      goog.string.padNumber(date.getUTCMonth() + 1, 2) + '-' +
-      goog.string.padNumber(date.getUTCDate(), 2) + 'T' +
-      goog.string.padNumber(date.getUTCHours(), 2) + ':' +
-      goog.string.padNumber(date.getUTCMinutes(), 2) + ':' +
-      goog.string.padNumber(date.getUTCSeconds(), 2) + 'Z';
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+ol.pointer.PointerEventHandler.prototype.cloneEvent =
+    function(browserEvent, inEvent) {
+  var eventCopy = {}, p;
+  for (var i = 0, ii = ol.pointer.CLONE_PROPS.length; i < ii; i++) {
+    p = ol.pointer.CLONE_PROPS[i][0];
+    eventCopy[p] =
+        browserEvent[p] ||
+        inEvent[p] ||
+        ol.pointer.CLONE_PROPS[i][1];
+  }
+
+  return eventCopy;
 };
 
 
+// EVENTS
+
+
 /**
- * @param {Node} node Node to append a TextNode with the decimal to.
- * @param {number} decimal Decimal.
+ * Triggers a 'pointerdown' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.XSD.writeDecimalTextNode = function(node, decimal) {
-  var string = decimal.toPrecision();
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+ol.pointer.PointerEventHandler.prototype.down =
+    function(pointerEventData, browserEvent) {
+  this.fireEvent(ol.pointer.EventType.POINTERDOWN,
+      pointerEventData, browserEvent);
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the decimal to.
- * @param {number} nonNegativeInteger Non negative integer.
+ * Triggers a 'pointermove' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.XSD.writeNonNegativeIntegerTextNode =
-    function(node, nonNegativeInteger) {
-  goog.asserts.assert(nonNegativeInteger >= 0);
-  goog.asserts.assert(nonNegativeInteger == (nonNegativeInteger | 0));
-  var string = nonNegativeInteger.toString();
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+ol.pointer.PointerEventHandler.prototype.move =
+    function(pointerEventData, browserEvent) {
+  this.fireEvent(ol.pointer.EventType.POINTERMOVE,
+      pointerEventData, browserEvent);
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the string to.
- * @param {string} string String.
+ * Triggers a 'pointerup' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.XSD.writeStringTextNode = function(node, string) {
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+ol.pointer.PointerEventHandler.prototype.up =
+    function(pointerEventData, browserEvent) {
+  this.fireEvent(ol.pointer.EventType.POINTERUP,
+      pointerEventData, browserEvent);
 };
 
-// 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.GML');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.NodeType');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('ol.Feature');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.format.XSD');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
 
 /**
- * @classdesc
- * Feature format for reading and writing data in the GML format.
- * Currently only supports GML 3.1.1 Simple Features profile.
- *
- * @constructor
- * @param {olx.format.GMLOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.XMLFeature}
- * @api stable
+ * Triggers a 'pointerenter' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.GML = function(opt_options) {
-  var options = /** @type {olx.format.GMLOptions} */
-      (goog.isDef(opt_options) ? opt_options : {});
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.featureType_ = options.featureType;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.featureNS_ = options.featureNS;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.srsName_ = options.srsName;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.surface_ = goog.isDef(options.surface) ?
-      options.surface : false;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.curve_ = goog.isDef(options.curve) ?
-      options.curve : false;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.multiCurve_ = goog.isDef(options.multiCurve) ?
-      options.multiCurve : true;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.multiSurface_ = goog.isDef(options.multiSurface) ?
-      options.multiSurface : true;
+ol.pointer.PointerEventHandler.prototype.enter =
+    function(pointerEventData, browserEvent) {
+  pointerEventData.bubbles = false;
+  this.fireEvent(ol.pointer.EventType.POINTERENTER,
+      pointerEventData, browserEvent);
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.schemaLocation_ = goog.isDef(options.schemaLocation) ?
-      options.schemaLocation : ol.format.GML.schemaLocation_;
 
-  goog.base(this);
+/**
+ * Triggers a 'pointerleave' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
+ */
+ol.pointer.PointerEventHandler.prototype.leave =
+    function(pointerEventData, browserEvent) {
+  pointerEventData.bubbles = false;
+  this.fireEvent(ol.pointer.EventType.POINTERLEAVE,
+      pointerEventData, browserEvent);
 };
-goog.inherits(ol.format.GML, ol.format.XMLFeature);
 
 
 /**
- * @const
- * @type {string}
- * @private
+ * Triggers a 'pointerover' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.GML.schemaLocation_ = 'http://www.opengis.net/gml ' +
-    'http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
-    '1.0.0/gmlsf.xsd';
+ol.pointer.PointerEventHandler.prototype.over =
+    function(pointerEventData, browserEvent) {
+  pointerEventData.bubbles = true;
+  this.fireEvent(ol.pointer.EventType.POINTEROVER,
+      pointerEventData, browserEvent);
+};
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<ol.Feature>} Features.
- * @private
+ * Triggers a 'pointerout' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.GML.readFeatures_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  var localName = ol.xml.getLocalName(node);
-  var context = objectStack[0];
-  goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var features;
-  if (localName == 'FeatureCollection') {
-    features = ol.xml.pushParseAndPop(null,
-        ol.format.GML.FEATURE_COLLECTION_PARSERS, node, objectStack);
-  } else if (localName == 'featureMembers' || localName == 'featureMember') {
-    var parsers = {};
-    var parsersNS = {};
-    parsers[featureType] = (localName == 'featureMembers') ?
-        ol.xml.makeArrayPusher(ol.format.GML.readFeature_) :
-        ol.xml.makeReplacer(ol.format.GML.readFeature_);
-    parsersNS[goog.object.get(context, 'featureNS')] = parsers;
-    features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack);
-  }
-  if (!goog.isDef(features)) {
-    features = [];
-  }
-  return features;
+ol.pointer.PointerEventHandler.prototype.out =
+    function(pointerEventData, browserEvent) {
+  pointerEventData.bubbles = true;
+  this.fireEvent(ol.pointer.EventType.POINTEROUT,
+      pointerEventData, browserEvent);
 };
 
 
 /**
- * @type {Object.<string, Object.<string, Object>>}
+ * Triggers a 'pointercancel' event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.GML.FEATURE_COLLECTION_PARSERS = {
-  'http://www.opengis.net/gml': {
-    'featureMember': ol.xml.makeArrayPusher(ol.format.GML.readFeatures_),
-    'featureMembers': ol.xml.makeReplacer(ol.format.GML.readFeatures_)
-  }
+ol.pointer.PointerEventHandler.prototype.cancel =
+    function(pointerEventData, browserEvent) {
+  this.fireEvent(ol.pointer.EventType.POINTERCANCEL,
+      pointerEventData, browserEvent);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.Geometry|undefined} Geometry.
+ * Triggers a combination of 'pointerout' and 'pointerleave' events.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.GML.readGeometry = function(node, objectStack) {
-  var context = objectStack[0];
-  goog.asserts.assert(goog.isObject(context));
-  goog.object.set(context, 'srsName',
-      node.firstElementChild.getAttribute('srsName'));
-  var geometry = ol.xml.pushParseAndPop(/** @type {ol.geom.Geometry} */(null),
-      ol.format.GML.GEOMETRY_PARSERS_, node, objectStack);
-  if (goog.isDefAndNotNull(geometry)) {
-    return /** @type {ol.geom.Geometry} */ (
-        ol.format.Feature.transformWithOptions(geometry, false, context));
-  } else {
-    return undefined;
+ol.pointer.PointerEventHandler.prototype.leaveOut =
+    function(pointerEventData, browserEvent) {
+  this.out(pointerEventData, browserEvent);
+  if (!this.contains_(
+      pointerEventData.target,
+      pointerEventData.relatedTarget)) {
+    this.leave(pointerEventData, browserEvent);
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.Feature} Feature.
- * @private
+ * Triggers a combination of 'pointerover' and 'pointerevents' events.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.GML.readFeature_ = function(node, objectStack) {
-  var n;
-  var fid = node.getAttribute('fid') ||
-      ol.xml.getAttributeNS(node, 'http://www.opengis.net/gml', 'id');
-  var values = {}, geometryName;
-  for (n = node.firstElementChild; !goog.isNull(n);
-      n = n.nextElementSibling) {
-    // Assume attribute elements have one child node and that the child
-    // is a text node.  Otherwise assume it is a geometry node.
-    if (n.childNodes.length === 0 ||
-        (n.childNodes.length === 1 &&
-        n.firstChild.nodeType === 3)) {
-      var value = ol.xml.getAllTextContent(n, false);
-      if (goog.string.isEmpty(value)) {
-        value = undefined;
-      }
-      values[ol.xml.getLocalName(n)] = value;
-    } else {
-      geometryName = ol.xml.getLocalName(n);
-      values[geometryName] = ol.format.GML.readGeometry(n, objectStack);
-    }
-  }
-  var feature = new ol.Feature(values);
-  if (goog.isDef(geometryName)) {
-    feature.setGeometryName(geometryName);
-  }
-  if (fid) {
-    feature.setId(fid);
+ol.pointer.PointerEventHandler.prototype.enterOver =
+    function(pointerEventData, browserEvent) {
+  this.over(pointerEventData, browserEvent);
+  if (!this.contains_(
+      pointerEventData.target,
+      pointerEventData.relatedTarget)) {
+    this.enter(pointerEventData, browserEvent);
   }
-  return feature;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
  * @private
- * @return {ol.geom.Point|undefined} Point.
+ * @param {Element} container
+ * @param {Element} contained
+ * @return {boolean} Returns true if the container element
+ *   contains the other element.
  */
-ol.format.GML.readPoint_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Point');
-  var flatCoordinates =
-      ol.format.GML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (goog.isDefAndNotNull(flatCoordinates)) {
-    var point = new ol.geom.Point(null);
-    goog.asserts.assert(flatCoordinates.length == 3);
-    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return point;
-  }
+ol.pointer.PointerEventHandler.prototype.contains_ =
+    function(container, contained) {
+  return container.contains(contained);
 };
 
 
+// EVENT CREATION AND TRACKING
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiPoint|undefined} MultiPoint.
+ * Creates a new Event of type `inType`, based on the information in
+ * `pointerEventData`.
+ *
+ * @param {string} inType A string representing the type of event to create.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
+ * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`.
  */
-ol.format.GML.readMultiPoint_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'MultiPoint');
-  var coordinates = ol.xml.pushParseAndPop(
-      /** @type {Array.<Array.<number>>} */ ([]),
-      ol.format.GML.MULTIPOINT_PARSERS_, node, objectStack);
-  if (goog.isDef(coordinates)) {
-    return new ol.geom.MultiPoint(coordinates);
-  } else {
-    return undefined;
-  }
+ol.pointer.PointerEventHandler.prototype.makeEvent =
+    function(inType, pointerEventData, browserEvent) {
+  return new ol.pointer.PointerEvent(inType, browserEvent, pointerEventData);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ * Make and dispatch an event in one call.
+ * @param {string} inType A string representing the type of event.
+ * @param {Object} pointerEventData
+ * @param {goog.events.BrowserEvent } browserEvent
  */
-ol.format.GML.readMultiLineString_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'MultiLineString');
-  var lineStrings = ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.geom.LineString>} */ ([]),
-      ol.format.GML.MULTILINESTRING_PARSERS_, node, objectStack);
-  if (goog.isDef(lineStrings)) {
-    var multiLineString = new ol.geom.MultiLineString(null);
-    multiLineString.setLineStrings(lineStrings);
-    return multiLineString;
-  } else {
-    return undefined;
-  }
+ol.pointer.PointerEventHandler.prototype.fireEvent =
+    function(inType, pointerEventData, browserEvent) {
+  var e = this.makeEvent(inType, pointerEventData, browserEvent);
+  this.dispatchEvent(e);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ * Creates a pointer event from a native pointer event
+ * and dispatches this event.
+ * @param {goog.events.BrowserEvent} nativeEvent A platform event with a target.
  */
-ol.format.GML.readMultiCurve_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'MultiCurve');
-  var lineStrings = ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.geom.LineString>} */ ([]),
-      ol.format.GML.MULTICURVE_PARSERS_, node, objectStack);
-  if (goog.isDef(lineStrings)) {
-    var multiLineString = new ol.geom.MultiLineString(null);
-    multiLineString.setLineStrings(lineStrings);
-    return multiLineString;
-  } else {
-    return undefined;
-  }
+ol.pointer.PointerEventHandler.prototype.fireNativeEvent =
+    function(nativeEvent) {
+  var e = this.makeEvent(nativeEvent.type, nativeEvent.getBrowserEvent(),
+      nativeEvent);
+  this.dispatchEvent(e);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
+ * Wrap a native mouse event into a pointer event.
+ * This proxy method is required for the legacy IE support.
+ * @param {string} eventType The pointer event type.
+ * @param {goog.events.BrowserEvent} browserEvent
+ * @return {ol.pointer.PointerEvent}
  */
-ol.format.GML.readMultiSurface_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'MultiSurface');
-  var polygons = ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.geom.Polygon>} */ ([]),
-      ol.format.GML.MULTISURFACE_PARSERS_, node, objectStack);
-  if (goog.isDef(polygons)) {
-    var multiPolygon = new ol.geom.MultiPolygon(null);
-    multiPolygon.setPolygons(polygons);
-    return multiPolygon;
-  } else {
-    return undefined;
-  }
+ol.pointer.PointerEventHandler.prototype.wrapMouseEvent =
+    function(eventType, browserEvent) {
+  var pointerEvent = this.makeEvent(
+      eventType,
+      ol.pointer.MouseSource.prepareEvent(browserEvent, this),
+      browserEvent
+      );
+  return pointerEvent;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
+ * @inheritDoc
  */
-ol.format.GML.readMultiPolygon_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'MultiPolygon');
-  var polygons = ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.geom.Polygon>} */ ([]),
-      ol.format.GML.MULTIPOLYGON_PARSERS_, node, objectStack);
-  if (goog.isDef(polygons)) {
-    var multiPolygon = new ol.geom.MultiPolygon(null);
-    multiPolygon.setPolygons(polygons);
-    return multiPolygon;
-  } else {
-    return undefined;
-  }
+ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
+  this.unregister_();
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Constants for event names.
+ * @enum {string}
  */
-ol.format.GML.pointMemberParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'pointMember' ||
-      node.localName == 'pointMembers');
-  ol.xml.parse(ol.format.GML.POINTMEMBER_PARSERS_, node, objectStack);
+ol.pointer.EventType = {
+  POINTERMOVE: 'pointermove',
+  POINTERDOWN: 'pointerdown',
+  POINTERUP: 'pointerup',
+  POINTEROVER: 'pointerover',
+  POINTEROUT: 'pointerout',
+  POINTERENTER: 'pointerenter',
+  POINTERLEAVE: 'pointerleave',
+  POINTERCANCEL: 'pointercancel'
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Properties to copy when cloning an event, with default values.
+ * @type {Array.<Array>}
  */
-ol.format.GML.lineStringMemberParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'lineStringMember' ||
-      node.localName == 'lineStringMembers');
-  ol.xml.parse(ol.format.GML.LINESTRINGMEMBER_PARSERS_, node, objectStack);
-};
+ol.pointer.CLONE_PROPS = [
+  // MouseEvent
+  ['bubbles', false],
+  ['cancelable', false],
+  ['view', null],
+  ['detail', null],
+  ['screenX', 0],
+  ['screenY', 0],
+  ['clientX', 0],
+  ['clientY', 0],
+  ['ctrlKey', false],
+  ['altKey', false],
+  ['shiftKey', false],
+  ['metaKey', false],
+  ['button', 0],
+  ['relatedTarget', null],
+  // DOM Level 3
+  ['buttons', 0],
+  // PointerEvent
+  ['pointerId', 0],
+  ['width', 0],
+  ['height', 0],
+  ['pressure', 0],
+  ['tiltX', 0],
+  ['tiltY', 0],
+  ['pointerType', ''],
+  ['hwTimestamp', 0],
+  ['isPrimary', false],
+  // event instance
+  ['type', ''],
+  ['target', null],
+  ['currentTarget', null],
+  ['which', 0]
+];
+
+goog.provide('ol.MapBrowserEvent');
+goog.provide('ol.MapBrowserEvent.EventType');
+goog.provide('ol.MapBrowserEventHandler');
+goog.provide('ol.MapBrowserPointerEvent');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol');
+goog.require('ol.Coordinate');
+goog.require('ol.MapEvent');
+goog.require('ol.Pixel');
+goog.require('ol.pointer.PointerEvent');
+goog.require('ol.pointer.PointerEventHandler');
+
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @classdesc
+ * Events emitted as map browser events are instances of this type.
+ * See {@link ol.Map} for which events trigger a map browser event.
+ *
+ * @constructor
+ * @extends {ol.MapEvent}
+ * @implements {oli.MapBrowserEvent}
+ * @param {string} type Event type.
+ * @param {ol.Map} map Map.
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-ol.format.GML.curveMemberParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'curveMember' ||
-      node.localName == 'curveMembers');
-  ol.xml.parse(ol.format.GML.CURVEMEMBER_PARSERS_, node, objectStack);
+ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) {
+
+  goog.base(this, type, map, opt_frameState);
+
+  /**
+   * @const
+   * @type {goog.events.BrowserEvent}
+   */
+  this.browserEvent = browserEvent;
+
+  /**
+   * @const
+   * @type {Event}
+   * @api stable
+   */
+  this.originalEvent = browserEvent.getBrowserEvent();
+
+  /**
+   * @type {ol.Pixel}
+   * @api stable
+   */
+  this.pixel = map.getEventPixel(this.originalEvent);
+
+  /**
+   * @type {ol.Coordinate}
+   * @api stable
+   */
+  this.coordinate = map.getCoordinateFromPixel(this.pixel);
+
 };
+goog.inherits(ol.MapBrowserEvent, ol.MapEvent);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Prevents the default browser action.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
+ * @override
+ * @api stable
  */
-ol.format.GML.surfaceMemberParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'surfaceMember' ||
-      node.localName == 'surfaceMembers');
-  ol.xml.parse(ol.format.GML.SURFACEMEMBER_PARSERS_, node, objectStack);
+ol.MapBrowserEvent.prototype.preventDefault = function() {
+  goog.base(this, 'preventDefault');
+  this.browserEvent.preventDefault();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Prevents further propagation of the current event.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
+ * @override
+ * @api stable
  */
-ol.format.GML.polygonMemberParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'polygonMember' ||
-      node.localName == 'polygonMembers');
-  ol.xml.parse(ol.format.GML.POLYGONMEMBER_PARSERS_, node, objectStack);
+ol.MapBrowserEvent.prototype.stopPropagation = function() {
+  goog.base(this, 'stopPropagation');
+  this.browserEvent.stopPropagation();
 };
 
 
+
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.LineString|undefined} LineString.
+ * @constructor
+ * @extends {ol.MapBrowserEvent}
+ * @param {string} type Event type.
+ * @param {ol.Map} map Map.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-ol.format.GML.readLineString_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'LineString');
-  var flatCoordinates =
-      ol.format.GML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (goog.isDefAndNotNull(flatCoordinates)) {
-    var lineString = new ol.geom.LineString(null);
-    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return lineString;
-  } else {
-    return undefined;
-  }
+ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_frameState) {
+
+  goog.base(this, type, map, pointerEvent.browserEvent, opt_frameState);
+
+  /**
+   * @const
+   * @type {ol.pointer.PointerEvent}
+   */
+  this.pointerEvent = pointerEvent;
+
 };
+goog.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);
+
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
+ * @param {ol.Map} map The map with the viewport to listen to events on.
+ * @constructor
+ * @extends {goog.events.EventTarget}
  */
-ol.format.GML.readPatch_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'patches');
-  return ol.xml.pushParseAndPop(
-      /** @type {Array.<Array.<number>>} */ ([null]),
-      ol.format.GML.PATCHES_PARSERS_, node, objectStack);
+ol.MapBrowserEventHandler = function(map) {
+
+  goog.base(this);
+
+  /**
+   * This is the element that we will listen to the real events on.
+   * @type {ol.Map}
+   * @private
+   */
+  this.map_ = map;
+
+  /**
+   * @type {number}
+   * @private
+   */
+  this.clickTimeoutId_ = 0;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.dragged_ = false;
+
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.dragListenerKeys_ = null;
+
+  /**
+   * @type {goog.events.Key}
+   * @private
+   */
+  this.pointerdownListenerKey_ = null;
+
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+    /**
+     * @type {goog.events.Key}
+     * @private
+     */
+    this.ieDblclickListenerKey_ = null;
+  }
+
+  /**
+   * @type {ol.pointer.PointerEvent}
+   * @private
+   */
+  this.down_ = null;
+
+  var element = this.map_.getViewport();
+
+  /**
+   * @type {number}
+   * @private
+   */
+  this.activePointers_ = 0;
+
+  /**
+   * @type {Object.<number, boolean>}
+   * @private
+   */
+  this.trackedTouches_ = {};
+
+  /**
+   * Event handler which generates pointer events for
+   * the viewport element.
+   *
+   * @type {ol.pointer.PointerEventHandler}
+   * @private
+   */
+  this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element);
+
+  /**
+   * Event handler which generates pointer events for
+   * the document (used when dragging).
+   *
+   * @type {ol.pointer.PointerEventHandler}
+   * @private
+   */
+  this.documentPointerEventHandler_ = null;
+
+  this.pointerdownListenerKey_ = goog.events.listen(this.pointerEventHandler_,
+      ol.pointer.EventType.POINTERDOWN,
+      this.handlePointerDown_, false, this);
+
+  this.relayedListenerKey_ = goog.events.listen(this.pointerEventHandler_,
+      ol.pointer.EventType.POINTERMOVE,
+      this.relayEvent_, false, this);
+
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+    /*
+     * On legacy IE, double clicks do not produce two mousedown and
+     * mouseup events. That is why a separate DBLCLICK event listener
+     * is used.
+     */
+    this.ieDblclickListenerKey_ = goog.events.listen(element,
+        goog.events.EventType.DBLCLICK,
+        this.emulateClickLegacyIE_, false, this);
+  }
+
 };
+goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} flat coordinates.
+ * Get the last "down" type event. This will be set on pointerdown.
+ * @return {ol.pointer.PointerEvent} The most recent "down" type event (or null
+ * if none have occurred).
  */
-ol.format.GML.readSegment_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'segments');
-  return ol.xml.pushParseAndPop(
-      /** @type {Array.<number>} */ ([null]),
-      ol.format.GML.SEGMENTS_PARSERS_, node, objectStack);
+ol.MapBrowserEventHandler.prototype.getDown = function() {
+  return this.down_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {goog.events.BrowserEvent} browserEvent Pointer event.
  * @private
- * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
  */
-ol.format.GML.readPolygonPatch_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'PolygonPatch');
-  return ol.xml.pushParseAndPop(
-      /** @type {Array.<Array.<number>>} */ ([null]),
-      ol.format.GML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack);
+ol.MapBrowserEventHandler.prototype.emulateClickLegacyIE_ =
+    function(browserEvent) {
+  var pointerEvent = this.pointerEventHandler_.wrapMouseEvent(
+      ol.MapBrowserEvent.EventType.POINTERUP,
+      browserEvent
+      );
+  this.emulateClick_(pointerEvent);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
  * @private
- * @return {Array.<number>|undefined} flat coordinates.
  */
-ol.format.GML.readLineStringSegment_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'LineStringSegment');
-  return ol.xml.pushParseAndPop(
-      /** @type {Array.<number>} */ ([null]),
-      ol.format.GML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack);
+ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) {
+  var newEvent;
+  newEvent = new ol.MapBrowserPointerEvent(
+      ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent);
+  this.dispatchEvent(newEvent);
+  if (this.clickTimeoutId_ !== 0) {
+    // double-click
+    goog.global.clearTimeout(this.clickTimeoutId_);
+    this.clickTimeoutId_ = 0;
+    newEvent = new ol.MapBrowserPointerEvent(
+        ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent);
+    this.dispatchEvent(newEvent);
+  } else {
+    // click
+    this.clickTimeoutId_ = goog.global.setTimeout(goog.bind(function() {
+      this.clickTimeoutId_ = 0;
+      var newEvent = new ol.MapBrowserPointerEvent(
+          ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent);
+      this.dispatchEvent(newEvent);
+    }, this), 250);
+  }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * Keeps track on how many pointers are currently active.
+ *
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
  * @private
  */
-ol.format.GML.interiorParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'interior');
-  var flatLinearRing = ol.xml.pushParseAndPop(
-      /** @type {Array.<number>|undefined} */ (undefined),
-      ol.format.GML.RING_PARSERS_, node, objectStack);
-  if (goog.isDef(flatLinearRing)) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    goog.asserts.assert(goog.isArray(flatLinearRings));
-    goog.asserts.assert(flatLinearRings.length > 0);
-    flatLinearRings.push(flatLinearRing);
+ol.MapBrowserEventHandler.prototype.updateActivePointers_ =
+    function(pointerEvent) {
+  var event = pointerEvent;
+
+  if (event.type == ol.MapBrowserEvent.EventType.POINTERUP ||
+      event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) {
+    delete this.trackedTouches_[event.pointerId];
+  } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
+    this.trackedTouches_[event.pointerId] = true;
   }
+  this.activePointers_ = goog.object.getCount(this.trackedTouches_);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
  * @private
  */
-ol.format.GML.exteriorParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'exterior');
-  var flatLinearRing = ol.xml.pushParseAndPop(
-      /** @type {Array.<number>|undefined} */ (undefined),
-      ol.format.GML.RING_PARSERS_, node, objectStack);
-  if (goog.isDef(flatLinearRing)) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    goog.asserts.assert(goog.isArray(flatLinearRings));
-    goog.asserts.assert(flatLinearRings.length > 0);
-    flatLinearRings[0] = flatLinearRing;
+ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
+  this.updateActivePointers_(pointerEvent);
+  var newEvent = new ol.MapBrowserPointerEvent(
+      ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent);
+  this.dispatchEvent(newEvent);
+
+  goog.asserts.assert(this.activePointers_ >= 0);
+  if (this.activePointers_ === 0) {
+    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
+    this.dragListenerKeys_ = null;
+    goog.dispose(this.documentPointerEventHandler_);
+    this.documentPointerEventHandler_ = null;
+  }
+
+  // We emulate click event on left mouse button click, touch contact, and pen
+  // contact. isMouseActionButton returns true in these cases (evt.button is set
+  // to 0).
+  // See http://www.w3.org/TR/pointerevents/#button-states
+  if (!this.dragged_ && this.isMouseActionButton_(pointerEvent)) {
+    goog.asserts.assert(!goog.isNull(this.down_));
+    this.emulateClick_(this.down_);
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @return {boolean} If the left mouse button was pressed.
  * @private
- * @return {Array.<number>|undefined} LinearRing flat coordinates.
  */
-ol.format.GML.readFlatLinearRing_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'LinearRing');
-  var ring = ol.xml.pushParseAndPop(/** @type {Array.<number>} */(null),
-      ol.format.GML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack);
-  if (goog.isDefAndNotNull(ring)) {
-    return ring;
+ol.MapBrowserEventHandler.prototype.isMouseActionButton_ =
+    function(pointerEvent) {
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+    return pointerEvent.button == 1;
   } else {
-    return undefined;
+    return pointerEvent.button === 0;
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
  * @private
- * @return {ol.geom.LinearRing|undefined} LinearRing.
  */
-ol.format.GML.readLinearRing_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'LinearRing');
-  var flatCoordinates =
-      ol.format.GML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (goog.isDef(flatCoordinates)) {
-    var ring = new ol.geom.LinearRing(null);
-    ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return ring;
-  } else {
-    return undefined;
-  }
-};
-
+ol.MapBrowserEventHandler.prototype.handlePointerDown_ =
+    function(pointerEvent) {
+  this.updateActivePointers_(pointerEvent);
+  var newEvent = new ol.MapBrowserPointerEvent(
+      ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent);
+  this.dispatchEvent(newEvent);
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Polygon|undefined} Polygon.
- */
-ol.format.GML.readPolygon_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Polygon');
-  var flatLinearRings = ol.xml.pushParseAndPop(
-      /** @type {Array.<Array.<number>>} */ ([null]),
-      ol.format.GML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack);
-  if (goog.isDef(flatLinearRings) &&
-      !goog.isNull(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.safeExtend(flatCoordinates, flatLinearRings[i]);
-      ends.push(flatCoordinates.length);
-    }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    return polygon;
-  } else {
-    return undefined;
-  }
-};
+  this.down_ = pointerEvent;
+  this.dragged_ = false;
 
+  if (goog.isNull(this.dragListenerKeys_)) {
+    /* Set up a pointer event handler on the `document`,
+     * which is required when the pointer is moved outside
+     * the viewport when dragging.
+     */
+    this.documentPointerEventHandler_ =
+        new ol.pointer.PointerEventHandler(document);
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Polygon|undefined} Polygon.
- */
-ol.format.GML.readSurface_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Surface');
-  var flatLinearRings = ol.xml.pushParseAndPop(
-      /** @type {Array.<Array.<number>>} */ ([null]),
-      ol.format.GML.SURFACE_PARSERS_, node, objectStack);
-  if (goog.isDef(flatLinearRings) &&
-      !goog.isNull(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.safeExtend(flatCoordinates, flatLinearRings[i]);
-      ends.push(flatCoordinates.length);
-    }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    return polygon;
-  } else {
-    return undefined;
+    this.dragListenerKeys_ = [
+      goog.events.listen(this.documentPointerEventHandler_,
+          ol.MapBrowserEvent.EventType.POINTERMOVE,
+          this.handlePointerMove_, false, this),
+      goog.events.listen(this.documentPointerEventHandler_,
+          ol.MapBrowserEvent.EventType.POINTERUP,
+          this.handlePointerUp_, false, this),
+      /* Note that the listener for `pointercancel is set up on
+       * `pointerEventHandler_` and not `documentPointerEventHandler_` like
+       * the `pointerup` and `pointermove` listeners.
+       *
+       * The reason for this is the following: `TouchSource.vacuumTouches_()`
+       * issues `pointercancel` events, when there was no `touchend` for a
+       * `touchstart`. Now, let's say a first `touchstart` is registered on
+       * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up.
+       * But `documentPointerEventHandler_` doesn't know about the first
+       * `touchstart`. If there is no `touchend` for the `touchstart`, we can
+       * only receive a `touchcancel` from `pointerEventHandler_`, because it is
+       * only registered there.
+       */
+      goog.events.listen(this.pointerEventHandler_,
+          ol.MapBrowserEvent.EventType.POINTERCANCEL,
+          this.handlePointerUp_, false, this)
+    ];
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
  * @private
- * @return {ol.geom.LineString|undefined} LineString.
  */
-ol.format.GML.readCurve_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Curve');
-  var flatCoordinates = ol.xml.pushParseAndPop(
-      /** @type {Array.<number>} */ ([null]),
-      ol.format.GML.CURVE_PARSERS_, node, objectStack);
-  if (goog.isDef(flatCoordinates)) {
-    var lineString = new ol.geom.LineString(null);
-    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return lineString;
-  } else {
-    return undefined;
+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 (pointerEvent.clientX != this.down_.clientX ||
+      pointerEvent.clientY != this.down_.clientY) {
+    this.dragged_ = true;
+    var newEvent = new ol.MapBrowserPointerEvent(
+        ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent);
+    this.dispatchEvent(newEvent);
   }
-};
 
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Extent|undefined} Envelope.
- */
-ol.format.GML.readEnvelope_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Envelope');
-  var flatCoordinates = ol.xml.pushParseAndPop(
-      /** @type {Array.<number>} */ ([null]),
-      ol.format.GML.ENVELOPE_PARSERS_, node, objectStack);
-  return ol.extent.createOrUpdate(flatCoordinates[1][0],
-      flatCoordinates[1][1], flatCoordinates[2][0],
-      flatCoordinates[2][1]);
+  // 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();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * 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
- * @return {Array.<number>} Flat coordinates.
  */
-ol.format.GML.readFlatCoordinatesFromNode_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(
-      null,
-      ol.format.GML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack));
+ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
+  this.dispatchEvent(new ol.MapBrowserPointerEvent(
+      pointerEvent.type, this.map_, pointerEvent));
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
+ * @inheritDoc
  */
-ol.format.GML.readFlatPos_ = function(node, objectStack) {
-  var s = ol.xml.getAllTextContent(node, false);
-  var re = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\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);
+ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
+  if (!goog.isNull(this.relayedListenerKey_)) {
+    goog.events.unlistenByKey(this.relayedListenerKey_);
+    this.relayedListenerKey_ = null;
   }
-  if (s !== '') {
-    return undefined;
+  if (!goog.isNull(this.pointerdownListenerKey_)) {
+    goog.events.unlistenByKey(this.pointerdownListenerKey_);
+    this.pointerdownListenerKey_ = null;
   }
-  var context = objectStack[0];
-  goog.asserts.assert(goog.isObject(context));
-  var containerSrs = goog.object.get(context, 'srsName');
-  var axisOrientation = 'enu';
-  if (!goog.isNull(containerSrs)) {
-    var proj = ol.proj.get(containerSrs);
-    axisOrientation = proj.getAxisOrientation();
+  if (!goog.isNull(this.dragListenerKeys_)) {
+    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
+    this.dragListenerKeys_ = null;
   }
-  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;
-    }
+  if (!goog.isNull(this.documentPointerEventHandler_)) {
+    goog.dispose(this.documentPointerEventHandler_);
+    this.documentPointerEventHandler_ = null;
   }
-  var len = flatCoordinates.length;
-  if (len == 2) {
-    flatCoordinates.push(0);
+  if (!goog.isNull(this.pointerEventHandler_)) {
+    goog.dispose(this.pointerEventHandler_);
+    this.pointerEventHandler_ = null;
   }
-  if (len === 0) {
-    return undefined;
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE &&
+      !goog.isNull(this.ieDblclickListenerKey_)) {
+    goog.events.unlistenByKey(this.ieDblclickListenerKey_);
+    this.ieDblclickListenerKey_ = null;
   }
-  return flatCoordinates;
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
+ * Constants for event names.
+ * @enum {string}
  */
-ol.format.GML.readFlatPosList_ = function(node, objectStack) {
-  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
-  var context = objectStack[0];
-  goog.asserts.assert(goog.isObject(context));
-  var containerSrs = goog.object.get(context, 'srsName');
-  var containerDimension = node.parentNode.getAttribute('srsDimension');
-  var axisOrientation = 'enu';
-  if (!goog.isNull(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 (!goog.isNull(node.getAttribute('srsDimension'))) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(
-        node.getAttribute('srsDimension'));
-  } else if (!goog.isNull(node.getAttribute('dimension'))) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(
-        node.getAttribute('dimension'));
-  } else if (!goog.isNull(containerDimension)) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
-  }
-  var x, y, z;
-  var flatCoordinates = [];
-  for (var i = 0, ii = coords.length; i < ii; i += dim) {
-    x = parseFloat(coords[i]);
-    y = parseFloat(coords[i + 1]);
-    z = (dim === 3) ? parseFloat(coords[i + 2]) : 0;
-    if (axisOrientation.substr(0, 2) === 'en') {
-      flatCoordinates.push(x, y, z);
-    } else {
-      flatCoordinates.push(y, x, z);
-    }
-  }
-  return flatCoordinates;
+ol.MapBrowserEvent.EventType = {
+  // derived event types
+  /**
+   * A true single click with no dragging and no double click. Note that this
+   * event is delayed by 250 ms to ensure that it is not a double click.
+   * @event ol.MapBrowserEvent#singleclick
+   * @api
+   */
+  SINGLECLICK: 'singleclick',
+  /**
+   * A click with no dragging. A double click will fire two of this.
+   * @event ol.MapBrowserEvent#click
+   * @api
+   */
+  CLICK: goog.events.EventType.CLICK,
+  /**
+   * A true double click, with no dragging.
+   * @event ol.MapBrowserEvent#dblclick
+   * @api
+   */
+  DBLCLICK: goog.events.EventType.DBLCLICK,
+  /**
+   * Triggered when a pointer is dragged.
+   * @event ol.MapBrowserEvent#pointerdrag
+   * @api
+   */
+  POINTERDRAG: 'pointerdrag',
+
+  // original pointer event types
+  /**
+   * Triggered when a pointer is moved. Note that on touch devices this is
+   * triggered when the map is panned, so is not the same as mousemove.
+   * @event ol.MapBrowserEvent#pointermove
+   * @api
+   */
+  POINTERMOVE: 'pointermove',
+  POINTERDOWN: 'pointerdown',
+  POINTERUP: 'pointerup',
+  POINTEROVER: 'pointerover',
+  POINTEROUT: 'pointerout',
+  POINTERENTER: 'pointerenter',
+  POINTERLEAVE: 'pointerleave',
+  POINTERCANCEL: 'pointercancel'
 };
 
+goog.provide('ol.source.Source');
+goog.provide('ol.source.State');
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.GML.GEOMETRY_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeReplacer(ol.format.GML.readPoint_),
-    'MultiPoint': ol.xml.makeReplacer(ol.format.GML.readMultiPoint_),
-    'LineString': ol.xml.makeReplacer(ol.format.GML.readLineString_),
-    'MultiLineString': ol.xml.makeReplacer(
-        ol.format.GML.readMultiLineString_),
-    'LinearRing' : ol.xml.makeReplacer(ol.format.GML.readLinearRing_),
-    'Polygon': ol.xml.makeReplacer(ol.format.GML.readPolygon_),
-    'MultiPolygon': ol.xml.makeReplacer(ol.format.GML.readMultiPolygon_),
-    'Surface': ol.xml.makeReplacer(ol.format.GML.readSurface_),
-    'MultiSurface': ol.xml.makeReplacer(ol.format.GML.readMultiSurface_),
-    'Curve': ol.xml.makeReplacer(ol.format.GML.readCurve_),
-    'MultiCurve': ol.xml.makeReplacer(ol.format.GML.readMultiCurve_),
-    'Envelope': ol.xml.makeReplacer(ol.format.GML.readEnvelope_)
-  }
-};
+goog.require('goog.events.EventType');
+goog.require('ol.Attribution');
+goog.require('ol.Observable');
+goog.require('ol.proj');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * State of the source, one of 'undefined', 'loading', 'ready' or 'error'.
+ * @enum {string}
+ * @api
  */
-ol.format.GML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'pos': ol.xml.makeReplacer(ol.format.GML.readFlatPos_),
-    'posList': ol.xml.makeReplacer(ol.format.GML.readFlatPosList_)
-  }
+ol.source.State = {
+  UNDEFINED: 'undefined',
+  LOADING: 'loading',
+  READY: 'ready',
+  ERROR: 'error'
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
+ *            logo: (string|olx.LogoOptions|undefined),
+ *            projection: ol.proj.ProjectionLike,
+ *            state: (ol.source.State|undefined)}}
  */
-ol.format.GML.FLAT_LINEAR_RINGS_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'interior': ol.format.GML.interiorParser_,
-    'exterior': ol.format.GML.exteriorParser_
-  }
-};
+ol.source.SourceOptions;
+
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for {@link ol.layer.Layer} sources.
+ *
+ * @constructor
+ * @extends {ol.Observable}
+ * @fires change Triggered when the state of the source changes.
+ * @param {ol.source.SourceOptions} options Source options.
+ * @api stable
  */
-ol.format.GML.MULTIPOINT_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'pointMember': ol.xml.makeArrayPusher(ol.format.GML.pointMemberParser_),
-    'pointMembers': ol.xml.makeArrayPusher(ol.format.GML.pointMemberParser_)
-  }
-};
+ol.source.Source = function(options) {
 
+  goog.base(this);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.GML.MULTILINESTRING_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'lineStringMember': ol.xml.makeArrayPusher(
-        ol.format.GML.lineStringMemberParser_),
-    'lineStringMembers': ol.xml.makeArrayPusher(
-        ol.format.GML.lineStringMemberParser_)
-  }
-};
+  /**
+   * @private
+   * @type {ol.proj.Projection}
+   */
+  this.projection_ = ol.proj.get(options.projection);
 
+  /**
+   * @private
+   * @type {Array.<ol.Attribution>}
+   */
+  this.attributions_ = goog.isDef(options.attributions) ?
+      options.attributions : null;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.GML.MULTICURVE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'curveMember': ol.xml.makeArrayPusher(
-        ol.format.GML.curveMemberParser_),
-    'curveMembers': ol.xml.makeArrayPusher(
-        ol.format.GML.curveMemberParser_)
-  }
-};
+  /**
+   * @private
+   * @type {string|olx.LogoOptions|undefined}
+   */
+  this.logo_ = options.logo;
 
+  /**
+   * @private
+   * @type {ol.source.State}
+   */
+  this.state_ = goog.isDef(options.state) ?
+      options.state : ol.source.State.READY;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.GML.MULTISURFACE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'surfaceMember': ol.xml.makeArrayPusher(
-        ol.format.GML.surfaceMemberParser_),
-    'surfaceMembers': ol.xml.makeArrayPusher(
-        ol.format.GML.surfaceMemberParser_)
-  }
 };
+goog.inherits(ol.source.Source, ol.Observable);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
+ * @param {function(ol.Feature): T} callback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
  */
-ol.format.GML.MULTIPOLYGON_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'polygonMember': ol.xml.makeArrayPusher(
-        ol.format.GML.polygonMemberParser_),
-    'polygonMembers': ol.xml.makeArrayPusher(
-        ol.format.GML.polygonMemberParser_)
-  }
-};
+ol.source.Source.prototype.forEachFeatureAtPixel =
+    goog.nullFunction;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {Array.<ol.Attribution>} Attributions.
+ * @api stable
  */
-ol.format.GML.POINTMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeArrayPusher(
-        ol.format.GML.readFlatCoordinatesFromNode_)
-  }
+ol.source.Source.prototype.getAttributions = function() {
+  return this.attributions_;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {string|olx.LogoOptions|undefined} Logo.
+ * @api stable
  */
-ol.format.GML.LINESTRINGMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'LineString': ol.xml.makeArrayPusher(
-        ol.format.GML.readLineString_)
-  }
+ol.source.Source.prototype.getLogo = function() {
+  return this.logo_;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-ol.format.GML.CURVEMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'LineString': ol.xml.makeArrayPusher(ol.format.GML.readLineString_),
-    'Curve': ol.xml.makeArrayPusher(ol.format.GML.readCurve_)
-  }
+ol.source.Source.prototype.getProjection = function() {
+  return this.projection_;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {Array.<number>|undefined} Resolutions.
  */
-ol.format.GML.SURFACEMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Polygon': ol.xml.makeArrayPusher(ol.format.GML.readPolygon_),
-    'Surface': ol.xml.makeArrayPusher(ol.format.GML.readSurface_)
-  }
-};
+ol.source.Source.prototype.getResolutions = goog.abstractMethod;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {ol.source.State} State.
+ * @api
  */
-ol.format.GML.POLYGONMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Polygon': ol.xml.makeArrayPusher(
-        ol.format.GML.readPolygon_)
-  }
+ol.source.Source.prototype.getState = function() {
+  return this.state_;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @param {Array.<ol.Attribution>} attributions Attributions.
  */
-ol.format.GML.SURFACE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'patches': ol.xml.makeReplacer(ol.format.GML.readPatch_)
-  }
+ol.source.Source.prototype.setAttributions = function(attributions) {
+  this.attributions_ = attributions;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @param {string|olx.LogoOptions|undefined} logo Logo.
  */
-ol.format.GML.CURVE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'segments': ol.xml.makeReplacer(ol.format.GML.readSegment_)
-  }
+ol.source.Source.prototype.setLogo = function(logo) {
+  this.logo_ = logo;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @param {ol.source.State} state State.
+ * @protected
  */
-ol.format.GML.ENVELOPE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'lowerCorner': ol.xml.makeArrayPusher(ol.format.GML.readFlatPosList_),
-    'upperCorner': ol.xml.makeArrayPusher(ol.format.GML.readFlatPosList_)
-  }
+ol.source.Source.prototype.setState = function(state) {
+  this.state_ = state;
+  this.changed();
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @param {ol.proj.Projection} projection Projetion.
  */
-ol.format.GML.PATCHES_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'PolygonPatch': ol.xml.makeReplacer(ol.format.GML.readPolygonPatch_)
-  }
+ol.source.Source.prototype.setProjection = function(projection) {
+  this.projection_ = projection;
 };
 
+goog.provide('ol.layer.Base');
+goog.provide('ol.layer.LayerProperty');
+goog.provide('ol.layer.LayerState');
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.GML.SEGMENTS_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'LineStringSegment': ol.xml.makeReplacer(
-        ol.format.GML.readLineStringSegment_)
-  }
-};
+goog.require('goog.events');
+goog.require('goog.math');
+goog.require('goog.object');
+goog.require('ol.Object');
+goog.require('ol.source.State');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @enum {string}
  */
-ol.format.GML.RING_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'LinearRing': ol.xml.makeReplacer(ol.format.GML.readFlatLinearRing_)
-  }
+ol.layer.LayerProperty = {
+  BRIGHTNESS: 'brightness',
+  CONTRAST: 'contrast',
+  HUE: 'hue',
+  OPACITY: 'opacity',
+  SATURATION: 'saturation',
+  VISIBLE: 'visible',
+  EXTENT: 'extent',
+  MAX_RESOLUTION: 'maxResolution',
+  MIN_RESOLUTION: 'minResolution',
+  SOURCE: 'source'
 };
 
 
 /**
- * @inheritDoc
+ * @typedef {{layer: ol.layer.Layer,
+ *            brightness: number,
+ *            contrast: number,
+ *            hue: number,
+ *            opacity: number,
+ *            saturation: number,
+ *            sourceState: ol.source.State,
+ *            visible: boolean,
+ *            extent: (ol.Extent|undefined),
+ *            maxResolution: number,
+ *            minResolution: number}}
  */
-ol.format.GML.prototype.readGeometryFromNode = function(node, opt_options) {
-  var geometry = ol.format.GML.readGeometry(node,
-      [this.getReadOptions(node, goog.isDef(opt_options) ? opt_options : {})]);
-  return (goog.isDef(geometry) ? geometry : null);
-};
+ol.layer.LayerState;
+
 
 
 /**
- * Read all features from a GML FeatureCollection.
+ * @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.
  *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {Array.<ol.Feature>} Features.
+ * @constructor
+ * @extends {ol.Object}
+ * @param {olx.layer.BaseOptions} options Layer options.
  * @api stable
  */
-ol.format.GML.prototype.readFeatures;
+ol.layer.Base = function(options) {
 
+  goog.base(this);
 
-/**
- * @inheritDoc
- */
-ol.format.GML.prototype.readFeaturesFromNode = function(node, opt_options) {
-  var options = {
-    'featureType': this.featureType_,
-    'featureNS': this.featureNS_
-  };
-  if (goog.isDef(opt_options)) {
-    goog.object.extend(options, this.getReadOptions(node, opt_options));
-  }
-  return ol.format.GML.readFeatures_(node, [options]);
+  /**
+   * @type {Object.<string, *>}
+   */
+  var properties = goog.object.clone(options);
+  properties[ol.layer.LayerProperty.BRIGHTNESS] =
+      goog.isDef(options.brightness) ? options.brightness : 0;
+  properties[ol.layer.LayerProperty.CONTRAST] =
+      goog.isDef(options.contrast) ? options.contrast : 1;
+  properties[ol.layer.LayerProperty.HUE] =
+      goog.isDef(options.hue) ? options.hue : 0;
+  properties[ol.layer.LayerProperty.OPACITY] =
+      goog.isDef(options.opacity) ? options.opacity : 1;
+  properties[ol.layer.LayerProperty.SATURATION] =
+      goog.isDef(options.saturation) ? options.saturation : 1;
+  properties[ol.layer.LayerProperty.VISIBLE] =
+      goog.isDef(options.visible) ? options.visible : true;
+  properties[ol.layer.LayerProperty.MAX_RESOLUTION] =
+      goog.isDef(options.maxResolution) ? options.maxResolution : Infinity;
+  properties[ol.layer.LayerProperty.MIN_RESOLUTION] =
+      goog.isDef(options.minResolution) ? options.minResolution : 0;
+
+  this.setProperties(properties);
 };
+goog.inherits(ol.layer.Base, ol.Object);
 
 
 /**
- * @inheritDoc
+ * @return {number|undefined} The brightness of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.prototype.readProjectionFromNode = function(node) {
-  return ol.proj.get(goog.isDef(this.srsName_) ? this.srsName_ :
-      node.firstElementChild.getAttribute('srsName'));
+ol.layer.Base.prototype.getBrightness = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.layer.LayerProperty.BRIGHTNESS));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getBrightness',
+    ol.layer.Base.prototype.getBrightness);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Point} value Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @return {number|undefined} The contrast of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.writePos_ = function(node, value, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  var axisOrientation = 'enu';
-  if (goog.isDefAndNotNull(srsName)) {
-    axisOrientation = ol.proj.get(srsName).getAxisOrientation();
-  }
-  var point = value.getCoordinates();
-  var coords;
-  // only 2d for simple features profile
-  if (axisOrientation.substr(0, 2) === 'en') {
-    coords = (point[0] + ' ' + point[1]);
-  } else {
-    coords = (point[1] + ' ' + point[0]);
-  }
-  ol.format.XSD.writeStringTextNode(node, coords);
+ol.layer.Base.prototype.getContrast = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.layer.LayerProperty.CONTRAST));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getContrast',
+    ol.layer.Base.prototype.getContrast);
 
 
 /**
- * @param {Array.<number>} point Point geometry.
- * @param {string=} opt_srsName Optional srsName
- * @return {string}
- * @private
+ * @return {number|undefined} The hue of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.getCoords_ = function(point, opt_srsName) {
-  var axisOrientation = 'enu';
-  if (goog.isDefAndNotNull(opt_srsName)) {
-    axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
-  }
-  return ((axisOrientation.substr(0, 2) === 'en') ?
-      point[0] + ' ' + point[1] :
-      point[1] + ' ' + point[0]);
+ol.layer.Base.prototype.getHue = function() {
+  return /** @type {number|undefined} */ (this.get(ol.layer.LayerProperty.HUE));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getHue',
+    ol.layer.Base.prototype.getHue);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @return {ol.layer.LayerState} Layer state.
  */
-ol.format.GML.writePosList_ = function(node, value, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(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] = ol.format.GML.getCoords_(point, srsName);
-  }
-  ol.format.XSD.writeStringTextNode(node, parts.join(' '));
+ol.layer.Base.prototype.getLayerState = function() {
+  var brightness = this.getBrightness();
+  var contrast = this.getContrast();
+  var hue = this.getHue();
+  var opacity = this.getOpacity();
+  var saturation = this.getSaturation();
+  var sourceState = this.getSourceState();
+  var visible = this.getVisible();
+  var extent = this.getExtent();
+  var maxResolution = this.getMaxResolution();
+  var minResolution = this.getMinResolution();
+  return {
+    layer: /** @type {ol.layer.Layer} */ (this),
+    brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0,
+    contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1,
+    hue: goog.isDef(hue) ? hue : 0,
+    opacity: goog.isDef(opacity) ? goog.math.clamp(opacity, 0, 1) : 1,
+    saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1,
+    sourceState: sourceState,
+    visible: goog.isDef(visible) ? !!visible : true,
+    extent: extent,
+    maxResolution: goog.isDef(maxResolution) ? maxResolution : Infinity,
+    minResolution: goog.isDef(minResolution) ? Math.max(minResolution, 0) : 0
+  };
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Point} geometry Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
+ *     modified in place).
+ * @return {Array.<ol.layer.Layer>} Array of layers.
  */
-ol.format.GML.writePoint_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  if (goog.isDefAndNotNull(srsName)) {
-    node.setAttribute('srsName', srsName);
-  }
-  var pos = ol.xml.createElementNS(node.namespaceURI, 'pos');
-  node.appendChild(pos);
-  ol.format.GML.writePos_(pos, geometry, objectStack);
-};
+ol.layer.Base.prototype.getLayersArray = goog.abstractMethod;
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @param {Array.<ol.layer.LayerState>=} opt_states Optional list of layer
+ *     states (to be modified in place).
+ * @return {Array.<ol.layer.LayerState>} List of layer states.
  */
-ol.format.GML.ENVELOPE_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-    'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-  }
-};
+ol.layer.Base.prototype.getLayerStatesArray = goog.abstractMethod;
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Extent} extent Extent.
- * @param {Array.<*>} objectStack Node stack.
+ * @return {ol.Extent|undefined} The layer extent.
+ * @observable
+ * @api stable
  */
-ol.format.GML.writeEnvelope = function(node, extent, objectStack) {
-  goog.asserts.assert(extent.length == 4);
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  if (goog.isDef(srsName)) {
-    node.setAttribute('srsName', srsName);
-  }
-  var keys = ['lowerCorner', 'upperCorner'];
-  var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]];
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
-      ({node: node}), ol.format.GML.ENVELOPE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values,
-      objectStack, keys);
+ol.layer.Base.prototype.getExtent = function() {
+  return /** @type {ol.Extent|undefined} */ (
+      this.get(ol.layer.LayerProperty.EXTENT));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getExtent',
+    ol.layer.Base.prototype.getExtent);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} geometry LinearRing geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @return {number|undefined} The maximum resolution of the layer.
+ * @observable
+ * @api stable
  */
-ol.format.GML.writeLinearRing_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  if (goog.isDefAndNotNull(srsName)) {
-    node.setAttribute('srsName', srsName);
-  }
-  var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
-  node.appendChild(posList);
-  ol.format.GML.writePosList_(posList, geometry, objectStack);
+ol.layer.Base.prototype.getMaxResolution = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getMaxResolution',
+    ol.layer.Base.prototype.getMaxResolution);
 
 
 /**
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node} Node.
- * @private
+ * @return {number|undefined} The minimum resolution of the layer.
+ * @observable
+ * @api stable
  */
-ol.format.GML.RING_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
-  var context = objectStack[objectStack.length - 1];
-  var parentNode = context.node;
-  goog.asserts.assert(goog.isObject(context));
-  var exteriorWritten = goog.object.get(context, 'exteriorWritten');
-  if (!goog.isDef(exteriorWritten)) {
-    goog.object.set(context, 'exteriorWritten', true);
-  }
-  return ol.xml.createElementNS(parentNode.namespaceURI,
-      goog.isDef(exteriorWritten) ? 'interior' : 'exterior');
+ol.layer.Base.prototype.getMinResolution = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getMinResolution',
+    ol.layer.Base.prototype.getMinResolution);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} geometry Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @return {number|undefined} The opacity of the layer.
+ * @observable
+ * @api stable
  */
-ol.format.GML.writeSurfaceOrPolygon_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  if (node.nodeName !== 'PolygonPatch' && goog.isDefAndNotNull(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.GML.RING_SERIALIZERS_, ol.format.GML.RING_NODE_FACTORY_,
-        rings, objectStack);
-  } else if (node.nodeName === 'Surface') {
-    var patches = ol.xml.createElementNS(node.namespaceURI, 'patches');
-    node.appendChild(patches);
-    ol.format.GML.writeSurfacePatches_(patches, geometry, objectStack);
-  }
+ol.layer.Base.prototype.getOpacity = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.layer.LayerProperty.OPACITY));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getOpacity',
+    ol.layer.Base.prototype.getOpacity);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} geometry LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @return {number|undefined} The saturation of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.writeCurveOrLineString_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  if (node.nodeName !== 'LineStringSegment' && goog.isDefAndNotNull(srsName)) {
-    node.setAttribute('srsName', srsName);
-  }
-  if (node.nodeName === 'LineString' || node.nodeName === 'LineStringSegment') {
-    var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
-    node.appendChild(posList);
-    ol.format.GML.writePosList_(posList, geometry, objectStack);
-  } else if (node.nodeName === 'Curve') {
-    var segments = ol.xml.createElementNS(node.namespaceURI, 'segments');
-    node.appendChild(segments);
-    ol.format.GML.writeCurveSegments_(segments, geometry, objectStack);
-  }
+ol.layer.Base.prototype.getSaturation = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.layer.LayerProperty.SATURATION));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getSaturation',
+    ol.layer.Base.prototype.getSaturation);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @return {ol.source.State} Source state.
  */
-ol.format.GML.writeMultiSurfaceOrPolygon_ = function(node, geometry,
-    objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  var surface = goog.object.get(context, 'surface');
-  if (goog.isDefAndNotNull(srsName)) {
-    node.setAttribute('srsName', srsName);
-  }
-  var polygons = geometry.getPolygons();
-  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface},
-      ol.format.GML.SURFACEORPOLYGONMEMBER_SERIALIZERS_,
-      ol.format.GML.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons,
-      objectStack);
-};
+ol.layer.Base.prototype.getSourceState = goog.abstractMethod;
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.MultiPoint} geometry MultiPoint geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @return {boolean|undefined} The visiblity of the layer.
+ * @observable
+ * @api stable
  */
-ol.format.GML.writeMultiPoint_ = function(node, geometry,
-    objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  if (goog.isDefAndNotNull(srsName)) {
-    node.setAttribute('srsName', srsName);
-  }
-  var points = geometry.getPoints();
-  ol.xml.pushSerializeAndPop({node: node, srsName: srsName},
-      ol.format.GML.POINTMEMBER_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory('pointMember'), points,
-      objectStack);
+ol.layer.Base.prototype.getVisible = function() {
+  return /** @type {boolean|undefined} */ (
+      this.get(ol.layer.LayerProperty.VISIBLE));
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'getVisible',
+    ol.layer.Base.prototype.getVisible);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.MultiLineString} geometry MultiLineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Adjust the layer brightness.  A value of -1 will render the layer completely
+ * black.  A value of 0 will leave the brightness unchanged.  A value of 1 will
+ * render the layer completely white.  Other values are linear multipliers on
+ * the effect (values are clamped between -1 and 1).
+ *
+ * The filter effects draft [1] says the brightness function is supposed to
+ * render 0 black, 1 unchanged, and all other values as a linear multiplier.
+ *
+ * The current WebKit implementation clamps values between -1 (black) and 1
+ * (white) [2].  There is a bug open to change the filter effect spec [3].
+ *
+ * TODO: revisit this if the spec is still unmodified before we release
+ *
+ * [1] https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
+ * [2] https://github.com/WebKit/webkit/commit/8f4765e569
+ * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647
+ *
+ * @param {number|undefined} brightness The brightness of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.writeMultiCurveOrLineString_ = function(node, geometry,
-    objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  var curve = goog.object.get(context, 'curve');
-  if (goog.isDefAndNotNull(srsName)) {
-    node.setAttribute('srsName', srsName);
-  }
-  var lines = geometry.getLineStrings();
-  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve},
-      ol.format.GML.LINESTRINGORCURVEMEMBER_SERIALIZERS_,
-      ol.format.GML.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines,
-      objectStack);
+ol.layer.Base.prototype.setBrightness = function(brightness) {
+  this.set(ol.layer.LayerProperty.BRIGHTNESS, brightness);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setBrightness',
+    ol.layer.Base.prototype.setBrightness);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} ring LinearRing geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Adjust the layer contrast.  A value of 0 will render the layer completely
+ * grey.  A value of 1 will leave the contrast unchanged.  Other values are
+ * linear multipliers on the effect (and values over 1 are permitted).
+ *
+ * @param {number|undefined} contrast The contrast of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.writeRing_ = function(node, ring, objectStack) {
-  var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing');
-  node.appendChild(linearRing);
-  ol.format.GML.writeLinearRing_(linearRing, ring, objectStack);
+ol.layer.Base.prototype.setContrast = function(contrast) {
+  this.set(ol.layer.LayerProperty.CONTRAST, contrast);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setContrast',
+    ol.layer.Base.prototype.setContrast);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Apply a hue-rotation to the layer.  A value of 0 will leave the hue
+ * unchanged.  Other values are radians around the color circle.
+ * @param {number|undefined} hue The hue of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.writeSurfaceOrPolygonMember_ = function(node, polygon,
-    objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var child = ol.format.GML.GEOMETRY_NODE_FACTORY_(polygon, objectStack);
-  if (goog.isDef(child)) {
-    node.appendChild(child);
-    ol.format.GML.writeSurfaceOrPolygon_(child, polygon, objectStack);
-  }
+ol.layer.Base.prototype.setHue = function(hue) {
+  this.set(ol.layer.LayerProperty.HUE, hue);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setHue',
+    ol.layer.Base.prototype.setHue);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Point} point Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * 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.format.GML.writePointMember_ = function(node, point, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI, 'Point');
-  node.appendChild(child);
-  ol.format.GML.writePoint_(child, point, objectStack);
+ol.layer.Base.prototype.setExtent = function(extent) {
+  this.set(ol.layer.LayerProperty.EXTENT, extent);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setExtent',
+    ol.layer.Base.prototype.setExtent);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} line LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @param {number|undefined} maxResolution The maximum resolution of the layer.
+ * @observable
+ * @api stable
  */
-ol.format.GML.writeLineStringOrCurveMember_ = function(node, line,
-    objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var child = ol.format.GML.GEOMETRY_NODE_FACTORY_(line, objectStack);
-  if (goog.isDef(child)) {
-    node.appendChild(child);
-    ol.format.GML.writeCurveOrLineString_(child, line, objectStack);
-  }
+ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
+  this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setMaxResolution',
+    ol.layer.Base.prototype.setMaxResolution);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML.writeSurfacePatches_ = function(node, polygon, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch');
-  node.appendChild(child);
-  ol.format.GML.writeSurfaceOrPolygon_(child, polygon, objectStack);
+ * @param {number|undefined} 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);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setMinResolution',
+    ol.layer.Base.prototype.setMinResolution);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} line LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @param {number|undefined} opacity The opacity of the layer.
+ * @observable
+ * @api stable
  */
-ol.format.GML.writeCurveSegments_ = function(node, line, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI, 'LineStringSegment');
-  node.appendChild(child);
-  ol.format.GML.writeCurveOrLineString_(child, line, objectStack);
+ol.layer.Base.prototype.setOpacity = function(opacity) {
+  this.set(ol.layer.LayerProperty.OPACITY, opacity);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setOpacity',
+    ol.layer.Base.prototype.setOpacity);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
- * @param {Array.<*>} objectStack Node stack.
+ * Adjust layer saturation.  A value of 0 will render the layer completely
+ * unsaturated.  A value of 1 will leave the saturation unchanged.  Other
+ * values are linear multipliers of the effect (and values over 1 are
+ * permitted).
+ *
+ * @param {number|undefined} saturation The saturation of the layer.
+ * @observable
+ * @api
  */
-ol.format.GML.writeGeometry = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var item = goog.object.clone(context);
-  item.node = node;
-  var value;
-  if (goog.isArray(geometry)) {
-    if (goog.isDef(context.dataProjection)) {
-      value = ol.proj.transformExtent(
-          geometry, context.featureProjection, context.dataProjection);
-    } else {
-      value = geometry;
-    }
-  } else {
-    goog.asserts.assertInstanceof(geometry, ol.geom.Geometry);
-    value =
-        ol.format.Feature.transformWithOptions(geometry, true, context);
-  }
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
-      (item), ol.format.GML.GEOMETRY_SERIALIZERS_,
-      ol.format.GML.GEOMETRY_NODE_FACTORY_, [value], objectStack);
+ol.layer.Base.prototype.setSaturation = function(saturation) {
+  this.set(ol.layer.LayerProperty.SATURATION, saturation);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setSaturation',
+    ol.layer.Base.prototype.setSaturation);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
+ * @param {boolean|undefined} visible The visiblity of the layer.
+ * @observable
+ * @api stable
  */
-ol.format.GML.writeFeature = function(node, feature, objectStack) {
-  var fid = feature.getId();
-  if (goog.isDef(fid)) {
-    node.setAttribute('fid', fid);
-  }
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var featureNS = goog.object.get(context, 'featureNS');
-  var geometryName = feature.getGeometryName();
-  if (!goog.isDef(context.serializers)) {
-    context.serializers = {};
-    context.serializers[featureNS] = {};
-  }
-  var properties = feature.getProperties();
-  var keys = [], values = [];
-  for (var key in properties) {
-    var value = properties[key];
-    if (!goog.isNull(value)) {
-      keys.push(key);
-      values.push(value);
-      if (key == geometryName) {
-        if (!(key in context.serializers[featureNS])) {
-          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
-              ol.format.GML.writeGeometry);
-        }
-      } else {
-        if (!(key in context.serializers[featureNS])) {
-          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
-              ol.format.XSD.writeStringTextNode);
-        }
-      }
-    }
-  }
-  var item = goog.object.clone(context);
-  item.node = node;
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
-      (item), context.serializers,
-      ol.xml.makeSimpleNodeFactory(undefined, featureNS),
-      values,
-      objectStack, keys);
+ol.layer.Base.prototype.setVisible = function(visible) {
+  this.set(ol.layer.LayerProperty.VISIBLE, visible);
 };
+goog.exportProperty(
+    ol.layer.Base.prototype,
+    'setVisible',
+    ol.layer.Base.prototype.setVisible);
 
+goog.provide('ol.layer.Layer');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol.Object');
+goog.require('ol.layer.Base');
+goog.require('ol.layer.LayerProperty');
+goog.require('ol.source.State');
 
-/**
- * @param {Node} node Node.
- * @param {Array.<ol.Feature>} features Features.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML.writeFeatureMembers_ = function(node, features, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featureNS = goog.object.get(context, 'featureNS');
-  var serializers = {};
-  serializers[featureNS] = {};
-  serializers[featureNS][featureType] = ol.xml.makeChildAppender(
-      ol.format.GML.writeFeature);
-  var item = goog.object.clone(context);
-  item.node = node;
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
-      (item),
-      serializers,
-      ol.xml.makeSimpleNodeFactory(featureType, featureNS), features,
-      objectStack);
-};
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @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.
+ *
+ * @constructor
+ * @extends {ol.layer.Base}
+ * @fires ol.render.Event
+ * @fires change Triggered when the state of the source changes.
+ * @param {olx.layer.LayerOptions} options Layer options.
+ * @api stable
  */
-ol.format.GML.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'surfaceMember': ol.xml.makeChildAppender(
-        ol.format.GML.writeSurfaceOrPolygonMember_),
-    'polygonMember': ol.xml.makeChildAppender(
-        ol.format.GML.writeSurfaceOrPolygonMember_)
-  }
+ol.layer.Layer = function(options) {
+
+  var baseOptions = goog.object.clone(options);
+  delete baseOptions.source;
+
+  goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
+
+  /**
+   * @private
+   * @type {goog.events.Key}
+   */
+  this.sourceChangeKey_ = null;
+
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.LayerProperty.SOURCE),
+      this.handleSourcePropertyChange_, false, this);
+
+  var source = goog.isDef(options.source) ? options.source : null;
+  this.setSource(source);
 };
+goog.inherits(ol.layer.Layer, ol.layer.Base);
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * Return `true` if the layer is visible, and if the passed resolution is
+ * between the layer's minResolution and maxResolution. The comparison is
+ * inclusive for `minResolution` and exclusive for `maxResolution`.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @param {number} resolution Resolution.
+ * @return {boolean} The layer is visible at the given resolution.
  */
-ol.format.GML.POINTMEMBER_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'pointMember': ol.xml.makeChildAppender(ol.format.GML.writePointMember_)
-  }
+ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
+  return layerState.visible && resolution >= layerState.minResolution &&
+      resolution < layerState.maxResolution;
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GML.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'lineStringMember': ol.xml.makeChildAppender(
-        ol.format.GML.writeLineStringOrCurveMember_),
-    'curveMember': ol.xml.makeChildAppender(
-        ol.format.GML.writeLineStringOrCurveMember_)
-  }
+ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
+  var array = goog.isDef(opt_array) ? opt_array : [];
+  array.push(this);
+  return array;
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-ol.format.GML.RING_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'exterior': ol.xml.makeChildAppender(ol.format.GML.writeRing_),
-    'interior': ol.xml.makeChildAppender(ol.format.GML.writeRing_)
-  }
+ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
+  var states = goog.isDef(opt_states) ? opt_states : [];
+  states.push(this.getLayerState());
+  return states;
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * Get the layer source.
+ * @return {ol.source.Source} The layer source (or `null` if not yet set).
+ * @observable
+ * @api stable
  */
-ol.format.GML.GEOMETRY_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'Curve': ol.xml.makeChildAppender(ol.format.GML.writeCurveOrLineString_),
-    'MultiCurve': ol.xml.makeChildAppender(
-        ol.format.GML.writeMultiCurveOrLineString_),
-    'Point': ol.xml.makeChildAppender(ol.format.GML.writePoint_),
-    'MultiPoint': ol.xml.makeChildAppender(ol.format.GML.writeMultiPoint_),
-    'LineString': ol.xml.makeChildAppender(
-        ol.format.GML.writeCurveOrLineString_),
-    'MultiLineString': ol.xml.makeChildAppender(
-        ol.format.GML.writeMultiCurveOrLineString_),
-    'LinearRing': ol.xml.makeChildAppender(ol.format.GML.writeLinearRing_),
-    'Polygon': ol.xml.makeChildAppender(ol.format.GML.writeSurfaceOrPolygon_),
-    'MultiPolygon': ol.xml.makeChildAppender(
-        ol.format.GML.writeMultiSurfaceOrPolygon_),
-    'Surface': ol.xml.makeChildAppender(ol.format.GML.writeSurfaceOrPolygon_),
-    'MultiSurface': ol.xml.makeChildAppender(
-        ol.format.GML.writeMultiSurfaceOrPolygon_),
-    'Envelope': ol.xml.makeChildAppender(
-        ol.format.GML.writeEnvelope)
-  }
+ol.layer.Layer.prototype.getSource = function() {
+  var source = this.get(ol.layer.LayerProperty.SOURCE);
+  return goog.isDef(source) ?
+      /** @type {ol.source.Source} */ (source) : null;
 };
+goog.exportProperty(
+    ol.layer.Layer.prototype,
+    'getSource',
+    ol.layer.Layer.prototype.getSource);
 
 
 /**
- * @const
- * @type {Object.<string, string>}
- * @private
- */
-ol.format.GML.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = {
-  'MultiLineString': 'lineStringMember',
-  'MultiCurve': 'curveMember',
-  'MultiPolygon': 'polygonMember',
-  'MultiSurface': 'surfaceMember'
+  * @inheritDoc
+  */
+ol.layer.Layer.prototype.getSourceState = function() {
+  var source = this.getSource();
+  return goog.isNull(source) ? ol.source.State.UNDEFINED : source.getState();
 };
 
 
 /**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
  * @private
  */
-ol.format.GML.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = function(value,
-    objectStack, opt_nodeName) {
-  var parentNode = objectStack[objectStack.length - 1].node;
-  goog.asserts.assert(ol.xml.isNode(parentNode));
-  return ol.xml.createElementNS('http://www.opengis.net/gml',
-      ol.format.GML.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]);
+ol.layer.Layer.prototype.handleSourceChange_ = function() {
+  this.changed();
 };
 
 
 /**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
  * @private
  */
-ol.format.GML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack,
-    opt_nodeName) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var multiSurface = goog.object.get(context, 'multiSurface');
-  var surface = goog.object.get(context, 'surface');
-  var curve = goog.object.get(context, 'curve');
-  var multiCurve = goog.object.get(context, 'multiCurve');
-  var parentNode = objectStack[objectStack.length - 1].node;
-  goog.asserts.assert(ol.xml.isNode(parentNode));
-  var nodeName;
-  if (!goog.isArray(value)) {
-    goog.asserts.assertInstanceof(value, ol.geom.Geometry);
-    nodeName = value.getType();
-    if (nodeName === 'MultiPolygon' && multiSurface === true) {
-      nodeName = 'MultiSurface';
-    } else if (nodeName === 'Polygon' && surface === true) {
-      nodeName = 'Surface';
-    } else if (nodeName === 'LineString' && curve === true) {
-      nodeName = 'Curve';
-    } else if (nodeName === 'MultiLineString' && multiCurve === true) {
-      nodeName = 'MultiCurve';
-    }
-  } else {
-    nodeName = 'Envelope';
+ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() {
+  if (!goog.isNull(this.sourceChangeKey_)) {
+    goog.events.unlistenByKey(this.sourceChangeKey_);
+    this.sourceChangeKey_ = null;
   }
-  return ol.xml.createElementNS('http://www.opengis.net/gml',
-      nodeName);
+  var source = this.getSource();
+  if (!goog.isNull(source)) {
+    this.sourceChangeKey_ = goog.events.listen(source,
+        goog.events.EventType.CHANGE, this.handleSourceChange_, false, this);
+  }
+  this.changed();
 };
 
 
 /**
- * @inheritDoc
+ * Set the layer source.
+ * @param {ol.source.Source} source The layer source.
+ * @observable
+ * @api stable
  */
-ol.format.GML.prototype.writeGeometryNode = function(geometry, 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 (goog.isDef(opt_options)) {
-    goog.object.extend(context, opt_options);
-  }
-  ol.format.GML.writeGeometry(geom, geometry, [context]);
-  return geom;
+ol.layer.Layer.prototype.setSource = function(source) {
+  this.set(ol.layer.LayerProperty.SOURCE, source);
 };
+goog.exportProperty(
+    ol.layer.Layer.prototype,
+    'setSource',
+    ol.layer.Layer.prototype.setSource);
 
+goog.provide('ol.ImageBase');
+goog.provide('ol.ImageState');
 
-/**
- * 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 {Node} Result.
- * @api stable
- */
-ol.format.GML.prototype.writeFeatures;
+goog.require('goog.asserts');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('ol.Attribution');
+goog.require('ol.Extent');
 
 
 /**
- * @inheritDoc
+ * @enum {number}
  */
-ol.format.GML.prototype.writeFeaturesNode = function(features, 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 (goog.isDef(opt_options)) {
-    goog.object.extend(context, opt_options);
-  }
-  ol.format.GML.writeFeatureMembers_(node, features, [context]);
-  return node;
+ol.ImageState = {
+  IDLE: 0,
+  LOADING: 1,
+  LOADED: 2,
+  ERROR: 3
 };
 
-goog.provide('ol.format.GPX');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.object');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.format.XSD');
-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
+ * @extends {goog.events.EventTarget}
+ * @param {ol.Extent} extent Extent.
+ * @param {number|undefined} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.ImageState} state State.
+ * @param {Array.<ol.Attribution>} attributions Attributions.
  */
-ol.format.GPX = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) {
 
   goog.base(this);
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {Array.<ol.Attribution>}
    */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+  this.attributions_ = attributions;
+
+  /**
+   * @protected
+   * @type {ol.Extent}
+   */
+  this.extent = extent;
 
   /**
-   * @type {function(ol.Feature, Node)|undefined}
    * @private
+   * @type {number}
    */
-  this.readExtensions_ = options.readExtensions;
+  this.pixelRatio_ = pixelRatio;
+
+  /**
+   * @protected
+   * @type {number|undefined}
+   */
+  this.resolution = resolution;
+
+  /**
+   * @protected
+   * @type {ol.ImageState}
+   */
+  this.state = state;
+
 };
-goog.inherits(ol.format.GPX, ol.format.XMLFeature);
+goog.inherits(ol.ImageBase, goog.events.EventTarget);
 
 
 /**
- * @const
- * @private
- * @type {Array.<string>}
+ * @protected
  */
-ol.format.GPX.NAMESPACE_URIS_ = [
-  null,
-  'http://www.topografix.com/GPX/1/0',
-  'http://www.topografix.com/GPX/1/1'
-];
+ol.ImageBase.prototype.changed = function() {
+  this.dispatchEvent(goog.events.EventType.CHANGE);
+};
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Node} node Node.
- * @param {Object} values Values.
- * @private
- * @return {Array.<number>} Flat coordinates.
+ * @return {Array.<ol.Attribution>} Attributions.
  */
-ol.format.GPX.appendCoordinate_ = function(flatCoordinates, node, values) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  flatCoordinates.push(
-      parseFloat(node.getAttribute('lon')),
-      parseFloat(node.getAttribute('lat')));
-  if (goog.object.containsKey(values, 'ele')) {
-    flatCoordinates.push(
-        /** @type {number} */ (goog.object.get(values, 'ele')));
-    goog.object.remove(values, 'ele');
-  } else {
-    flatCoordinates.push(0);
-  }
-  if (goog.object.containsKey(values, 'time')) {
-    flatCoordinates.push(
-        /** @type {number} */ (goog.object.get(values, 'time')));
-    goog.object.remove(values, 'time');
-  } else {
-    flatCoordinates.push(0);
-  }
-  return flatCoordinates;
+ol.ImageBase.prototype.getAttributions = function() {
+  return this.attributions_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {ol.Extent} Extent.
  */
-ol.format.GPX.parseLink_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'link');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var href = node.getAttribute('href');
-  if (!goog.isNull(href)) {
-    goog.object.set(values, 'link', href);
-  }
-  ol.xml.parse(ol.format.GPX.LINK_PARSERS_, node, objectStack);
+ol.ImageBase.prototype.getExtent = function() {
+  return this.extent;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @param {Object=} opt_context Object.
+ * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
  */
-ol.format.GPX.parseExtensions_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'extensions');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  goog.object.set(values, 'extensionsNode_', node);
-};
+ol.ImageBase.prototype.getImage = goog.abstractMethod;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {number} PixelRatio.
  */
-ol.format.GPX.parseRtePt_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'rtept');
-  var values = ol.xml.pushParseAndPop(
-      {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack);
-  if (goog.isDef(values)) {
-    var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-    var flatCoordinates = /** @type {Array.<number>} */
-        (goog.object.get(rteValues, 'flatCoordinates'));
-    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
-  }
+ol.ImageBase.prototype.getPixelRatio = function() {
+  return this.pixelRatio_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {number} Resolution.
  */
-ol.format.GPX.parseTrkPt_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'trkpt');
-  var values = ol.xml.pushParseAndPop(
-      {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack);
-  if (goog.isDef(values)) {
-    var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-    var flatCoordinates = /** @type {Array.<number>} */
-        (goog.object.get(trkValues, 'flatCoordinates'));
-    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
-  }
+ol.ImageBase.prototype.getResolution = function() {
+  goog.asserts.assert(goog.isDef(this.resolution), 'resolution not yet set');
+  return this.resolution;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {ol.ImageState} State.
  */
-ol.format.GPX.parseTrkSeg_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'trkseg');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  ol.xml.parse(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack);
-  var flatCoordinates = /** @type {Array.<number>} */
-      (goog.object.get(values, 'flatCoordinates'));
-  var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends'));
-  ends.push(flatCoordinates.length);
+ol.ImageBase.prototype.getState = function() {
+  return this.state;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
+ * Load not yet loaded URI.
  */
-ol.format.GPX.readRte_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'rte');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var values = ol.xml.pushParseAndPop({
-    'flatCoordinates': []
-  }, ol.format.GPX.RTE_PARSERS_, node, objectStack);
-  if (!goog.isDef(values)) {
-    return undefined;
-  }
-  var flatCoordinates = /** @type {Array.<number>} */
-      (goog.object.get(values, 'flatCoordinates'));
-  goog.object.remove(values, 'flatCoordinates');
-  var geometry = new ol.geom.LineString(null);
-  geometry.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setProperties(values);
-  return feature;
-};
+ol.ImageBase.prototype.load = goog.abstractMethod;
+
+goog.provide('ol.Tile');
+goog.provide('ol.TileState');
+
+goog.require('goog.events');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('ol.TileCoord');
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
+ * @enum {number}
  */
-ol.format.GPX.readTrk_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'trk');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var values = ol.xml.pushParseAndPop({
-    'flatCoordinates': [],
-    'ends': []
-  }, ol.format.GPX.TRK_PARSERS_, node, objectStack);
-  if (!goog.isDef(values)) {
-    return undefined;
-  }
-  var flatCoordinates = /** @type {Array.<number>} */
-      (goog.object.get(values, 'flatCoordinates'));
-  goog.object.remove(values, 'flatCoordinates');
-  var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends'));
-  goog.object.remove(values, 'ends');
-  var geometry = new ol.geom.MultiLineString(null);
-  geometry.setFlatCoordinates(
-      ol.geom.GeometryLayout.XYZM, flatCoordinates, ends);
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setProperties(values);
-  return feature;
+ol.TileState = {
+  IDLE: 0,
+  LOADING: 1,
+  LOADED: 2,
+  ERROR: 3,
+  EMPTY: 4
 };
 
 
+
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Waypoint.
+ * @classdesc
+ * Base class for tiles.
+ *
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
  */
-ol.format.GPX.readWpt_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'wpt');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var values = ol.xml.pushParseAndPop(
-      {}, ol.format.GPX.WPT_PARSERS_, node, objectStack);
-  if (!goog.isDef(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;
+ol.Tile = function(tileCoord, state) {
+
+  goog.base(this);
+
+  /**
+   * @type {ol.TileCoord}
+   */
+  this.tileCoord = tileCoord;
+
+  /**
+   * @protected
+   * @type {ol.TileState}
+   */
+  this.state = state;
+
 };
+goog.inherits(ol.Tile, goog.events.EventTarget);
 
 
 /**
- * @const
- * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>}
- * @private
+ * @protected
  */
-ol.format.GPX.FEATURE_READER_ = {
-  'rte': ol.format.GPX.readRte_,
-  'trk': ol.format.GPX.readTrk_,
-  'wpt': ol.format.GPX.readWpt_
+ol.Tile.prototype.changed = function() {
+  this.dispatchEvent(goog.events.EventType.CHANGE);
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @function
+ * @param {Object=} opt_context Object.
+ * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
  */
-ol.format.GPX.GPX_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_),
-      'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_),
-      'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_)
-    });
+ol.Tile.prototype.getImage = goog.abstractMethod;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {string} Key.
  */
-ol.format.GPX.LINK_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'text':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'),
-      'type':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType')
-    });
+ol.Tile.prototype.getKey = function() {
+  return goog.getUid(this).toString();
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {ol.TileCoord}
+ * @api
  */
-ol.format.GPX.RTE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'link': ol.format.GPX.parseLink_,
-      'number':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
-      'extensions': ol.format.GPX.parseExtensions_,
-      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'rtept': ol.format.GPX.parseRtePt_
-    });
+ol.Tile.prototype.getTileCoord = function() {
+  return this.tileCoord;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {ol.TileState} State.
  */
-ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
-    });
+ol.Tile.prototype.getState = function() {
+  return this.state;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * FIXME empty description for jsdoc
  */
-ol.format.GPX.TRK_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'link': ol.format.GPX.parseLink_,
-      'number':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
-      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'extensions': ol.format.GPX.parseExtensions_,
-      'trkseg': ol.format.GPX.parseTrkSeg_
-    });
+ol.Tile.prototype.load = goog.abstractMethod;
+
+goog.provide('ol.tilegrid.TileGrid');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('ol');
+goog.require('ol.Coordinate');
+goog.require('ol.TileCoord');
+goog.require('ol.TileRange');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.extent.Corner');
+goog.require('ol.proj');
+goog.require('ol.proj.METERS_PER_UNIT');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.tilecoord');
+
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @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.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'trkpt': ol.format.GPX.parseTrkPt_
-    });
+ol.tilegrid.TileGrid = function(options) {
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.minZoom = goog.isDef(options.minZoom) ? options.minZoom : 0;
+
+  /**
+   * @private
+   * @type {!Array.<number>}
+   */
+  this.resolutions_ = options.resolutions;
+  goog.asserts.assert(goog.array.isSorted(this.resolutions_, function(a, b) {
+    return b - a;
+  }, true));
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.maxZoom = this.resolutions_.length - 1;
+
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.origin_ = goog.isDef(options.origin) ? options.origin : null;
+
+  /**
+   * @private
+   * @type {Array.<ol.Coordinate>}
+   */
+  this.origins_ = null;
+  if (goog.isDef(options.origins)) {
+    this.origins_ = options.origins;
+    goog.asserts.assert(this.origins_.length == this.maxZoom + 1);
+  }
+  goog.asserts.assert(
+      (goog.isNull(this.origin_) && !goog.isNull(this.origins_)) ||
+      (!goog.isNull(this.origin_) && goog.isNull(this.origins_)));
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.tileSizes_ = null;
+  if (goog.isDef(options.tileSizes)) {
+    this.tileSizes_ = options.tileSizes;
+    goog.asserts.assert(this.tileSizes_.length == this.maxZoom + 1);
+  }
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.tileSize_ = goog.isDef(options.tileSize) ?
+      options.tileSize :
+      goog.isNull(this.tileSizes_) ? ol.DEFAULT_TILE_SIZE : undefined;
+  goog.asserts.assert(
+      (!goog.isDef(this.tileSize_) && !goog.isNull(this.tileSizes_)) ||
+      (goog.isDef(this.tileSize_) && goog.isNull(this.tileSizes_)));
+
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
  * @private
+ * @type {ol.TileCoord}
  */
-ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
-    });
+ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Returns the identity function. May be overridden in subclasses.
+ * @param {{extent: (ol.Extent|undefined),
+ *          wrapX: (boolean|undefined)}=} opt_options Options.
+ * @return {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=):
+ *     ol.TileCoord} Tile coordinate transform.
  */
-ol.format.GPX.WPT_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime),
-      'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'link': ol.format.GPX.parseLink_,
-      'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'sat': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger),
-      'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'ageofdgpsdata':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'dgpsid':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
-      'extensions': ol.format.GPX.parseExtensions_
-    });
+ol.tilegrid.TileGrid.prototype.createTileCoordTransform =
+    function(opt_options) {
+  return goog.functions.identity;
+};
 
 
 /**
- * @param {Array.<ol.Feature>} features
- * @private
+ * @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.format.GPX.prototype.handleReadExtensions_ = function(features) {
-  if (goog.isNull(features)) {
-    features = [];
-  }
-  for (var i = 0, ii = features.length; i < ii; ++i) {
-    var feature = features[i];
-    if (goog.isDef(this.readExtensions_)) {
-      var extensionsNode = feature.get('extensionsNode_') || null;
-      this.readExtensions_(feature, extensionsNode);
+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;
     }
-    feature.set('extensionsNode_', undefined);
+    --z;
   }
+  return false;
 };
 
 
 /**
- * Read the first feature from a GPX source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
+ * @return {number} Max zoom.
+ * @api
  */
-ol.format.GPX.prototype.readFeature;
+ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
+  return this.maxZoom;
+};
 
 
 /**
- * @inheritDoc
+ * @return {number} Min zoom.
+ * @api
  */
-ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
-    return null;
-  }
-  var featureReader = ol.format.GPX.FEATURE_READER_[node.localName];
-  if (!goog.isDef(featureReader)) {
-    return null;
-  }
-  var feature = featureReader(node, [this.getReadOptions(node, opt_options)]);
-  if (!goog.isDef(feature)) {
-    return null;
-  }
-  this.handleReadExtensions_([feature]);
-  return feature;
+ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
+  return this.minZoom;
 };
 
 
 /**
- * Read all features from a GPX source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
+ * @param {number} z Z.
+ * @return {ol.Coordinate} Origin.
  * @api stable
  */
-ol.format.GPX.prototype.readFeatures;
+ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
+  if (!goog.isNull(this.origin_)) {
+    return this.origin_;
+  } else {
+    goog.asserts.assert(!goog.isNull(this.origins_));
+    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
+    return this.origins_[z];
+  }
+};
 
 
 /**
- * @inheritDoc
+ * @param {number} z Z.
+ * @return {number} Resolution.
+ * @api stable
  */
-ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
-    return [];
-  }
-  if (node.localName == 'gpx') {
-    var features = ol.xml.pushParseAndPop(
-        /** @type {Array.<ol.Feature>} */ ([]), ol.format.GPX.GPX_PARSERS_,
-        node, [this.getReadOptions(node, opt_options)]);
-    if (goog.isDef(features)) {
-      this.handleReadExtensions_(features);
-      return features;
-    } else {
-      return [];
-    }
-  }
-  return [];
+ol.tilegrid.TileGrid.prototype.getResolution = function(z) {
+  goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
+  return this.resolutions_[z];
 };
 
 
 /**
- * Read the projection from a GPX source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
+ * @return {Array.<number>} Resolutions.
  * @api stable
  */
-ol.format.GPX.prototype.readProjection;
+ol.tilegrid.TileGrid.prototype.getResolutions = function() {
+  return this.resolutions_;
+};
 
 
 /**
- * @inheritDoc
+ * @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.format.GPX.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
+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;
+  }
 };
 
 
 /**
- * @inheritDoc
+ * @param {number} z Z.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
+ * @return {ol.Extent} Extent.
  */
-ol.format.GPX.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
+ol.tilegrid.TileGrid.prototype.getTileRangeExtent =
+    function(z, tileRange, opt_extent) {
+  var origin = this.getOrigin(z);
+  var resolution = this.getResolution(z);
+  var tileSize = this.getTileSize(z);
+  var minX = origin[0] + tileRange.minX * tileSize * resolution;
+  var maxX = origin[0] + (tileRange.maxX + 1) * tileSize * resolution;
+  var minY = origin[1] + tileRange.minY * tileSize * resolution;
+  var maxY = origin[1] + (tileRange.maxY + 1) * tileSize * resolution;
+  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {string} value Value for the link's `href` attribute.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
+ * @return {ol.TileRange} Tile range.
  */
-ol.format.GPX.writeLink_ = function(node, value, objectStack) {
-  node.setAttribute('href', value);
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var properties = goog.object.get(context, 'properties');
-  var link = [
-    goog.object.get(properties, 'linkText'),
-    goog.object.get(properties, 'linkType')
-  ];
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}),
-      ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      link, objectStack, ol.format.GPX.LINK_SEQUENCE_);
+ol.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 {Node} node Node.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @param {ol.Extent} extent Extent.
+ * @param {number} z Z.
+ * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
+ * @return {ol.TileRange} Tile range.
  */
-ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var parentNode = context.node;
-  goog.asserts.assert(ol.xml.isNode(parentNode));
-  var namespaceURI = parentNode.namespaceURI;
-  var properties = goog.object.get(context, 'properties');
-  //FIXME Projection handling
-  ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]);
-  ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]);
-  var geometryLayout = goog.object.get(context, 'geometryLayout');
-  /* jshint -W086 */
-  switch (geometryLayout) {
-    case ol.geom.GeometryLayout.XYZM:
-      if (coordinate[3] !== 0) {
-        goog.object.set(properties, 'time', coordinate[3]);
-      }
-    case ol.geom.GeometryLayout.XYZ:
-      if (coordinate[2] !== 0) {
-        goog.object.set(properties, 'ele', coordinate[2]);
-      }
-      break;
-    case ol.geom.GeometryLayout.XYM:
-      if (coordinate[2] !== 0) {
-        goog.object.set(properties, 'time', coordinate[2]);
-      }
-  }
-  /* jshint +W086 */
-  var orderedKeys = ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
-      ({node: node, 'properties': properties}),
-      ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
+ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ =
+    function(extent, z, opt_tileRange) {
+  var resolution = this.getResolution(z);
+  return this.getTileRangeForExtentAndResolution(
+      extent, resolution, opt_tileRange);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @return {ol.Coordinate} Tile center.
  */
-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 (goog.isDef(geometry)) {
-    goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-    geometry = /** @type {ol.geom.LineString} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
-    goog.object.set(context, 'geometryLayout', geometry.getLayout());
-    goog.object.set(properties, 'rtept', geometry.getCoordinates());
-  }
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context),
-      ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
+ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) {
+  var origin = this.getOrigin(tileCoord[0]);
+  var resolution = this.getResolution(tileCoord[0]);
+  var tileSize = this.getTileSize(tileCoord[0]);
+  return [
+    origin[0] + (tileCoord[1] + 0.5) * tileSize * resolution,
+    origin[1] + (tileCoord[2] + 0.5) * tileSize * resolution
+  ];
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.Extent=} opt_extent Temporary extent object.
+ * @return {ol.Extent} Extent.
  */
-ol.format.GPX.writeTrk_ = function(node, feature, objectStack) {
-  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
-  var properties = feature.getProperties();
-  var context = {node: node, 'properties': properties};
-  var geometry = feature.getGeometry();
-  if (goog.isDef(geometry)) {
-    goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
-    geometry = /** @type {ol.geom.MultiLineString} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
-    goog.object.set(properties, 'trkseg', geometry.getLineStrings());
-  }
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context),
-      ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
+ol.tilegrid.TileGrid.prototype.getTileCoordExtent =
+    function(tileCoord, opt_extent) {
+  var origin = this.getOrigin(tileCoord[0]);
+  var resolution = this.getResolution(tileCoord[0]);
+  var tileSize = this.getTileSize(tileCoord[0]);
+  var minX = origin[0] + tileCoord[1] * tileSize * resolution;
+  var minY = origin[1] + tileCoord[2] * tileSize * resolution;
+  var maxX = minX + tileSize * resolution;
+  var maxY = minY + tileSize * resolution;
+  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} lineString LineString.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * 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.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) {
-  var context = {node: node, 'geometryLayout': lineString.getLayout(),
-    'properties': {}};
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context),
-      ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_,
-      lineString.getCoordinates(), objectStack);
+ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(
+    coordinate, resolution, opt_tileCoord) {
+  return this.getTileCoordForXYAndResolution_(
+      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
+ * @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.format.GPX.writeWpt_ = function(node, feature, objectStack) {
-  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  goog.object.set(context, 'properties', feature.getProperties());
-  var geometry = feature.getGeometry();
-  if (goog.isDef(geometry)) {
-    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
-    geometry = /** @type {ol.geom.Point} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
-    goog.object.set(context, 'geometryLayout', geometry.getLayout());
-    ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack);
+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 = this.getTileSize(z);
+
+  var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize);
+  var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize);
+
+  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);
 };
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} z Z.
+ * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
+ * @return {ol.TileCoord} Tile coordinate.
+ * @api
  */
-ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type'];
+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);
+};
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @return {number} Tile resolution.
  */
-ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-    });
+ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) {
+  goog.asserts.assert(
+      this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom);
+  return this.resolutions_[tileCoord[0]];
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @param {number} z Z.
+ * @return {number} Tile size.
+ * @api stable
  */
-ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept'
-    ]);
+ol.tilegrid.TileGrid.prototype.getTileSize = function(z) {
+  if (goog.isDef(this.tileSize_)) {
+    return this.tileSize_;
+  } else {
+    goog.asserts.assert(!goog.isNull(this.tileSizes_));
+    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
+    return this.tileSizes_[z];
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @param {number} resolution Resolution.
+ * @return {number} Z.
  */
-ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
-      'number': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
-          ol.format.GPX.writeWptType_))
-    });
+ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) {
+  return ol.array.linearFindNearest(this.resolutions_, resolution, 0);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection.
  */
-ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg'
-    ]);
+ol.tilegrid.getForProjection = function(projection) {
+  var tileGrid = projection.getDefaultTileGrid();
+  if (goog.isNull(tileGrid)) {
+    tileGrid = ol.tilegrid.createForProjection(projection);
+    projection.setDefaultTileGrid(tileGrid);
+  }
+  return tileGrid;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @param {ol.Extent} extent Extent.
+ * @param {number=} opt_maxZoom Maximum zoom level (default is
+ *     ol.DEFAULT_MAX_ZOOM).
+ * @param {number=} 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.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
-      'number': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
-          ol.format.GPX.writeTrkSeg_))
-    });
-
+ol.tilegrid.createForExtent =
+    function(extent, opt_maxZoom, opt_tileSize, opt_corner) {
+  var tileSize = goog.isDef(opt_tileSize) ?
+      opt_tileSize : ol.DEFAULT_TILE_SIZE;
 
-/**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
- */
-ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');
+  var corner = goog.isDef(opt_corner) ?
+      opt_corner : ol.extent.Corner.BOTTOM_LEFT;
 
+  var resolutions = ol.tilegrid.resolutionsFromExtent(
+      extent, opt_maxZoom, tileSize);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_)
-    });
+  return new ol.tilegrid.TileGrid({
+    origin: ol.extent.getCorner(extent, corner),
+    resolutions: resolutions,
+    tileSize: tileSize
+  });
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * 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=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE).
+ * @return {!Array.<number>} Resolutions array.
  */
-ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src',
-      'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop',
-      'ageofdgpsdata', 'dgpsid'
-    ]);
+ol.tilegrid.resolutionsFromExtent =
+    function(extent, opt_maxZoom, opt_tileSize) {
+  var maxZoom = goog.isDef(opt_maxZoom) ?
+      opt_maxZoom : ol.DEFAULT_MAX_ZOOM;
 
+  var height = ol.extent.getHeight(extent);
+  var width = ol.extent.getWidth(extent);
 
-/**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode),
-      'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'geoidheight': ol.xml.makeChildAppender(
-          ol.format.XSD.writeDecimalTextNode),
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
-      'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'sat': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode),
-      'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'ageofdgpsdata': ol.xml.makeChildAppender(
-          ol.format.XSD.writeDecimalTextNode),
-      'dgpsid': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode)
-    });
+  var tileSize = goog.isDef(opt_tileSize) ?
+      opt_tileSize : ol.DEFAULT_TILE_SIZE;
+  var maxResolution = Math.max(
+      width / tileSize, height / tileSize);
+
+  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;
+};
 
 
 /**
- * @const
- * @type {Object.<string, string>}
- * @private
+ * @param {ol.proj.ProjectionLike} projection Projection.
+ * @param {number=} opt_maxZoom Maximum zoom level (default is
+ *     ol.DEFAULT_MAX_ZOOM).
+ * @param {number=} 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.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = {
-  'Point': 'wpt',
-  'LineString': 'rte',
-  'MultiLineString': 'trk'
+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);
 };
 
 
 /**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
+ * Generate a tile grid extent from a projection.  If the projection has an
+ * extent, it is used.  If not, a global extent is assumed.
+ * @param {ol.proj.ProjectionLike} projection Projection.
+ * @return {ol.Extent} Extent.
  */
-ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
-  goog.asserts.assertInstanceof(value, ol.Feature);
-  var geometry = value.getGeometry();
-  if (goog.isDef(geometry)) {
-    var parentNode = objectStack[objectStack.length - 1].node;
-    goog.asserts.assert(ol.xml.isNode(parentNode));
-    return ol.xml.createElementNS(parentNode.namespaceURI,
-        ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]);
+ol.tilegrid.extentFromProjection = function(projection) {
+  projection = ol.proj.get(projection);
+  var extent = projection.getExtent();
+  if (goog.isNull(extent)) {
+    var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] /
+        projection.getMetersPerUnit();
+    extent = ol.extent.createOrUpdate(-half, -half, half, half);
   }
+  return extent;
 };
 
+goog.provide('ol.source.Tile');
+goog.provide('ol.source.TileOptions');
+
+goog.require('goog.functions');
+goog.require('ol.Attribution');
+goog.require('ol.Extent');
+goog.require('ol.TileRange');
+goog.require('ol.source.Source');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.TileGrid');
+
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
+ *            extent: (ol.Extent|undefined),
+ *            logo: (string|olx.LogoOptions|undefined),
+ *            opaque: (boolean|undefined),
+ *            tilePixelRatio: (number|undefined),
+ *            projection: ol.proj.ProjectionLike,
+ *            state: (ol.source.State|undefined),
+ *            tileGrid: (ol.tilegrid.TileGrid|undefined)}}
  */
-ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_),
-      'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_),
-      'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_)
-    });
+ol.source.TileOptions;
+
 
 
 /**
- * Encode an array of features in the GPX format.
+ * @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.
  *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Node} Result.
- * @api stable
+ * @constructor
+ * @extends {ol.source.Source}
+ * @param {ol.source.TileOptions} options Tile source options.
+ * @api
  */
-ol.format.GPX.prototype.writeFeatures;
+ol.source.Tile = function(options) {
 
+  goog.base(this, {
+    attributions: options.attributions,
+    extent: options.extent,
+    logo: options.logo,
+    projection: options.projection,
+    state: options.state
+  });
 
-/**
- * @inheritDoc
- */
-ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) {
-  //FIXME Serialize metadata
-  var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx');
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.opaque_ = goog.isDef(options.opaque) ? options.opaque : false;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.tilePixelRatio_ = goog.isDef(options.tilePixelRatio) ?
+      options.tilePixelRatio : 1;
+
+  /**
+   * @protected
+   * @type {ol.tilegrid.TileGrid}
+   */
+  this.tileGrid = goog.isDef(options.tileGrid) ? options.tileGrid : null;
 
-  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
-      ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_,
-      ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]);
-  return gpx;
 };
+goog.inherits(ol.source.Tile, ol.source.Source);
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Utilities for string newlines.
- * @author nnaze@google.com (Nathan Naze)
+ * @return {boolean} Can expire cache.
  */
+ol.source.Tile.prototype.canExpireCache = goog.functions.FALSE;
 
 
 /**
- * Namespace for string utilities
+ * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
  */
-goog.provide('goog.string.newlines');
-goog.provide('goog.string.newlines.Line');
-
-goog.require('goog.array');
+ol.source.Tile.prototype.expireCache = goog.abstractMethod;
 
 
 /**
- * Splits a string into lines, properly handling universal newlines.
- * @param {string} str String to split.
- * @param {boolean=} opt_keepNewlines Whether to keep the newlines in the
- *     resulting strings. Defaults to false.
- * @return {!Array.<string>} String split into lines.
+ * Look for loaded tiles over a given tile range and zoom level.  Adds
+ * properties to the provided lookup representing key/tile pairs for already
+ * loaded tiles.
+ *
+ * @param {Object.<number, Object.<string, ol.Tile>>} loadedTilesByZ A lookup of
+ *     loaded tiles by zoom level.
+ * @param {function(number, number, number): ol.Tile} getTileIfLoaded A function
+ *     that returns the tile only if it is fully loaded.
+ * @param {number} z Zoom level.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @return {boolean} The tile range is fully covered with loaded tiles.
  */
-goog.string.newlines.splitLines = function(str, opt_keepNewlines) {
-  var lines = goog.string.newlines.getLines(str);
-  return goog.array.map(lines, function(line) {
-    return opt_keepNewlines ? line.getFullLine() : line.getContent();
-  });
+ol.source.Tile.prototype.findLoadedTiles = function(loadedTilesByZ,
+    getTileIfLoaded, z, tileRange) {
+  // FIXME this could be more efficient about filling partial holes
+  var fullyCovered = true;
+  var tile, tileCoordKey, x, y;
+  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
+    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+      tileCoordKey = this.getKeyZXY(z, x, y);
+      if (loadedTilesByZ[z] && loadedTilesByZ[z][tileCoordKey]) {
+        continue;
+      }
+      tile = getTileIfLoaded(z, x, y);
+      if (!goog.isNull(tile)) {
+        if (!loadedTilesByZ[z]) {
+          loadedTilesByZ[z] = {};
+        }
+        loadedTilesByZ[z][tileCoordKey] = tile;
+      } else {
+        fullyCovered = false;
+      }
+    }
+  }
+  return fullyCovered;
 };
 
 
-
 /**
- * Line metadata class that records the start/end indicies of lines
- * in a string.  Can be used to implement common newline use cases such as
- * splitLines() or determining line/column of an index in a string.
- * Also implements methods to get line contents.
- *
- * Indexes are expressed as string indicies into string.substring(), inclusive
- * at the start, exclusive at the end.
- *
- * Create an array of these with goog.string.newlines.getLines().
- * @param {string} string The original string.
- * @param {number} startLineIndex The index of the start of the line.
- * @param {number} endContentIndex The index of the end of the line, excluding
- *     newlines.
- * @param {number} endLineIndex The index of the end of the line, index
- *     newlines.
- * @constructor
- * @struct
- * @final
+ * @return {number} Gutter.
  */
-goog.string.newlines.Line = function(string, startLineIndex,
-                                     endContentIndex, endLineIndex) {
-  /**
-   * The original string.
-   * @type {string}
-   */
-  this.string = string;
+ol.source.Tile.prototype.getGutter = function() {
+  return 0;
+};
 
-  /**
-   * Index of the start of the line.
-   * @type {number}
-   */
-  this.startLineIndex = startLineIndex;
 
-  /**
-   * Index of the end of the line, excluding any newline characters.
-   * Index is the first character after the line, suitable for
-   * String.substring().
-   * @type {number}
-   */
-  this.endContentIndex = endContentIndex;
+/**
+ * @param {number} z Z.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {string} Key.
+ * @protected
+ */
+ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
 
-  /**
-   * Index of the end of the line, excluding any newline characters.
-   * Index is the first character after the line, suitable for
-   * String.substring().
-   * @type {number}
-   */
 
-  this.endLineIndex = endLineIndex;
+/**
+ * @return {boolean} Opaque.
+ */
+ol.source.Tile.prototype.getOpaque = function() {
+  return this.opaque_;
 };
 
 
 /**
- * @return {string} The content of the line, excluding any newline characters.
+ * @inheritDoc
  */
-goog.string.newlines.Line.prototype.getContent = function() {
-  return this.string.substring(this.startLineIndex, this.endContentIndex);
+ol.source.Tile.prototype.getResolutions = function() {
+  return this.tileGrid.getResolutions();
 };
 
 
 /**
- * @return {string} The full line, including any newline characters.
+ * @param {number} z Tile coordinate z.
+ * @param {number} x Tile coordinate x.
+ * @param {number} y Tile coordinate y.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection=} opt_projection Projection.
+ * @return {!ol.Tile} Tile.
  */
-goog.string.newlines.Line.prototype.getFullLine = function() {
-  return this.string.substring(this.startLineIndex, this.endLineIndex);
-};
+ol.source.Tile.prototype.getTile = goog.abstractMethod;
 
 
 /**
- * @return {string} The newline characters, if any ('\n', \r', '\r\n', '', etc).
+ * @return {ol.tilegrid.TileGrid} Tile grid.
+ * @api stable
  */
-goog.string.newlines.Line.prototype.getNewline = function() {
-  return this.string.substring(this.endContentIndex, this.endLineIndex);
+ol.source.Tile.prototype.getTileGrid = function() {
+  return this.tileGrid;
 };
 
 
 /**
- * Splits a string into an array of line metadata.
- * @param {string} str String to split.
- * @return {!Array.<!goog.string.newlines.Line>} Array of line metadata.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.tilegrid.TileGrid} Tile grid.
  */
-goog.string.newlines.getLines = function(str) {
-  // We use the constructor because literals are evaluated only once in
-  // < ES 3.1.
-  // See http://www.mail-archive.com/es-discuss@mozilla.org/msg01796.html
-  var re = RegExp('\r\n|\r|\n', 'g');
-  var sliceIndex = 0;
-  var result;
-  var lines = [];
-
-  while (result = re.exec(str)) {
-    var line = new goog.string.newlines.Line(
-        str, sliceIndex, result.index, result.index + result[0].length);
-    lines.push(line);
-
-    // remember where to start the slice from
-    sliceIndex = re.lastIndex;
+ol.source.Tile.prototype.getTileGridForProjection = function(projection) {
+  if (goog.isNull(this.tileGrid)) {
+    return ol.tilegrid.getForProjection(projection);
+  } else {
+    return this.tileGrid;
   }
+};
 
-  // If the string does not end with a newline, add the last line.
-  if (sliceIndex < str.length) {
-    var line = new goog.string.newlines.Line(
-        str, sliceIndex, str.length, str.length);
-    lines.push(line);
-  }
 
-  return lines;
+/**
+ * @param {number} z Z.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {number} Tile size.
+ */
+ol.source.Tile.prototype.getTilePixelSize =
+    function(z, pixelRatio, projection) {
+  var tileGrid = this.getTileGridForProjection(projection);
+  return tileGrid.getTileSize(z) * this.tilePixelRatio_;
 };
 
-goog.provide('ol.format.TextFeature');
 
+/**
+ * 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.
+ */
+ol.source.Tile.prototype.useTile = goog.nullFunction;
+
+goog.provide('ol.renderer.Layer');
+
+goog.require('goog.Disposable');
 goog.require('goog.asserts');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
+goog.require('ol.ImageState');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.layer.Layer');
+goog.require('ol.source.Source');
+goog.require('ol.source.State');
+goog.require('ol.source.Tile');
+goog.require('ol.tilecoord');
 
 
 
 /**
- * @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}
+ * @extends {goog.Disposable}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Layer} layer Layer.
+ * @suppress {checkStructDictInheritance}
+ * @struct
  */
-ol.format.TextFeature = function() {
+ol.renderer.Layer = function(mapRenderer, layer) {
+
   goog.base(this);
-};
-goog.inherits(ol.format.TextFeature, ol.format.Feature);
+
+  /**
+   * @private
+   * @type {ol.renderer.Map}
+   */
+  this.mapRenderer_ = mapRenderer;
+
+  /**
+   * @private
+   * @type {ol.layer.Layer}
+   */
+  this.layer_ = layer;
 
 
-/**
- * @param {Document|Node|Object|string} source Source.
- * @private
- * @return {string} Text.
- */
-ol.format.TextFeature.prototype.getText_ = function(source) {
-  if (goog.isString(source)) {
-    return source;
-  } else {
-    goog.asserts.fail();
-    return '';
-  }
 };
+goog.inherits(ol.renderer.Layer, goog.Disposable);
 
 
 /**
- * @inheritDoc
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
+ *     callback.
+ * @param {S} thisArg Value to use as `this` when executing `callback`.
+ * @return {T|undefined} Callback result.
+ * @template S,T
  */
-ol.format.TextFeature.prototype.getType = function() {
-  return ol.format.FormatType.TEXT;
-};
+ol.renderer.Layer.prototype.forEachFeatureAtPixel = goog.nullFunction;
 
 
 /**
- * @inheritDoc
+ * @protected
+ * @return {ol.layer.Layer} Layer.
  */
-ol.format.TextFeature.prototype.readFeature = function(source, opt_options) {
-  return this.readFeatureFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
+ol.renderer.Layer.prototype.getLayer = function() {
+  return this.layer_;
 };
 
 
 /**
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
  * @protected
- * @return {ol.Feature} Feature.
+ * @return {ol.Map} Map.
  */
-ol.format.TextFeature.prototype.readFeatureFromText = goog.abstractMethod;
+ol.renderer.Layer.prototype.getMap = function() {
+  return this.mapRenderer_.getMap();
+};
 
 
 /**
- * @inheritDoc
+ * @protected
+ * @return {ol.renderer.Map} Map renderer.
  */
-ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) {
-  return this.readFeaturesFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
+ol.renderer.Layer.prototype.getMapRenderer = function() {
+  return this.mapRenderer_;
 };
 
 
 /**
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
+ * Handle changes in image state.
+ * @param {goog.events.Event} event Image change event.
  * @protected
- * @return {Array.<ol.Feature>} Features.
  */
-ol.format.TextFeature.prototype.readFeaturesFromText = goog.abstractMethod;
+ol.renderer.Layer.prototype.handleImageChange = function(event) {
+  var image = /** @type {ol.Image} */ (event.target);
+  if (image.getState() === ol.ImageState.LOADED) {
+    this.renderIfReadyAndVisible();
+  }
+};
 
 
 /**
- * @inheritDoc
+ * @protected
  */
-ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) {
-  return this.readGeometryFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
+ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
+  var layer = this.getLayer();
+  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
+    this.getMap().render();
+  }
 };
 
 
 /**
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Tile} tileSource Tile source.
  * @protected
- * @return {ol.geom.Geometry} Geometry.
  */
-ol.format.TextFeature.prototype.readGeometryFromText = goog.abstractMethod;
+ol.renderer.Layer.prototype.scheduleExpireCache =
+    function(frameState, tileSource) {
+  if (tileSource.canExpireCache()) {
+    frameState.postRenderFunctions.push(
+        goog.partial(
+            /**
+             * @param {ol.source.Tile} tileSource Tile source.
+             * @param {ol.Map} map Map.
+             * @param {olx.FrameState} frameState Frame state.
+             */
+            function(tileSource, map, frameState) {
+              var tileSourceKey = goog.getUid(tileSource).toString();
+              tileSource.expireCache(frameState.usedTiles[tileSourceKey]);
+            }, tileSource));
+  }
+};
 
 
 /**
- * @inheritDoc
+ * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
+ *     set (target).
+ * @param {Array.<ol.Attribution>} attributions Attributions (source).
+ * @protected
  */
-ol.format.TextFeature.prototype.readProjection = function(source) {
-  return this.readProjectionFromText(this.getText_(source));
+ol.renderer.Layer.prototype.updateAttributions =
+    function(attributionsSet, attributions) {
+  if (goog.isDefAndNotNull(attributions)) {
+    var attribution, i, ii;
+    for (i = 0, ii = attributions.length; i < ii; ++i) {
+      attribution = attributions[i];
+      attributionsSet[goog.getUid(attribution).toString()] = attribution;
+    }
+  }
 };
 
 
 /**
- * @param {string} text Text.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Source} source Source.
  * @protected
- * @return {ol.proj.Projection} Projection.
  */
-ol.format.TextFeature.prototype.readProjectionFromText = goog.abstractMethod;
+ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
+  var logo = source.getLogo();
+  if (goog.isDef(logo)) {
+    if (goog.isString(logo)) {
+      frameState.logos[logo] = '';
+    } else if (goog.isObject(logo)) {
+      goog.asserts.assertString(logo.href);
+      goog.asserts.assertString(logo.src);
+      frameState.logos[logo.src] = logo.href;
+    }
+  }
+};
 
 
 /**
- * @inheritDoc
+ * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles.
+ * @param {ol.source.Tile} tileSource Tile source.
+ * @param {number} z Z.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @protected
  */
-ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) {
-  return this.writeFeatureText(feature, this.adaptOptions(opt_options));
+ol.renderer.Layer.prototype.updateUsedTiles =
+    function(usedTiles, tileSource, z, tileRange) {
+  // FIXME should we use tilesToDrawByZ instead?
+  var tileSourceKey = goog.getUid(tileSource).toString();
+  var zKey = z.toString();
+  if (tileSourceKey in usedTiles) {
+    if (zKey in usedTiles[tileSourceKey]) {
+      usedTiles[tileSourceKey][zKey].extend(tileRange);
+    } else {
+      usedTiles[tileSourceKey][zKey] = tileRange;
+    }
+  } else {
+    usedTiles[tileSourceKey] = {};
+    usedTiles[tileSourceKey][zKey] = tileRange;
+  }
 };
 
 
 /**
- * @param {ol.Feature} feature Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @param {function(ol.Tile): boolean} isLoadedFunction Function to
+ *     determine if the tile is loaded.
+ * @param {ol.source.Tile} tileSource Tile source.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
  * @protected
- * @return {string} Text.
+ * @return {function(number, number, number): ol.Tile} Returns a tile if it is
+ *     loaded.
  */
-ol.format.TextFeature.prototype.writeFeatureText = goog.abstractMethod;
+ol.renderer.Layer.prototype.createGetTileIfLoadedFunction =
+    function(isLoadedFunction, tileSource, pixelRatio, projection) {
+  return (
+      /**
+       * @param {number} z Z.
+       * @param {number} x X.
+       * @param {number} y Y.
+       * @return {ol.Tile} Tile.
+       */
+      function(z, x, y) {
+        var tile = tileSource.getTile(z, x, y, pixelRatio, projection);
+        return isLoadedFunction(tile) ? tile : null;
+      });
+};
 
 
 /**
- * @inheritDoc
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {ol.Size} size Size.
+ * @protected
+ * @return {ol.Coordinate} Snapped center.
  */
-ol.format.TextFeature.prototype.writeFeatures = function(
-    features, opt_options) {
-  return this.writeFeaturesText(features, this.adaptOptions(opt_options));
+ol.renderer.Layer.prototype.snapCenterToPixel =
+    function(center, resolution, size) {
+  return [
+    resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2),
+    resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2)
+  ];
 };
 
 
 /**
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
+ * 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|undefined} 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
- * @return {string} Text.
+ * @template T
  */
-ol.format.TextFeature.prototype.writeFeaturesText = goog.abstractMethod;
+ol.renderer.Layer.prototype.manageTilePyramid = function(
+    frameState, tileSource, tileGrid, pixelRatio, projection, extent,
+    currentZ, preload, opt_tileCallback, opt_this) {
+  var tileSourceKey = goog.getUid(tileSource).toString();
+  if (!(tileSourceKey in frameState.wantedTiles)) {
+    frameState.wantedTiles[tileSourceKey] = {};
+  }
+  var wantedTiles = frameState.wantedTiles[tileSourceKey];
+  var tileQueue = frameState.tileQueue;
+  var minZoom = tileGrid.getMinZoom();
+  var tile, tileRange, tileResolution, x, y, z;
+  if (!goog.isDef(preload)) {
+    preload = 0;
+  }
+  for (z = currentZ; z >= minZoom; --z) {
+    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
+    tileResolution = tileGrid.getResolution(z);
+    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
+      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+        if (currentZ - z <= preload) {
+          tile = tileSource.getTile(z, x, y, pixelRatio, projection);
+          if (tile.getState() == ol.TileState.IDLE) {
+            wantedTiles[ol.tilecoord.toString(tile.tileCoord)] = true;
+            if (!tileQueue.isKeyQueued(tile.getKey())) {
+              tileQueue.enqueue([tile, tileSourceKey,
+                tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
+            }
+          }
+          if (goog.isDef(opt_tileCallback)) {
+            opt_tileCallback.call(opt_this, tile);
+          }
+        } else {
+          tileSource.useTile(z, x, y);
+        }
+      }
+    }
+  }
+};
+
+goog.provide('ol.style.Image');
+goog.provide('ol.style.ImageState');
 
 
 /**
- * @inheritDoc
+ * @enum {number}
  */
-ol.format.TextFeature.prototype.writeGeometry = function(
-    geometry, opt_options) {
-  return this.writeGeometryText(geometry, this.adaptOptions(opt_options));
+ol.style.ImageState = {
+  IDLE: 0,
+  LOADING: 1,
+  LOADED: 2,
+  ERROR: 3
 };
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {string} Text.
+ * @typedef {{opacity: number,
+ *            rotateWithView: boolean,
+ *            rotation: number,
+ *            scale: number,
+ *            snapToPixel: boolean}}
  */
-ol.format.TextFeature.prototype.writeGeometryText = goog.abstractMethod;
-
-goog.provide('ol.format.IGC');
-goog.provide('ol.format.IGCZ');
-
-goog.require('goog.asserts');
-goog.require('goog.string');
-goog.require('goog.string.newlines');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.TextFeature');
-goog.require('ol.geom.LineString');
-goog.require('ol.proj');
-
-
-/**
- * IGC altitude/z. One of 'barometric', 'gps', 'none'.
- * @enum {string}
- * @api
- */
-ol.format.IGCZ = {
-  BAROMETRIC: 'barometric',
-  GPS: 'gps',
-  NONE: 'none'
-};
+ol.style.ImageOptions;
 
 
 
 /**
  * @classdesc
- * Feature format for `*.igc` flight recording files.
+ * Abstract base class; used for creating subclasses and not instantiated in
+ * apps. Base class for {@link ol.style.Icon} and {@link ol.style.Circle}.
  *
  * @constructor
- * @extends {ol.format.TextFeature}
- * @param {olx.format.IGCOptions=} opt_options Options.
+ * @param {ol.style.ImageOptions} options Options.
  * @api
  */
-ol.format.IGC = function(opt_options) {
+ol.style.Image = function(options) {
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.opacity_ = options.opacity;
 
-  goog.base(this);
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.rotateWithView_ = options.rotateWithView;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {number}
    */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+  this.rotation_ = options.rotation;
 
   /**
    * @private
-   * @type {ol.format.IGCZ}
+   * @type {number}
    */
-  this.altitudeMode_ = goog.isDef(options.altitudeMode) ?
-      options.altitudeMode : ol.format.IGCZ.NONE;
+  this.scale_ = options.scale;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.snapToPixel_ = options.snapToPixel;
 
 };
-goog.inherits(ol.format.IGC, ol.format.TextFeature);
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * @return {number} Opacity.
+ * @api
  */
-ol.format.IGC.EXTENSIONS_ = ['.igc'];
+ol.style.Image.prototype.getOpacity = function() {
+  return this.opacity_;
+};
 
 
 /**
- * @const
- * @type {RegExp}
- * @private
+ * @return {boolean} Rotate with map.
+ * @api
  */
-ol.format.IGC.B_RECORD_RE_ =
-    /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/;
+ol.style.Image.prototype.getRotateWithView = function() {
+  return this.rotateWithView_;
+};
 
 
 /**
- * @const
- * @type {RegExp}
- * @private
+ * @return {number} Rotation.
+ * @api
  */
-ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;
+ol.style.Image.prototype.getRotation = function() {
+  return this.rotation_;
+};
 
 
 /**
- * @const
- * @type {RegExp}
- * @private
+ * @return {number} Scale.
+ * @api
  */
-ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/;
+ol.style.Image.prototype.getScale = function() {
+  return this.scale_;
+};
 
 
 /**
- * @inheritDoc
+ * @return {boolean} Snap to pixel?
+ * @api
  */
-ol.format.IGC.prototype.getExtensions = function() {
-  return ol.format.IGC.EXTENSIONS_;
+ol.style.Image.prototype.getSnapToPixel = function() {
+  return this.snapToPixel_;
 };
 
 
 /**
- * Read the feature from the IGC source.
- *
  * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api
+ * @return {Array.<number>} Anchor.
  */
-ol.format.IGC.prototype.readFeature;
+ol.style.Image.prototype.getAnchor = goog.abstractMethod;
 
 
 /**
- * @inheritDoc
+ * @function
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
+ * @api
  */
-ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) {
-  var altitudeMode = this.altitudeMode_;
-  var lines = goog.string.newlines.splitLines(text);
-  /** @type {Object.<string, string>} */
-  var properties = {};
-  var flatCoordinates = [];
-  var year = 2000;
-  var month = 0;
-  var day = 1;
-  var i, ii;
-  for (i = 0, ii = lines.length; i < ii; ++i) {
-    var line = lines[i];
-    var m;
-    if (line.charAt(0) == 'B') {
-      m = ol.format.IGC.B_RECORD_RE_.exec(line);
-      if (m) {
-        var hour = parseInt(m[1], 10);
-        var minute = parseInt(m[2], 10);
-        var second = parseInt(m[3], 10);
-        var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000;
-        if (m[6] == 'S') {
-          y = -y;
-        }
-        var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000;
-        if (m[9] == 'W') {
-          x = -x;
-        }
-        flatCoordinates.push(x, y);
-        if (altitudeMode != ol.format.IGCZ.NONE) {
-          var z;
-          if (altitudeMode == ol.format.IGCZ.GPS) {
-            z = parseInt(m[11], 10);
-          } else if (altitudeMode == ol.format.IGCZ.BAROMETRIC) {
-            z = parseInt(m[12], 10);
-          } else {
-            goog.asserts.fail();
-            z = 0;
-          }
-          flatCoordinates.push(z);
-        }
-        var dateTime = Date.UTC(year, month, day, hour, minute, second);
-        flatCoordinates.push(dateTime / 1000);
-      }
-    } else if (line.charAt(0) == 'H') {
-      m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line);
-      if (m) {
-        day = parseInt(m[1], 10);
-        month = parseInt(m[2], 10) - 1;
-        year = 2000 + parseInt(m[3], 10);
-      } else {
-        m = ol.format.IGC.H_RECORD_RE_.exec(line);
-        if (m) {
-          properties[m[1]] = goog.string.trim(m[2]);
-          m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line);
-        }
-      }
-    }
-  }
-  if (flatCoordinates.length === 0) {
-    return null;
-  }
-  var lineString = new ol.geom.LineString(null);
-  var layout = altitudeMode == ol.format.IGCZ.NONE ?
-      ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM;
-  lineString.setFlatCoordinates(layout, flatCoordinates);
-  var feature = new ol.Feature(ol.format.Feature.transformWithOptions(
-      lineString, false, opt_options));
-  feature.setProperties(properties);
-  return feature;
-};
+ol.style.Image.prototype.getImage = goog.abstractMethod;
 
 
 /**
- * Read the feature from the source. As IGC sources contain a single
- * feature, this will return the feature in an array.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
  */
-ol.format.IGC.prototype.readFeatures;
+ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;
 
 
 /**
- * @inheritDoc
+ * @return {ol.style.ImageState} Image state.
  */
-ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) {
-  var feature = this.readFeatureFromText(text, opt_options);
-  if (!goog.isNull(feature)) {
-    return [feature];
-  } else {
-    return [];
-  }
-};
+ol.style.Image.prototype.getImageState = goog.abstractMethod;
 
 
 /**
- * Read the projection from the IGC source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api
+ * @return {ol.Size} Image size.
  */
-ol.format.IGC.prototype.readProjection;
+ol.style.Image.prototype.getImageSize = goog.abstractMethod;
 
 
 /**
- * @inheritDoc
+ * @return {ol.Size} Size of the hit-detection image.
  */
-ol.format.IGC.prototype.readProjectionFromText = function(text) {
-  return this.defaultDataProjection;
-};
+ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod;
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Generics method for collection-like classes and objects.
- *
- * @author arv@google.com (Erik Arvidsson)
- *
- * This file contains functions to work with collections. It supports using
- * Map, Set, Array and Object and other classes that implement collection-like
- * methods.
+ * @function
+ * @return {Array.<number>} Origin.
  */
+ol.style.Image.prototype.getOrigin = goog.abstractMethod;
 
 
-goog.provide('goog.structs');
-
-goog.require('goog.array');
-goog.require('goog.object');
+/**
+ * @function
+ * @return {Array.<number>} Origin for the hit-detection image.
+ */
+ol.style.Image.prototype.getHitDetectionOrigin = goog.abstractMethod;
 
 
-// We treat an object as a dictionary if it has getKeys or it is an object that
-// isn't arrayLike.
+/**
+ * @function
+ * @return {ol.Size} Size.
+ */
+ol.style.Image.prototype.getSize = goog.abstractMethod;
 
 
 /**
- * Returns the number of values in the collection-like object.
- * @param {Object} col The collection-like object.
- * @return {number} The number of values in the collection-like object.
+ * Set the opacity.
+ *
+ * @param {number} opacity Opacity.
  */
-goog.structs.getCount = function(col) {
-  if (typeof col.getCount == 'function') {
-    return col.getCount();
-  }
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    return col.length;
-  }
-  return goog.object.getCount(col);
+ol.style.Image.prototype.setOpacity = function(opacity) {
+  this.opacity_ = opacity;
 };
 
 
 /**
- * Returns the values of the collection-like object.
- * @param {Object} col The collection-like object.
- * @return {!Array} The values in the collection-like object.
+ * Set whether to rotate the style with the view.
+ *
+ * @param {boolean} rotateWithView Rotate with map.
  */
-goog.structs.getValues = function(col) {
-  if (typeof col.getValues == 'function') {
-    return col.getValues();
-  }
-  if (goog.isString(col)) {
-    return col.split('');
-  }
-  if (goog.isArrayLike(col)) {
-    var rv = [];
-    var l = col.length;
-    for (var i = 0; i < l; i++) {
-      rv.push(col[i]);
-    }
-    return rv;
-  }
-  return goog.object.getValues(col);
+ol.style.Image.prototype.setRotateWithView = function(rotateWithView) {
+  this.rotateWithView_ = rotateWithView;
 };
 
 
 /**
- * Returns the keys of the collection. Some collections have no notion of
- * keys/indexes and this function will return undefined in those cases.
- * @param {Object} col The collection-like object.
- * @return {!Array|undefined} The keys in the collection.
+ * Set the rotation.
+ *
+ * @param {number} rotation Rotation.
+ * @api
  */
-goog.structs.getKeys = function(col) {
-  if (typeof col.getKeys == 'function') {
-    return col.getKeys();
-  }
-  // if we have getValues but no getKeys we know this is a key-less collection
-  if (typeof col.getValues == 'function') {
-    return undefined;
-  }
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    var rv = [];
-    var l = col.length;
-    for (var i = 0; i < l; i++) {
-      rv.push(i);
-    }
-    return rv;
-  }
-
-  return goog.object.getKeys(col);
+ol.style.Image.prototype.setRotation = function(rotation) {
+  this.rotation_ = rotation;
 };
 
 
 /**
- * Whether the collection contains the given value. This is O(n) and uses
- * equals (==) to test the existence.
- * @param {Object} col The collection-like object.
- * @param {*} val The value to check for.
- * @return {boolean} True if the map contains the value.
+ * Set the scale.
+ *
+ * @param {number} scale Scale.
+ * @api
  */
-goog.structs.contains = function(col, val) {
-  if (typeof col.contains == 'function') {
-    return col.contains(val);
-  }
-  if (typeof col.containsValue == 'function') {
-    return col.containsValue(val);
-  }
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    return goog.array.contains(/** @type {Array} */ (col), val);
-  }
-  return goog.object.containsValue(col, val);
+ol.style.Image.prototype.setScale = function(scale) {
+  this.scale_ = scale;
 };
 
 
 /**
- * Whether the collection is empty.
- * @param {Object} col The collection-like object.
- * @return {boolean} True if empty.
+ * Set whether to snap the image to the closest pixel.
+ *
+ * @param {boolean} snapToPixel Snap to pixel?
  */
-goog.structs.isEmpty = function(col) {
-  if (typeof col.isEmpty == 'function') {
-    return col.isEmpty();
-  }
-
-  // We do not use goog.string.isEmpty because here we treat the string as
-  // collection and as such even whitespace matters
-
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    return goog.array.isEmpty(/** @type {Array} */ (col));
-  }
-  return goog.object.isEmpty(col);
+ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) {
+  this.snapToPixel_ = snapToPixel;
 };
 
 
 /**
- * Removes all the elements from the collection.
- * @param {Object} col The collection-like object.
+ * @param {function(this: T, goog.events.Event)} listener Listener function.
+ * @param {T} thisArg Value to use as `this` when executing `listener`.
+ * @return {goog.events.Key|undefined} Listener key.
+ * @template T
  */
-goog.structs.clear = function(col) {
-  // NOTE(arv): This should not contain strings because strings are immutable
-  if (typeof col.clear == 'function') {
-    col.clear();
-  } else if (goog.isArrayLike(col)) {
-    goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
-  } else {
-    goog.object.clear(col);
-  }
-};
+ol.style.Image.prototype.listenImageChange = goog.abstractMethod;
 
 
 /**
- * Calls a function for each value in a collection. The function takes
- * three arguments; the value, the key and the collection.
- *
- * NOTE: This will be deprecated soon! Please use a more specific method if
- * possible, e.g. goog.array.forEach, goog.object.forEach, etc.
- *
- * @param {S} col The collection-like object.
- * @param {function(this:T,?,?,S):?} f The function to call for every value.
- *     This function takes
- *     3 arguments (the value, the key or undefined if the collection has no
- *     notion of keys, and the collection) and the return value is irrelevant.
- * @param {T=} opt_obj The object to be used as the value of 'this'
- *     within {@code f}.
- * @template T,S
+ * Load not yet loaded URI.
  */
-goog.structs.forEach = function(col, f, opt_obj) {
-  if (typeof col.forEach == 'function') {
-    col.forEach(f, opt_obj);
-  } else if (goog.isArrayLike(col) || goog.isString(col)) {
-    goog.array.forEach(/** @type {Array} */ (col), f, opt_obj);
-  } else {
-    var keys = goog.structs.getKeys(col);
-    var values = goog.structs.getValues(col);
-    var l = values.length;
-    for (var i = 0; i < l; i++) {
-      f.call(opt_obj, values[i], keys && keys[i], col);
-    }
-  }
-};
+ol.style.Image.prototype.load = goog.abstractMethod;
 
 
 /**
- * Calls a function for every value in the collection. When a call returns true,
- * adds the value to a new collection (Array is returned by default).
- *
- * @param {S} col The collection-like object.
- * @param {function(this:T,?,?,S):boolean} f The function to call for every
- *     value. This function takes
- *     3 arguments (the value, the key or undefined if the collection has no
- *     notion of keys, and the collection) and should return a Boolean. If the
- *     return value is true the value is added to the result collection. If it
- *     is false the value is not included.
- * @param {T=} opt_obj The object to be used as the value of 'this'
- *     within {@code f}.
- * @return {!Object|!Array} A new collection where the passed values are
- *     present. If col is a key-less collection an array is returned.  If col
- *     has keys and values a plain old JS object is returned.
- * @template T,S
+ * @param {function(this: T, goog.events.Event)} listener Listener function.
+ * @param {T} thisArg Value to use as `this` when executing `listener`.
+ * @template T
  */
-goog.structs.filter = function(col, f, opt_obj) {
-  if (typeof col.filter == 'function') {
-    return col.filter(f, opt_obj);
-  }
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj);
-  }
+ol.style.Image.prototype.unlistenImageChange = goog.abstractMethod;
 
-  var rv;
-  var keys = goog.structs.getKeys(col);
-  var values = goog.structs.getValues(col);
-  var l = values.length;
-  if (keys) {
-    rv = {};
-    for (var i = 0; i < l; i++) {
-      if (f.call(opt_obj, values[i], keys[i], col)) {
-        rv[keys[i]] = values[i];
-      }
-    }
-  } else {
-    // We should not use goog.array.filter here since we want to make sure that
-    // the index is undefined as well as make sure that col is passed to the
-    // function.
-    rv = [];
-    for (var i = 0; i < l; i++) {
-      if (f.call(opt_obj, values[i], undefined, col)) {
-        rv.push(values[i]);
-      }
-    }
-  }
-  return rv;
-};
+goog.provide('ol.style.Icon');
+goog.provide('ol.style.IconAnchorUnits');
+goog.provide('ol.style.IconImageCache');
+goog.provide('ol.style.IconOrigin');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('ol.dom');
+goog.require('ol.style.Image');
+goog.require('ol.style.ImageState');
 
 
 /**
- * Calls a function for every value in the collection and adds the result into a
- * new collection (defaults to creating a new Array).
- *
- * @param {S} col The collection-like object.
- * @param {function(this:T,?,?,S):V} f The function to call for every value.
- *     This function takes 3 arguments (the value, the key or undefined if the
- *     collection has no notion of keys, and the collection) and should return
- *     something. The result will be used as the value in the new collection.
- * @param {T=} opt_obj  The object to be used as the value of 'this'
- *     within {@code f}.
- * @return {!Object.<V>|!Array.<V>} A new collection with the new values.  If
- *     col is a key-less collection an array is returned.  If col has keys and
- *     values a plain old JS object is returned.
- * @template T,S,V
+ * Icon anchor units. One of 'fraction', 'pixels'.
+ * @enum {string}
+ * @api
  */
-goog.structs.map = function(col, f, opt_obj) {
-  if (typeof col.map == 'function') {
-    return col.map(f, opt_obj);
-  }
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    return goog.array.map(/** @type {!Array} */ (col), f, opt_obj);
-  }
+ol.style.IconAnchorUnits = {
+  FRACTION: 'fraction',
+  PIXELS: 'pixels'
+};
 
-  var rv;
-  var keys = goog.structs.getKeys(col);
-  var values = goog.structs.getValues(col);
-  var l = values.length;
-  if (keys) {
-    rv = {};
-    for (var i = 0; i < l; i++) {
-      rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
-    }
-  } else {
-    // We should not use goog.array.map here since we want to make sure that
-    // the index is undefined as well as make sure that col is passed to the
-    // function.
-    rv = [];
-    for (var i = 0; i < l; i++) {
-      rv[i] = f.call(opt_obj, values[i], undefined, col);
-    }
-  }
-  return rv;
+
+/**
+ * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'.
+ * @enum {string}
+ * @api
+ */
+ol.style.IconOrigin = {
+  BOTTOM_LEFT: 'bottom-left',
+  BOTTOM_RIGHT: 'bottom-right',
+  TOP_LEFT: 'top-left',
+  TOP_RIGHT: 'top-right'
 };
 
 
+
 /**
- * Calls f for each value in a collection. If any call returns true this returns
- * true (without checking the rest). If all returns false this returns false.
+ * @classdesc
+ * Set icon style for vector features.
  *
- * @param {S} col The collection-like object.
- * @param {function(this:T,?,?,S):boolean} f The function to call for every
- *     value. This function takes 3 arguments (the value, the key or undefined
- *     if the collection has no notion of keys, and the collection) and should
- *     return a boolean.
- * @param {T=} opt_obj  The object to be used as the value of 'this'
- *     within {@code f}.
- * @return {boolean} True if any value passes the test.
- * @template T,S
+ * @constructor
+ * @param {olx.style.IconOptions=} opt_options Options.
+ * @extends {ol.style.Image}
+ * @api
  */
-goog.structs.some = function(col, f, opt_obj) {
-  if (typeof col.some == 'function') {
-    return col.some(f, opt_obj);
-  }
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    return goog.array.some(/** @type {!Array} */ (col), f, opt_obj);
-  }
-  var keys = goog.structs.getKeys(col);
-  var values = goog.structs.getValues(col);
-  var l = values.length;
-  for (var i = 0; i < l; i++) {
-    if (f.call(opt_obj, values[i], keys && keys[i], col)) {
-      return true;
-    }
-  }
-  return false;
-};
+ol.style.Icon = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * Calls f for each value in a collection. If all calls return true this return
- * true this returns true. If any returns false this returns false at this point
- *  and does not continue to check the remaining values.
- *
- * @param {S} col The collection-like object.
- * @param {function(this:T,?,?,S):boolean} f The function to call for every
- *     value. This function takes 3 arguments (the value, the key or
- *     undefined if the collection has no notion of keys, and the collection)
- *     and should return a boolean.
- * @param {T=} opt_obj  The object to be used as the value of 'this'
- *     within {@code f}.
- * @return {boolean} True if all key-value pairs pass the test.
- * @template T,S
- */
-goog.structs.every = function(col, f, opt_obj) {
-  if (typeof col.every == 'function') {
-    return col.every(f, opt_obj);
-  }
-  if (goog.isArrayLike(col) || goog.isString(col)) {
-    return goog.array.every(/** @type {!Array} */ (col), f, opt_obj);
-  }
-  var keys = goog.structs.getKeys(col);
-  var values = goog.structs.getValues(col);
-  var l = values.length;
-  for (var i = 0; i < l; i++) {
-    if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
-      return false;
-    }
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.anchor_ = goog.isDef(options.anchor) ? options.anchor : [0.5, 0.5];
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.normalizedAnchor_ = null;
+
+  /**
+   * @private
+   * @type {ol.style.IconOrigin}
+   */
+  this.anchorOrigin_ = goog.isDef(options.anchorOrigin) ?
+      options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;
+
+  /**
+   * @private
+   * @type {ol.style.IconAnchorUnits}
+   */
+  this.anchorXUnits_ = goog.isDef(options.anchorXUnits) ?
+      options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;
+
+  /**
+   * @private
+   * @type {ol.style.IconAnchorUnits}
+   */
+  this.anchorYUnits_ = goog.isDef(options.anchorYUnits) ?
+      options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;
+
+  /**
+   * @type {?string}
+   */
+  var crossOrigin =
+      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
+
+  /**
+   * @type {Image}
+   */
+  var image = goog.isDef(options.img) ? options.img : null;
+
+  /**
+   * @type {string|undefined}
+   */
+  var src = options.src;
+
+  if ((!goog.isDef(src) || src.length === 0) && !goog.isNull(image)) {
+    src = image.src;
   }
-  return true;
-};
+  goog.asserts.assert(goog.isDef(src) && src.length > 0);
 
-// Copyright 2007 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+  /**
+   * @type {ol.style.ImageState}
+   */
+  var imageState = goog.isDef(options.src) ?
+      ol.style.ImageState.IDLE : ol.style.ImageState.LOADED;
 
-/**
- * @fileoverview Python style iteration utilities.
- * @author arv@google.com (Erik Arvidsson)
- */
+  /**
+   * @private
+   * @type {ol.style.IconImage_}
+   */
+  this.iconImage_ = ol.style.IconImage_.get(
+      image, src, crossOrigin, imageState);
 
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.offset_ = goog.isDef(options.offset) ? options.offset : [0, 0];
 
-goog.provide('goog.iter');
-goog.provide('goog.iter.Iterable');
-goog.provide('goog.iter.Iterator');
-goog.provide('goog.iter.StopIteration');
+  /**
+   * @private
+   * @type {ol.style.IconOrigin}
+   */
+  this.offsetOrigin_ = goog.isDef(options.offsetOrigin) ?
+      options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT;
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.functions');
-goog.require('goog.math');
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.origin_ = null;
 
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.size_ = goog.isDef(options.size) ? options.size : null;
 
-/**
- * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
- */
-goog.iter.Iterable;
+  /**
+   * @type {number}
+   */
+  var opacity = goog.isDef(options.opacity) ? options.opacity : 1;
 
+  /**
+   * @type {boolean}
+   */
+  var rotateWithView = goog.isDef(options.rotateWithView) ?
+      options.rotateWithView : false;
 
-// For script engines that already support iterators.
-if ('StopIteration' in goog.global) {
   /**
-   * Singleton Error object that is used to terminate iterations.
-   * @type {Error}
+   * @type {number}
    */
-  goog.iter.StopIteration = goog.global['StopIteration'];
-} else {
+  var rotation = goog.isDef(options.rotation) ? options.rotation : 0;
+
   /**
-   * Singleton Error object that is used to terminate iterations.
-   * @type {Error}
-   * @suppress {duplicate}
+   * @type {number}
    */
-  goog.iter.StopIteration = Error('StopIteration');
-}
+  var scale = goog.isDef(options.scale) ? options.scale : 1;
 
+  /**
+   * @type {boolean}
+   */
+  var snapToPixel = goog.isDef(options.snapToPixel) ?
+      options.snapToPixel : true;
 
+  goog.base(this, {
+    opacity: opacity,
+    rotation: rotation,
+    scale: scale,
+    snapToPixel: snapToPixel,
+    rotateWithView: rotateWithView
+  });
 
-/**
- * Class/interface for iterators.  An iterator needs to implement a {@code next}
- * method and it needs to throw a {@code goog.iter.StopIteration} when the
- * iteration passes beyond the end.  Iterators have no {@code hasNext} method.
- * It is recommended to always use the helper functions to iterate over the
- * iterator or in case you are only targeting JavaScript 1.7 for in loops.
- * @constructor
- * @template VALUE
- */
-goog.iter.Iterator = function() {};
+};
+goog.inherits(ol.style.Icon, ol.style.Image);
 
 
 /**
- * Returns the next value of the iteration.  This will throw the object
- * {@see goog.iter#StopIteration} when the iteration passes the end.
- * @return {VALUE} Any object or value.
+ * @inheritDoc
+ * @api
  */
-goog.iter.Iterator.prototype.next = function() {
-  throw goog.iter.StopIteration;
+ol.style.Icon.prototype.getAnchor = function() {
+  if (!goog.isNull(this.normalizedAnchor_)) {
+    return this.normalizedAnchor_;
+  }
+  var anchor = this.anchor_;
+  var size = this.getSize();
+  if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION ||
+      this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
+    if (goog.isNull(size)) {
+      return null;
+    }
+    anchor = this.anchor_.slice();
+    if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION) {
+      anchor[0] *= size[0];
+    }
+    if (this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
+      anchor[1] *= size[1];
+    }
+  }
+
+  if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
+    if (goog.isNull(size)) {
+      return null;
+    }
+    if (anchor === this.anchor_) {
+      anchor = this.anchor_.slice();
+    }
+    if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
+        this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
+      anchor[0] = -anchor[0] + size[0];
+    }
+    if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
+        this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
+      anchor[1] = -anchor[1] + size[1];
+    }
+  }
+  this.normalizedAnchor_ = anchor;
+  return this.normalizedAnchor_;
 };
 
 
 /**
- * Returns the {@code Iterator} object itself.  This is used to implement
- * the iterator protocol in JavaScript 1.7
- * @param {boolean=} opt_keys  Whether to return the keys or values. Default is
- *     to only return the values.  This is being used by the for-in loop (true)
- *     and the for-each-in loop (false).  Even though the param gives a hint
- *     about what the iterator will return there is no guarantee that it will
- *     return the keys when true is passed.
- * @return {!goog.iter.Iterator.<VALUE>} The object itself.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image} Image element.
+ * @api
  */
-goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
-  return this;
+ol.style.Icon.prototype.getImage = function(pixelRatio) {
+  return this.iconImage_.getImage(pixelRatio);
 };
 
 
 /**
- * Returns an iterator that knows how to iterate over the values in the object.
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable  If the
- *     object is an iterator it will be returned as is.  If the object has an
- *     {@code __iterator__} method that will be called to get the value
- *     iterator.  If the object is an array-like object we create an iterator
- *     for that.
- * @return {!goog.iter.Iterator.<VALUE>} An iterator that knows how to iterate
- *     over the values in {@code iterable}.
- * @template VALUE
+ * Real Image size used.
+ * @return {ol.Size} Size.
  */
-goog.iter.toIterator = function(iterable) {
-  if (iterable instanceof goog.iter.Iterator) {
-    return iterable;
-  }
-  if (typeof iterable.__iterator__ == 'function') {
-    return iterable.__iterator__(false);
-  }
-  if (goog.isArrayLike(iterable)) {
-    var i = 0;
-    var newIter = new goog.iter.Iterator;
-    newIter.next = function() {
-      while (true) {
-        if (i >= iterable.length) {
-          throw goog.iter.StopIteration;
-        }
-        // Don't include deleted elements.
-        if (!(i in iterable)) {
-          i++;
-          continue;
-        }
-        return iterable[i++];
-      }
-    };
-    return newIter;
-  }
-
-
-  // TODO(arv): Should we fall back on goog.structs.getValues()?
-  throw Error('Not implemented');
+ol.style.Icon.prototype.getImageSize = function() {
+  return this.iconImage_.getSize();
 };
 
 
 /**
- * Calls a function for each element in the iterator with the element of the
- * iterator passed as argument.
- *
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable  The iterator
- *     to iterate over. If the iterable is an object {@code toIterator} will be
- *     called on it.
- * @param {function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>)|
- *         function(this:THIS,number,undefined,goog.iter.Iterator.<VALUE>)} f
- *     The function to call for every element.  This function takes 3 arguments
- *     (the element, undefined, and the iterator) and the return value is
- *     irrelevant.  The reason for passing undefined as the second argument is
- *     so that the same function can be used in {@see goog.array#forEach} as
- *     well as others.
- * @param {THIS=} opt_obj  The object to be used as the value of 'this' within
- *     {@code f}.
- * @template THIS, VALUE
+ * @inheritDoc
  */
-goog.iter.forEach = function(iterable, f, opt_obj) {
-  if (goog.isArrayLike(iterable)) {
-    /** @preserveTry */
-    try {
-      // NOTES: this passes the index number to the second parameter
-      // of the callback contrary to the documentation above.
-      goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
-                         opt_obj);
-    } catch (ex) {
-      if (ex !== goog.iter.StopIteration) {
-        throw ex;
-      }
-    }
-  } else {
-    iterable = goog.iter.toIterator(iterable);
-    /** @preserveTry */
-    try {
-      while (true) {
-        f.call(opt_obj, iterable.next(), undefined, iterable);
-      }
-    } catch (ex) {
-      if (ex !== goog.iter.StopIteration) {
-        throw ex;
-      }
-    }
-  }
+ol.style.Icon.prototype.getHitDetectionImageSize = function() {
+  return this.getImageSize();
 };
 
 
 /**
- * Calls a function for every element in the iterator, and if the function
- * returns true adds the element to a new iterator.
- *
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     to iterate over.
- * @param {
- *     function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
- *     The function to call for every element. This function takes 3 arguments
- *     (the element, undefined, and the iterator) and should return a boolean.
- *     If the return value is true the element will be included in the returned
- *     iterator.  If it is false the element is not included.
- * @param {THIS=} opt_obj The object to be used as the value of 'this' within
- *     {@code f}.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator in which only elements
- *     that passed the test are present.
- * @template THIS, VALUE
+ * @inheritDoc
  */
-goog.iter.filter = function(iterable, f, opt_obj) {
-  var iterator = goog.iter.toIterator(iterable);
-  var newIter = new goog.iter.Iterator;
-  newIter.next = function() {
-    while (true) {
-      var val = iterator.next();
-      if (f.call(opt_obj, val, undefined, iterator)) {
-        return val;
-      }
-    }
-  };
-  return newIter;
+ol.style.Icon.prototype.getImageState = function() {
+  return this.iconImage_.getImageState();
 };
 
 
 /**
- * Calls a function for every element in the iterator, and if the function
- * returns false adds the element to a new iterator.
- *
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     to iterate over.
- * @param {
- *     function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
- *     The function to call for every element. This function takes 3 arguments
- *     (the element, undefined, and the iterator) and should return a boolean.
- *     If the return value is false the element will be included in the returned
- *     iterator.  If it is true the element is not included.
- * @param {THIS=} opt_obj The object to be used as the value of 'this' within
- *     {@code f}.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator in which only elements
- *     that did not pass the test are present.
- * @template THIS, VALUE
+ * @inheritDoc
  */
-goog.iter.filterFalse = function(iterable, f, opt_obj) {
-  return goog.iter.filter(iterable, goog.functions.not(f), opt_obj);
+ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) {
+  return this.iconImage_.getHitDetectionImage(pixelRatio);
 };
 
 
 /**
- * Creates a new iterator that returns the values in a range.  This function
- * can take 1, 2 or 3 arguments:
- * <pre>
- * range(5) same as range(0, 5, 1)
- * range(2, 5) same as range(2, 5, 1)
- * </pre>
- *
- * @param {number} startOrStop  The stop value if only one argument is provided.
- *     The start value if 2 or more arguments are provided.  If only one
- *     argument is used the start value is 0.
- * @param {number=} opt_stop  The stop value.  If left out then the first
- *     argument is used as the stop value.
- * @param {number=} opt_step  The number to increment with between each call to
- *     next.  This can be negative.
- * @return {!goog.iter.Iterator.<number>} A new iterator that returns the values
- *     in the range.
+ * @inheritDoc
+ * @api
  */
-goog.iter.range = function(startOrStop, opt_stop, opt_step) {
-  var start = 0;
-  var stop = startOrStop;
-  var step = opt_step || 1;
-  if (arguments.length > 1) {
-    start = startOrStop;
-    stop = opt_stop;
-  }
-  if (step == 0) {
-    throw Error('Range step argument must not be zero');
+ol.style.Icon.prototype.getOrigin = function() {
+  if (!goog.isNull(this.origin_)) {
+    return this.origin_;
   }
+  var offset = this.offset_;
 
-  var newIter = new goog.iter.Iterator;
-  newIter.next = function() {
-    if (step > 0 && start >= stop || step < 0 && start <= stop) {
-      throw goog.iter.StopIteration;
+  if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
+    var size = this.getSize();
+    var iconImageSize = this.iconImage_.getSize();
+    if (goog.isNull(size) || goog.isNull(iconImageSize)) {
+      return null;
     }
-    var rv = start;
-    start += step;
-    return rv;
-  };
-  return newIter;
+    offset = offset.slice();
+    if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
+        this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
+      offset[0] = iconImageSize[0] - size[0] - offset[0];
+    }
+    if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
+        this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
+      offset[1] = iconImageSize[1] - size[1] - offset[1];
+    }
+  }
+  this.origin_ = offset;
+  return this.origin_;
 };
 
 
 /**
- * Joins the values in a iterator with a delimiter.
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     to get the values from.
- * @param {string} deliminator  The text to put between the values.
- * @return {string} The joined value string.
- * @template VALUE
+ * @inheritDoc
  */
-goog.iter.join = function(iterable, deliminator) {
-  return goog.iter.toArray(iterable).join(deliminator);
+ol.style.Icon.prototype.getHitDetectionOrigin = function() {
+  return this.getOrigin();
 };
 
 
 /**
- * For every element in the iterator call a function and return a new iterator
- * with that value.
- *
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterator to iterate over.
- * @param {
- *     function(this:THIS,VALUE,undefined,!goog.iter.Iterator.<VALUE>):RESULT} f
- *     The function to call for every element.  This function takes 3 arguments
- *     (the element, undefined, and the iterator) and should return a new value.
- * @param {THIS=} opt_obj The object to be used as the value of 'this' within
- *     {@code f}.
- * @return {!goog.iter.Iterator.<RESULT>} A new iterator that returns the
- *     results of applying the function to each element in the original
- *     iterator.
- * @template THIS, VALUE, RESULT
+ * @return {string|undefined} Image src.
+ * @api
  */
-goog.iter.map = function(iterable, f, opt_obj) {
-  var iterator = goog.iter.toIterator(iterable);
-  var newIter = new goog.iter.Iterator;
-  newIter.next = function() {
-    var val = iterator.next();
-    return f.call(opt_obj, val, undefined, iterator);
-  };
-  return newIter;
+ol.style.Icon.prototype.getSrc = function() {
+  return this.iconImage_.getSrc();
 };
 
 
 /**
- * Passes every element of an iterator into a function and accumulates the
- * result.
- *
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     to iterate over.
- * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for
- *     every element. This function takes 2 arguments (the function's previous
- *     result or the initial value, and the value of the current element).
- *     function(previousValue, currentElement) : newValue.
- * @param {VALUE} val The initial value to pass into the function on the first
- *     call.
- * @param {THIS=} opt_obj  The object to be used as the value of 'this' within
- *     f.
- * @return {VALUE} Result of evaluating f repeatedly across the values of
- *     the iterator.
- * @template THIS, VALUE
+ * @inheritDoc
+ * @api
  */
-goog.iter.reduce = function(iterable, f, val, opt_obj) {
-  var rval = val;
-  goog.iter.forEach(iterable, function(val) {
-    rval = f.call(opt_obj, rval, val);
-  });
-  return rval;
+ol.style.Icon.prototype.getSize = function() {
+  return goog.isNull(this.size_) ? this.iconImage_.getSize() : this.size_;
 };
 
 
 /**
- * Goes through the values in the iterator. Calls f for each of these, and if
- * any of them returns true, this returns true (without checking the rest). If
- * all return false this will return false.
- *
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     object.
- * @param {
- *     function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
- *     The function to call for every value. This function takes 3 arguments
- *     (the value, undefined, and the iterator) and should return a boolean.
- * @param {THIS=} opt_obj The object to be used as the value of 'this' within
- *     {@code f}.
- * @return {boolean} true if any value passes the test.
- * @template THIS, VALUE
+ * @inheritDoc
  */
-goog.iter.some = function(iterable, f, opt_obj) {
-  iterable = goog.iter.toIterator(iterable);
-  /** @preserveTry */
-  try {
-    while (true) {
-      if (f.call(opt_obj, iterable.next(), undefined, iterable)) {
-        return true;
-      }
-    }
-  } catch (ex) {
-    if (ex !== goog.iter.StopIteration) {
-      throw ex;
-    }
-  }
-  return false;
+ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) {
+  return goog.events.listen(this.iconImage_, goog.events.EventType.CHANGE,
+      listener, false, thisArg);
 };
 
 
 /**
- * Goes through the values in the iterator. Calls f for each of these and if any
- * of them returns false this returns false (without checking the rest). If all
- * return true this will return true.
- *
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     object.
- * @param {
- *     function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
- *     The function to call for every value. This function takes 3 arguments
- *     (the value, undefined, and the iterator) and should return a boolean.
- * @param {THIS=} opt_obj The object to be used as the value of 'this' within
- *     {@code f}.
- * @return {boolean} true if every value passes the test.
- * @template THIS, VALUE
+ * Load not yet loaded URI.
  */
-goog.iter.every = function(iterable, f, opt_obj) {
-  iterable = goog.iter.toIterator(iterable);
-  /** @preserveTry */
-  try {
-    while (true) {
-      if (!f.call(opt_obj, iterable.next(), undefined, iterable)) {
-        return false;
-      }
-    }
-  } catch (ex) {
-    if (ex !== goog.iter.StopIteration) {
-      throw ex;
-    }
-  }
-  return true;
+ol.style.Icon.prototype.load = function() {
+  this.iconImage_.load();
 };
 
 
 /**
- * Takes zero or more iterables and returns one iterator that will iterate over
- * them in the order chained.
- * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any
- *     number of iterable objects.
- * @return {!goog.iter.Iterator.<VALUE>} Returns a new iterator that will
- *     iterate over all the given iterables' contents.
- * @template VALUE
+ * @inheritDoc
  */
-goog.iter.chain = function(var_args) {
-  var iterator = goog.iter.toIterator(arguments);
-  var iter = new goog.iter.Iterator();
-  var current = null;
+ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
+  goog.events.unlisten(this.iconImage_, goog.events.EventType.CHANGE,
+      listener, false, thisArg);
+};
 
-  iter.next = function() {
-    while (true) {
-      if (current == null) {
-        var it = iterator.next();
-        current = goog.iter.toIterator(it);
-      }
-      try {
-        return current.next();
-      } catch (ex) {
-        if (ex !== goog.iter.StopIteration) {
-          throw ex;
-        }
-        current = null;
-      }
-    }
-  };
 
-  return iter;
+
+/**
+ * @constructor
+ * @param {Image} image Image.
+ * @param {string|undefined} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.style.ImageState} imageState Image state.
+ * @extends {goog.events.EventTarget}
+ * @private
+ */
+ol.style.IconImage_ = function(image, src, crossOrigin, imageState) {
+
+  goog.base(this);
+
+  /**
+   * @private
+   * @type {Image|HTMLCanvasElement}
+   */
+  this.hitDetectionImage_ = null;
+
+  /**
+   * @private
+   * @type {Image}
+   */
+  this.image_ = goog.isNull(image) ? new Image() : image;
+
+  if (!goog.isNull(crossOrigin)) {
+    this.image_.crossOrigin = crossOrigin;
+  }
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.imageListenerKeys_ = null;
+
+  /**
+   * @private
+   * @type {ol.style.ImageState}
+   */
+  this.imageState_ = imageState;
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.size_ = null;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.src_ = src;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.tainting_ = false;
+
 };
+goog.inherits(ol.style.IconImage_, goog.events.EventTarget);
 
 
 /**
- * Takes a single iterable containing zero or more iterables and returns one
- * iterator that will iterate over each one in the order given.
- * @see http://docs.python.org/2/library/itertools.html#itertools.chain.from_iterable
- * @param {goog.iter.Iterable} iterable The iterable of iterables to chain.
- * @return {!goog.iter.Iterator.<VALUE>} Returns a new iterator that will
- *     iterate over all the contents of the iterables contained within
- *     {@code iterable}.
- * @template VALUE
+ * @param {Image} image Image.
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.style.ImageState} imageState Image state.
+ * @return {ol.style.IconImage_} Icon image.
  */
-goog.iter.chainFromIterable = function(iterable) {
-  return goog.iter.chain.apply(undefined, iterable);
+ol.style.IconImage_.get = function(image, src, crossOrigin, imageState) {
+  var iconImageCache = ol.style.IconImageCache.getInstance();
+  var iconImage = iconImageCache.get(src, crossOrigin);
+  if (goog.isNull(iconImage)) {
+    iconImage = new ol.style.IconImage_(image, src, crossOrigin, imageState);
+    iconImageCache.set(src, crossOrigin, iconImage);
+  }
+  return iconImage;
 };
 
 
 /**
- * Builds a new iterator that iterates over the original, but skips elements as
- * long as a supplied function returns true.
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     object.
- * @param {
- *     function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
- *     The function to call for every value. This function takes 3 arguments
- *     (the value, undefined, and the iterator) and should return a boolean.
- * @param {THIS=} opt_obj The object to be used as the value of 'this' within
- *     {@code f}.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator that drops elements from
- *     the original iterator as long as {@code f} is true.
- * @template THIS, VALUE
+ * @private
  */
-goog.iter.dropWhile = function(iterable, f, opt_obj) {
-  var iterator = goog.iter.toIterator(iterable);
-  var newIter = new goog.iter.Iterator;
-  var dropping = true;
-  newIter.next = function() {
-    while (true) {
-      var val = iterator.next();
-      if (dropping && f.call(opt_obj, val, undefined, iterator)) {
-        continue;
-      } else {
-        dropping = false;
-      }
-      return val;
-    }
-  };
-  return newIter;
+ol.style.IconImage_.prototype.determineTainting_ = function() {
+  var context = ol.dom.createCanvasContext2D(1, 1);
+  context.drawImage(this.image_, 0, 0);
+  try {
+    context.getImageData(0, 0, 1, 1);
+  } catch (e) {
+    this.tainting_ = true;
+  }
 };
 
 
 /**
- * Builds a new iterator that iterates over the original, but only as long as a
- * supplied function returns true.
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     object.
- * @param {
- *     function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
- *     The function to call for every value. This function takes 3 arguments
- *     (the value, undefined, and the iterator) and should return a boolean.
- * @param {THIS=} opt_obj This is used as the 'this' object in f when called.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator that keeps elements in
- *     the original iterator as long as the function is true.
- * @template THIS, VALUE
+ * @private
  */
-goog.iter.takeWhile = function(iterable, f, opt_obj) {
-  var iterator = goog.iter.toIterator(iterable);
-  var newIter = new goog.iter.Iterator;
-  var taking = true;
-  newIter.next = function() {
-    while (true) {
-      if (taking) {
-        var val = iterator.next();
-        if (f.call(opt_obj, val, undefined, iterator)) {
-          return val;
-        } else {
-          taking = false;
-        }
-      } else {
-        throw goog.iter.StopIteration;
-      }
-    }
-  };
-  return newIter;
+ol.style.IconImage_.prototype.dispatchChangeEvent_ = function() {
+  this.dispatchEvent(goog.events.EventType.CHANGE);
 };
 
 
 /**
- * Converts the iterator to an array
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
- *     to convert to an array.
- * @return {!Array.<VALUE>} An array of the elements the iterator iterates over.
- * @template VALUE
+ * @private
  */
-goog.iter.toArray = function(iterable) {
-  // Fast path for array-like.
-  if (goog.isArrayLike(iterable)) {
-    return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable));
-  }
-  iterable = goog.iter.toIterator(iterable);
-  var array = [];
-  goog.iter.forEach(iterable, function(val) {
-    array.push(val);
-  });
-  return array;
+ol.style.IconImage_.prototype.handleImageError_ = function() {
+  this.imageState_ = ol.style.ImageState.ERROR;
+  this.unlistenImage_();
+  this.dispatchChangeEvent_();
 };
 
 
 /**
- * Iterates over two iterables and returns true if they contain the same
- * sequence of elements and have the same length.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable1 The first
- *     iterable object.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable2 The second
- *     iterable object.
- * @return {boolean} true if the iterables contain the same sequence of elements
- *     and have the same length.
- * @template VALUE
+ * @private
  */
-goog.iter.equals = function(iterable1, iterable2) {
-  var fillValue = {};
-  var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2);
-  return goog.iter.every(pairs, function(pair) {
-    return pair[0] == pair[1];
-  });
+ol.style.IconImage_.prototype.handleImageLoad_ = function() {
+  this.imageState_ = ol.style.ImageState.LOADED;
+  this.size_ = [this.image_.width, this.image_.height];
+  this.unlistenImage_();
+  this.determineTainting_();
+  this.dispatchChangeEvent_();
 };
 
 
 /**
- * Advances the iterator to the next position, returning the given default value
- * instead of throwing an exception if the iterator has no more entries.
- * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterable
- *     object.
- * @param {VALUE} defaultValue The value to return if the iterator is empty.
- * @return {VALUE} The next item in the iteration, or defaultValue if the
- *     iterator was empty.
- * @template VALUE
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image} Image element.
  */
-goog.iter.nextOrValue = function(iterable, defaultValue) {
-  try {
-    return goog.iter.toIterator(iterable).next();
-  } catch (e) {
-    if (e != goog.iter.StopIteration) {
-      throw e;
-    }
-    return defaultValue;
-  }
+ol.style.IconImage_.prototype.getImage = function(pixelRatio) {
+  return this.image_;
 };
 
 
 /**
- * Cartesian product of zero or more sets.  Gives an iterator that gives every
- * combination of one element chosen from each set.  For example,
- * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).
- * @see http://docs.python.org/library/itertools.html#itertools.product
- * @param {...!goog.array.ArrayLike.<VALUE>} var_args Zero or more sets, as
- *     arrays.
- * @return {!goog.iter.Iterator.<!Array.<VALUE>>} An iterator that gives each
- *     n-tuple (as an array).
- * @template VALUE
+ * @return {ol.style.ImageState} Image state.
  */
-goog.iter.product = function(var_args) {
-  var someArrayEmpty = goog.array.some(arguments, function(arr) {
-    return !arr.length;
-  });
+ol.style.IconImage_.prototype.getImageState = function() {
+  return this.imageState_;
+};
 
-  // An empty set in a cartesian product gives an empty set.
-  if (someArrayEmpty || !arguments.length) {
-    return new goog.iter.Iterator();
+
+/**
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {Image|HTMLCanvasElement} Image element.
+ */
+ol.style.IconImage_.prototype.getHitDetectionImage = function(pixelRatio) {
+  if (goog.isNull(this.hitDetectionImage_)) {
+    if (this.tainting_) {
+      var width = this.size_[0];
+      var height = this.size_[1];
+      var context = ol.dom.createCanvasContext2D(width, height);
+      context.fillRect(0, 0, width, height);
+      this.hitDetectionImage_ = context.canvas;
+    } else {
+      this.hitDetectionImage_ = this.image_;
+    }
   }
+  return this.hitDetectionImage_;
+};
 
-  var iter = new goog.iter.Iterator();
-  var arrays = arguments;
 
-  // The first indices are [0, 0, ...]
-  var indicies = goog.array.repeat(0, arrays.length);
+/**
+ * @return {ol.Size} Image size.
+ */
+ol.style.IconImage_.prototype.getSize = function() {
+  return this.size_;
+};
 
-  iter.next = function() {
 
-    if (indicies) {
-      var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) {
-        return arrays[arrayIndex][valueIndex];
-      });
+/**
+ * @return {string|undefined} Image src.
+ */
+ol.style.IconImage_.prototype.getSrc = function() {
+  return this.src_;
+};
 
-      // Generate the next-largest indices for the next call.
-      // Increase the rightmost index. If it goes over, increase the next
-      // rightmost (like carry-over addition).
-      for (var i = indicies.length - 1; i >= 0; i--) {
-        // Assertion prevents compiler warning below.
-        goog.asserts.assert(indicies);
-        if (indicies[i] < arrays[i].length - 1) {
-          indicies[i]++;
-          break;
-        }
 
-        // We're at the last indices (the last element of every array), so
-        // the iteration is over on the next call.
-        if (i == 0) {
-          indicies = null;
-          break;
-        }
-        // Reset the index in this column and loop back to increment the
-        // next one.
-        indicies[i] = 0;
-      }
-      return retVal;
+/**
+ * Load not yet loaded URI.
+ */
+ol.style.IconImage_.prototype.load = function() {
+  if (this.imageState_ == ol.style.ImageState.IDLE) {
+    goog.asserts.assert(goog.isDef(this.src_));
+    goog.asserts.assert(goog.isNull(this.imageListenerKeys_));
+    this.imageState_ = ol.style.ImageState.LOADING;
+    this.imageListenerKeys_ = [
+      goog.events.listenOnce(this.image_, goog.events.EventType.ERROR,
+          this.handleImageError_, false, this),
+      goog.events.listenOnce(this.image_, goog.events.EventType.LOAD,
+          this.handleImageLoad_, false, this)
+    ];
+    try {
+      this.image_.src = this.src_;
+    } catch (e) {
+      this.handleImageError_();
     }
-
-    throw goog.iter.StopIteration;
-  };
-
-  return iter;
+  }
 };
 
 
 /**
- * Create an iterator to cycle over the iterable's elements indefinitely.
- * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...
- * @see: http://docs.python.org/library/itertools.html#itertools.cycle.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable object.
- * @return {!goog.iter.Iterator.<VALUE>} An iterator that iterates indefinitely
- *     over the values in {@code iterable}.
- * @template VALUE
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
  */
-goog.iter.cycle = function(iterable) {
-  var baseIterator = goog.iter.toIterator(iterable);
-
-  // We maintain a cache to store the iterable elements as we iterate
-  // over them. The cache is used to return elements once we have
-  // iterated over the iterable once.
-  var cache = [];
-  var cacheIndex = 0;
-
-  var iter = new goog.iter.Iterator();
+ol.style.IconImage_.prototype.unlistenImage_ = function() {
+  goog.asserts.assert(!goog.isNull(this.imageListenerKeys_));
+  goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
+};
 
-  // This flag is set after the iterable is iterated over once
-  var useCache = false;
 
-  iter.next = function() {
-    var returnElement = null;
 
-    // Pull elements off the original iterator if not using cache
-    if (!useCache) {
-      try {
-        // Return the element from the iterable
-        returnElement = baseIterator.next();
-        cache.push(returnElement);
-        return returnElement;
-      } catch (e) {
-        // If an exception other than StopIteration is thrown
-        // or if there are no elements to iterate over (the iterable was empty)
-        // throw an exception
-        if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) {
-          throw e;
-        }
-        // set useCache to true after we know that a 'StopIteration' exception
-        // was thrown and the cache is not empty (to handle the 'empty iterable'
-        // use case)
-        useCache = true;
-      }
-    }
+/**
+ * @constructor
+ */
+ol.style.IconImageCache = function() {
 
-    returnElement = cache[cacheIndex];
-    cacheIndex = (cacheIndex + 1) % cache.length;
+  /**
+   * @type {Object.<string, ol.style.IconImage_>}
+   * @private
+   */
+  this.cache_ = {};
 
-    return returnElement;
-  };
+  /**
+   * @type {number}
+   * @private
+   */
+  this.cacheSize_ = 0;
 
-  return iter;
+  /**
+   * @const
+   * @type {number}
+   * @private
+   */
+  this.maxCacheSize_ = 32;
 };
+goog.addSingletonGetter(ol.style.IconImageCache);
 
 
 /**
- * Creates an iterator that counts indefinitely from a starting value.
- * @see http://docs.python.org/2/library/itertools.html#itertools.count
- * @param {number=} opt_start The starting value. Default is 0.
- * @param {number=} opt_step The number to increment with between each call to
- *     next. Negative and floating point numbers are allowed. Default is 1.
- * @return {!goog.iter.Iterator.<number>} A new iterator that returns the values
- *     in the series.
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @return {string} Cache key.
  */
-goog.iter.count = function(opt_start, opt_step) {
-  var counter = opt_start || 0;
-  var step = goog.isDef(opt_step) ? opt_step : 1;
-  var iter = new goog.iter.Iterator();
-
-  iter.next = function() {
-    var returnValue = counter;
-    counter += step;
-    return returnValue;
-  };
-
-  return iter;
+ol.style.IconImageCache.getKey = function(src, crossOrigin) {
+  goog.asserts.assert(goog.isDef(crossOrigin));
+  return crossOrigin + ':' + src;
 };
 
 
 /**
- * Creates an iterator that returns the same object or value repeatedly.
- * @param {VALUE} value Any object or value to repeat.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator that returns the
- *     repeated value.
- * @template VALUE
+ * FIXME empty description for jsdoc
  */
-goog.iter.repeat = function(value) {
-  var iter = new goog.iter.Iterator();
-
-  iter.next = goog.functions.constant(value);
-
-  return iter;
+ol.style.IconImageCache.prototype.clear = function() {
+  this.cache_ = {};
+  this.cacheSize_ = 0;
 };
 
 
 /**
- * Creates an iterator that returns running totals from the numbers in
- * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields
- * {@code 1 -> 3 -> 6 -> 10 -> 15}.
- * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate
- * @param {!goog.iter.Iterable.<number>} iterable The iterable of numbers to
- *     accumulate.
- * @return {!goog.iter.Iterator.<number>} A new iterator that returns the
- *     numbers in the series.
+ * FIXME empty description for jsdoc
  */
-goog.iter.accumulate = function(iterable) {
-  var iterator = goog.iter.toIterator(iterable);
-  var total = 0;
-  var iter = new goog.iter.Iterator();
+ol.style.IconImageCache.prototype.expire = function() {
+  if (this.cacheSize_ > this.maxCacheSize_) {
+    var i = 0;
+    var key, iconImage;
+    for (key in this.cache_) {
+      iconImage = this.cache_[key];
+      if ((i++ & 3) === 0 && !goog.events.hasListener(iconImage)) {
+        delete this.cache_[key];
+        --this.cacheSize_;
+      }
+    }
+  }
+};
 
-  iter.next = function() {
-    total += iterator.next();
-    return total;
-  };
 
-  return iter;
+/**
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @return {ol.style.IconImage_} Icon image.
+ */
+ol.style.IconImageCache.prototype.get = function(src, crossOrigin) {
+  var key = ol.style.IconImageCache.getKey(src, crossOrigin);
+  return key in this.cache_ ? this.cache_[key] : null;
 };
 
 
 /**
- * Creates an iterator that returns arrays containing the ith elements from the
- * provided iterables. The returned arrays will be the same size as the number
- * of iterables given in {@code var_args}. Once the shortest iterable is
- * exhausted, subsequent calls to {@code next()} will throw
- * {@code goog.iter.StopIteration}.
- * @see http://docs.python.org/2/library/itertools.html#itertools.izip
- * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any
- *     number of iterable objects.
- * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator that returns
- *     arrays of elements from the provided iterables.
- * @template VALUE
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.style.IconImage_} iconImage Icon image.
  */
-goog.iter.zip = function(var_args) {
-  var args = arguments;
-  var iter = new goog.iter.Iterator();
+ol.style.IconImageCache.prototype.set = function(src, crossOrigin, iconImage) {
+  var key = ol.style.IconImageCache.getKey(src, crossOrigin);
+  this.cache_[key] = iconImage;
+  ++this.cacheSize_;
+};
 
-  if (args.length > 0) {
-    var iterators = goog.array.map(args, goog.iter.toIterator);
-    iter.next = function() {
-      var arr = goog.array.map(iterators, function(it) {
-        return it.next();
-      });
-      return arr;
-    };
-  }
+goog.provide('ol.vec.Mat4');
 
-  return iter;
-};
+goog.require('goog.vec.Mat4');
 
 
 /**
- * Creates an iterator that returns arrays containing the ith elements from the
- * provided iterables. The returned arrays will be the same size as the number
- * of iterables given in {@code var_args}. Shorter iterables will be extended
- * with {@code fillValue}. Once the longest iterable is exhausted, subsequent
- * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
- * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest
- * @param {VALUE} fillValue The object or value used to fill shorter iterables.
- * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any
- *     number of iterable objects.
- * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator that returns
- *     arrays of elements from the provided iterables.
- * @template VALUE
+ * @param {!goog.vec.Mat4.Number} mat Matrix.
+ * @param {number} translateX1 Translate X1.
+ * @param {number} translateY1 Translate Y1.
+ * @param {number} scaleX Scale X.
+ * @param {number} scaleY Scale Y.
+ * @param {number} rotation Rotation.
+ * @param {number} translateX2 Translate X2.
+ * @param {number} translateY2 Translate Y2.
+ * @return {!goog.vec.Mat4.Number} Matrix.
  */
-goog.iter.zipLongest = function(fillValue, var_args) {
-  var args = goog.array.slice(arguments, 1);
-  var iter = new goog.iter.Iterator();
+ol.vec.Mat4.makeTransform2D = function(mat, translateX1, translateY1,
+    scaleX, scaleY, rotation, translateX2, translateY2) {
+  goog.vec.Mat4.makeIdentity(mat);
+  if (translateX1 !== 0 || translateY1 !== 0) {
+    goog.vec.Mat4.translate(mat, translateX1, translateY1, 0);
+  }
+  if (scaleX != 1 || scaleY != 1) {
+    goog.vec.Mat4.scale(mat, scaleX, scaleY, 1);
+  }
+  if (rotation !== 0) {
+    goog.vec.Mat4.rotateZ(mat, rotation);
+  }
+  if (translateX2 !== 0 || translateY2 !== 0) {
+    goog.vec.Mat4.translate(mat, translateX2, translateY2, 0);
+  }
+  return mat;
+};
 
-  if (args.length > 0) {
-    var iterators = goog.array.map(args, goog.iter.toIterator);
 
-    iter.next = function() {
-      var iteratorsHaveValues = false;  // false when all iterators are empty.
-      var arr = goog.array.map(iterators, function(it) {
-        var returnValue;
-        try {
-          returnValue = it.next();
-          // Iterator had a value, so we've not exhausted the iterators.
-          // Set flag accordingly.
-          iteratorsHaveValues = true;
-        } catch (ex) {
-          if (ex !== goog.iter.StopIteration) {
-            throw ex;
-          }
-          returnValue = fillValue;
-        }
-        return returnValue;
-      });
+/**
+ * Returns true if mat1 and mat2 represent the same 2D transformation.
+ * @param {goog.vec.Mat4.Number} mat1 Matrix 1.
+ * @param {goog.vec.Mat4.Number} mat2 Matrix 2.
+ * @return {boolean} Equal 2D.
+ */
+ol.vec.Mat4.equals2D = function(mat1, mat2) {
+  return (
+      goog.vec.Mat4.getElement(mat1, 0, 0) ==
+      goog.vec.Mat4.getElement(mat2, 0, 0) &&
+      goog.vec.Mat4.getElement(mat1, 1, 0) ==
+      goog.vec.Mat4.getElement(mat2, 1, 0) &&
+      goog.vec.Mat4.getElement(mat1, 0, 1) ==
+      goog.vec.Mat4.getElement(mat2, 0, 1) &&
+      goog.vec.Mat4.getElement(mat1, 1, 1) ==
+      goog.vec.Mat4.getElement(mat2, 1, 1) &&
+      goog.vec.Mat4.getElement(mat1, 0, 3) ==
+      goog.vec.Mat4.getElement(mat2, 0, 3) &&
+      goog.vec.Mat4.getElement(mat1, 1, 3) ==
+      goog.vec.Mat4.getElement(mat2, 1, 3));
+};
 
-      if (!iteratorsHaveValues) {
-        throw goog.iter.StopIteration;
-      }
-      return arr;
-    };
-  }
 
-  return iter;
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * upper 2x4 matrix omitting the projective component.
+ *
+ * @param {goog.vec.Mat4.Number} mat The matrix supplying the transformation.
+ * @param {Array.<number>} vec The 3 element vector to transform.
+ * @param {Array.<number>} resultVec The 3 element vector to receive the results
+ *     (may be vec).
+ * @return {Array.<number>} return resultVec so that operations can be
+ *     chained together.
+ */
+ol.vec.Mat4.multVec2 = function(mat, vec, resultVec) {
+  var m00 = goog.vec.Mat4.getElement(mat, 0, 0);
+  var m10 = goog.vec.Mat4.getElement(mat, 1, 0);
+  var m01 = goog.vec.Mat4.getElement(mat, 0, 1);
+  var m11 = goog.vec.Mat4.getElement(mat, 1, 1);
+  var m03 = goog.vec.Mat4.getElement(mat, 0, 3);
+  var m13 = goog.vec.Mat4.getElement(mat, 1, 3);
+  var x = vec[0], y = vec[1];
+  resultVec[0] = m00 * x + m01 * y + m03;
+  resultVec[1] = m10 * x + m11 * y + m13;
+  return resultVec;
 };
 
+goog.provide('ol.RendererType');
+goog.provide('ol.renderer.Map');
+
+goog.require('goog.Disposable');
+goog.require('goog.asserts');
+goog.require('goog.dispose');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('ol.layer.Layer');
+goog.require('ol.renderer.Layer');
+goog.require('ol.style.IconImageCache');
+goog.require('ol.vec.Mat4');
+
 
 /**
- * Creates an iterator that filters {@code iterable} based on a series of
- * {@code selectors}. On each call to {@code next()}, one item is taken from
- * both the {@code iterable} and {@code selectors} iterators. If the item from
- * {@code selectors} evaluates to true, the item from {@code iterable} is given.
- * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors}
- * is exhausted, subsequent calls to {@code next()} will throw
- * {@code goog.iter.StopIteration}.
- * @see http://docs.python.org/2/library/itertools.html#itertools.compress
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to filter.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} selectors An
- *     iterable of items to be evaluated in a boolean context to determine if
- *     the corresponding element in {@code iterable} should be included in the
- *     result.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator that returns the
- *     filtered values.
- * @template VALUE
+ * Available renderers: `'canvas'`, `'dom'` or `'webgl'`.
+ * @enum {string}
+ * @api stable
  */
-goog.iter.compress = function(iterable, selectors) {
-  var selectorIterator = goog.iter.toIterator(selectors);
-
-  return goog.iter.filter(iterable, function() {
-    return !!selectorIterator.next();
-  });
+ol.RendererType = {
+  CANVAS: 'canvas',
+  DOM: 'dom',
+  WEBGL: 'webgl'
 };
 
 
 
 /**
- * Implements the {@code goog.iter.groupBy} iterator.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to group.
- * @param {function(...[VALUE]): KEY=} opt_keyFunc  Optional function for
- *     determining the key value for each group in the {@code iterable}. Default
- *     is the identity function.
  * @constructor
- * @extends {goog.iter.Iterator.<!Array>}
- * @template KEY, VALUE
- * @private
+ * @extends {goog.Disposable}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
+ * @suppress {checkStructDictInheritance}
+ * @struct
  */
-goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) {
+ol.renderer.Map = function(container, map) {
 
-  /**
-   * The iterable to group, coerced to an iterator.
-   * @type {!goog.iter.Iterator}
-   */
-  this.iterator = goog.iter.toIterator(iterable);
+  goog.base(this);
 
   /**
-   * A function for determining the key value for each element in the iterable.
-   * If no function is provided, the identity function is used and returns the
-   * element unchanged.
-   * @type {function(...[VALUE]): KEY}
+   * @private
+   * @type {ol.Map}
    */
-  this.keyFunc = opt_keyFunc || goog.functions.identity;
+  this.map_ = map;
 
   /**
-   * The target key for determining the start of a group.
-   * @type {KEY}
+   * @protected
+   * @type {ol.render.IReplayGroup}
    */
-  this.targetKey;
+  this.replayGroup = null;
 
   /**
-   * The current key visited during iteration.
-   * @type {KEY}
+   * @private
+   * @type {Object.<string, ol.renderer.Layer>}
    */
-  this.currentKey;
+  this.layerRenderers_ = {};
 
-  /**
-   * The current value being added to the group.
-   * @type {VALUE}
-   */
-  this.currentValue;
 };
-goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator);
+goog.inherits(ol.renderer.Map, goog.Disposable);
 
 
-/** @override */
-goog.iter.GroupByIterator_.prototype.next = function() {
-  while (this.currentKey == this.targetKey) {
-    this.currentValue = this.iterator.next();  // Exits on StopIteration
-    this.currentKey = this.keyFunc(this.currentValue);
-  }
-  this.targetKey = this.currentKey;
-  return [this.currentKey, this.groupItems_(this.targetKey)];
+/**
+ * @param {olx.FrameState} frameState FrameState.
+ * @protected
+ */
+ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) {
+  var viewState = frameState.viewState;
+  var coordinateToPixelMatrix = frameState.coordinateToPixelMatrix;
+  goog.asserts.assert(!goog.isNull(coordinateToPixelMatrix));
+  ol.vec.Mat4.makeTransform2D(coordinateToPixelMatrix,
+      frameState.size[0] / 2, frameState.size[1] / 2,
+      1 / viewState.resolution, -1 / viewState.resolution,
+      -viewState.rotation,
+      -viewState.center[0], -viewState.center[1]);
+  var inverted = goog.vec.Mat4.invert(
+      coordinateToPixelMatrix, frameState.pixelToCoordinateMatrix);
+  goog.asserts.assert(inverted);
 };
 
 
 /**
- * Performs the grouping of objects using the given key.
- * @param {KEY} targetKey  The target key object for the group.
- * @return {!Array.<VALUE>} An array of grouped objects.
- * @private
+ * @param {ol.layer.Layer} layer Layer.
+ * @protected
+ * @return {ol.renderer.Layer} layerRenderer Layer renderer.
  */
-goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) {
-  var arr = [];
-  while (this.currentKey == targetKey) {
-    arr.push(this.currentValue);
-    try {
-      this.currentValue = this.iterator.next();
-    } catch (ex) {
-      if (ex !== goog.iter.StopIteration) {
-        throw ex;
-      }
-      break;
-    }
-    this.currentKey = this.keyFunc(this.currentValue);
-  }
-  return arr;
+ol.renderer.Map.prototype.createLayerRenderer = function(layer) {
+  return new ol.renderer.Layer(this, layer);
 };
 
 
 /**
- * Creates an iterator that returns arrays containing elements from the
- * {@code iterable} grouped by a key value. For iterables with repeated
- * elements (i.e. sorted according to a particular key function), this function
- * has a {@code uniq}-like effect. For example, grouping the array:
- * {@code [A, B, B, C, C, A]} produces
- * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}.
- * @see http://docs.python.org/2/library/itertools.html#itertools.groupby
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to group.
- * @param {function(...[VALUE]): KEY=} opt_keyFunc  Optional function for
- *     determining the key value for each group in the {@code iterable}. Default
- *     is the identity function.
- * @return {!goog.iter.Iterator.<!Array>} A new iterator that returns arrays of
- *     consecutive key and groups.
- * @template KEY, VALUE
+ * @inheritDoc
  */
-goog.iter.groupBy = function(iterable, opt_keyFunc) {
-  return new goog.iter.GroupByIterator_(iterable, opt_keyFunc);
+ol.renderer.Map.prototype.disposeInternal = function() {
+  goog.object.forEach(this.layerRenderers_, goog.dispose);
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * Gives an iterator that gives the result of calling the given function
- * <code>f</code> with the arguments taken from the next element from
- * <code>iterable</code> (the elements are expected to also be iterables).
- *
- * Similar to {@see goog.iter#map} but allows the function to accept multiple
- * arguments from the iterable.
- *
- * @param {!goog.iter.Iterable.<!goog.iter.Iterable>} iterable The iterable of
- *     iterables to iterate over.
- * @param {function(this:THIS,...[*]):RESULT} f The function to call for every
- *     element.  This function takes N+2 arguments, where N represents the
- *     number of items from the next element of the iterable. The two
- *     additional arguments passed to the function are undefined and the
- *     iterator itself. The function should return a new value.
- * @param {THIS=} opt_obj The object to be used as the value of 'this' within
- *     {@code f}.
- * @return {!goog.iter.Iterator.<RESULT>} A new iterator that returns the
- *     results of applying the function to each element in the original
- *     iterator.
- * @template THIS, RESULT
+ * @param {ol.Map} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-goog.iter.starMap = function(iterable, f, opt_obj) {
-  var iterator = goog.iter.toIterator(iterable);
-  var iter = new goog.iter.Iterator();
-
-  iter.next = function() {
-    var args = goog.iter.toArray(iterator.next());
-    return f.apply(opt_obj, goog.array.concat(args, undefined, iterator));
-  };
-
-  return iter;
+ol.renderer.Map.expireIconCache_ = function(map, frameState) {
+  ol.style.IconImageCache.getInstance().expire();
 };
 
 
 /**
- * Returns an array of iterators each of which can iterate over the values in
- * {@code iterable} without advancing the others.
- * @see http://docs.python.org/2/library/itertools.html#itertools.tee
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to tee.
- * @param {number=} opt_num  The number of iterators to create. Default is 2.
- * @return {!Array.<goog.iter.Iterator.<VALUE>>} An array of iterators.
- * @template VALUE
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
+ *     callback.
+ * @param {S} thisArg Value to use as `this` when executing `callback`.
+ * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
+ *     function, only layers which are visible and for which this function
+ *     returns `true` will be tested for features.  By default, all visible
+ *     layers will be tested.
+ * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`.
+ * @return {T|undefined} Callback result.
+ * @template S,T,U
  */
-goog.iter.tee = function(iterable, opt_num) {
-  var iterator = goog.iter.toIterator(iterable);
-  var num = goog.isNumber(opt_num) ? opt_num : 2;
-  var buffers = goog.array.map(goog.array.range(num), function() {
-    return [];
-  });
-
-  var addNextIteratorValueToBuffers = function() {
-    var val = iterator.next();
-    goog.array.forEach(buffers, function(buffer) {
-      buffer.push(val);
-    });
-  };
-
-  var createIterator = function(buffer) {
-    // Each tee'd iterator has an associated buffer (initially empty). When a
-    // tee'd iterator's buffer is empty, it calls
-    // addNextIteratorValueToBuffers(), adding the next value to all tee'd
-    // iterators' buffers, and then returns that value. This allows each
-    // iterator to be advanced independently.
-    var iter = new goog.iter.Iterator();
-
-    iter.next = function() {
-      if (goog.array.isEmpty(buffer)) {
-        addNextIteratorValueToBuffers();
+ol.renderer.Map.prototype.forEachFeatureAtPixel =
+    function(coordinate, frameState, callback, thisArg,
+        layerFilter, thisArg2) {
+  var result;
+  var viewState = frameState.viewState;
+  var viewResolution = viewState.resolution;
+  var viewRotation = viewState.rotation;
+  if (!goog.isNull(this.replayGroup)) {
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+    result = this.replayGroup.forEachGeometryAtPixel(viewResolution,
+        viewRotation, coordinate, {},
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          goog.asserts.assert(goog.isDef(feature));
+          var key = goog.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback.call(thisArg, feature, null);
+          }
+        });
+    if (result) {
+      return result;
+    }
+  }
+  var layerStates = this.map_.getLayerGroup().getLayerStatesArray();
+  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.forEachFeatureAtPixel(
+          coordinate, frameState, callback, thisArg);
+      if (result) {
+        return result;
       }
-      goog.asserts.assert(!goog.array.isEmpty(buffer));
-      return buffer.shift();
-    };
+    }
+  }
+  return undefined;
+};
 
-    return iter;
-  };
 
-  return goog.array.map(buffers, createIterator);
+/**
+ * @param {ol.layer.Layer} layer Layer.
+ * @protected
+ * @return {ol.renderer.Layer} Layer renderer.
+ */
+ol.renderer.Map.prototype.getLayerRenderer = function(layer) {
+  var layerKey = goog.getUid(layer).toString();
+  if (layerKey in this.layerRenderers_) {
+    return this.layerRenderers_[layerKey];
+  } else {
+    var layerRenderer = this.createLayerRenderer(layer);
+    this.layerRenderers_[layerKey] = layerRenderer;
+    return layerRenderer;
+  }
 };
 
 
 /**
- * Creates an iterator that returns arrays containing a count and an element
- * obtained from the given {@code iterable}.
- * @see http://docs.python.org/2/library/functions.html#enumerate
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to enumerate.
- * @param {number=} opt_start  Optional starting value. Default is 0.
- * @return {!goog.iter.Iterator.<!Array>} A new iterator containing count/item
- *     pairs.
- * @template VALUE
+ * @param {string} layerKey Layer key.
+ * @protected
+ * @return {ol.renderer.Layer} Layer renderer.
  */
-goog.iter.enumerate = function(iterable, opt_start) {
-  return goog.iter.zip(goog.iter.count(opt_start), iterable);
+ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) {
+  goog.asserts.assert(layerKey in this.layerRenderers_);
+  return this.layerRenderers_[layerKey];
 };
 
 
 /**
- * Creates an iterator that returns the first {@code limitSize} elements from an
- * iterable. If this number is greater than the number of elements in the
- * iterable, all the elements are returned.
- * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to limit.
- * @param {number} limitSize  The maximum number of elements to return.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator containing
- *     {@code limitSize} elements.
- * @template VALUE
+ * @protected
+ * @return {Object.<number, ol.renderer.Layer>} Layer renderers.
  */
-goog.iter.limit = function(iterable, limitSize) {
-  goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0);
+ol.renderer.Map.prototype.getLayerRenderers = function() {
+  return this.layerRenderers_;
+};
 
-  var iterator = goog.iter.toIterator(iterable);
 
-  var iter = new goog.iter.Iterator();
-  var remaining = limitSize;
+/**
+ * @return {ol.Map} Map.
+ */
+ol.renderer.Map.prototype.getMap = function() {
+  return this.map_;
+};
 
-  iter.next = function() {
-    if (remaining-- > 0) {
-      return iterator.next();
-    }
-    throw goog.iter.StopIteration;
-  };
 
-  return iter;
-};
+/**
+ * @return {string} Type
+ */
+ol.renderer.Map.prototype.getType = goog.abstractMethod;
 
 
 /**
- * Creates an iterator that is advanced {@code count} steps ahead. Consumed
- * values are silently discarded. If {@code count} is greater than the number
- * of elements in {@code iterable}, an empty iterator is returned. Subsequent
- * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to consume.
- * @param {number} count  The number of elements to consume from the iterator.
- * @return {!goog.iter.Iterator.<VALUE>} An iterator advanced zero or more steps
- *     ahead.
- * @template VALUE
+ * @param {string} layerKey Layer key.
+ * @return {ol.renderer.Layer} Layer renderer.
+ * @private
  */
-goog.iter.consume = function(iterable, count) {
-  goog.asserts.assert(goog.math.isInt(count) && count >= 0);
-
-  var iterator = goog.iter.toIterator(iterable);
-
-  while (count-- > 0) {
-    goog.iter.nextOrValue(iterator, null);
-  }
-
-  return iterator;
+ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) {
+  goog.asserts.assert(layerKey in this.layerRenderers_);
+  var layerRenderer = this.layerRenderers_[layerKey];
+  delete this.layerRenderers_[layerKey];
+  return layerRenderer;
 };
 
 
 /**
- * Creates an iterator that returns a range of elements from an iterable.
- * Similar to {@see goog.array#slice} but does not support negative indexes.
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to slice.
- * @param {number} start  The index of the first element to return.
- * @param {number=} opt_end  The index after the last element to return. If
- *     defined, must be greater than or equal to {@code start}.
- * @return {!goog.iter.Iterator.<VALUE>} A new iterator containing a slice of
- *     the original.
- * @template VALUE
+ * Render.
+ * @param {?olx.FrameState} frameState Frame state.
  */
-goog.iter.slice = function(iterable, start, opt_end) {
-  goog.asserts.assert(goog.math.isInt(start) && start >= 0);
-
-  var iterator = goog.iter.consume(iterable, start);
-
-  if (goog.isNumber(opt_end)) {
-    goog.asserts.assert(
-        goog.math.isInt(/** @type {number} */ (opt_end)) && opt_end >= start);
-    iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */);
-  }
-
-  return iterator;
-};
+ol.renderer.Map.prototype.renderFrame = goog.nullFunction;
 
 
 /**
- * Checks an array for duplicate elements.
- * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to check for
- *     duplicates.
- * @return {boolean} True, if the array contains duplicates, false otherwise.
+ * @param {ol.Map} map Map.
+ * @param {olx.FrameState} frameState Frame state.
  * @private
- * @template VALUE
  */
-// TODO(user): Consider moving this into goog.array as a public function.
-goog.iter.hasDuplicates_ = function(arr) {
-  var deduped = [];
-  goog.array.removeDuplicates(arr, deduped);
-  return arr.length != deduped.length;
+ol.renderer.Map.prototype.removeUnusedLayerRenderers_ =
+    function(map, frameState) {
+  var layerKey;
+  for (layerKey in this.layerRenderers_) {
+    if (goog.isNull(frameState) || !(layerKey in frameState.layerStates)) {
+      goog.dispose(this.removeLayerRendererByKey_(layerKey));
+    }
+  }
 };
 
 
 /**
- * Creates an iterator that returns permutations of elements in
- * {@code iterable}.
- *
- * Permutations are obtained by taking the Cartesian product of
- * {@code opt_length} iterables and filtering out those with repeated
- * elements. For example, the permutations of {@code [1,2,3]} are
- * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}.
- * @see http://docs.python.org/2/library/itertools.html#itertools.permutations
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable from which to generate permutations.
- * @param {number=} opt_length Length of each permutation. If omitted, defaults
- *     to the length of {@code iterable}.
- * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing the
- *     permutations of {@code iterable}.
- * @template VALUE
+ * @param {olx.FrameState} frameState Frame state.
+ * @protected
  */
-goog.iter.permutations = function(iterable, opt_length) {
-  var elements = goog.iter.toArray(iterable);
-  var length = goog.isNumber(opt_length) ? opt_length : elements.length;
-
-  var sets = goog.array.repeat(elements, length);
-  var product = goog.iter.product.apply(undefined, sets);
-
-  return goog.iter.filter(product, function(arr) {
-    return !goog.iter.hasDuplicates_(arr);
-  });
+ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
+  frameState.postRenderFunctions.push(ol.renderer.Map.expireIconCache_);
 };
 
 
 /**
- * Creates an iterator that returns combinations of elements from
- * {@code iterable}.
- *
- * Combinations are obtained by taking the {@see goog.iter#permutations} of
- * {@code iterable} and filtering those whose elements appear in the order they
- * are encountered in {@code iterable}. For example, the 3-length combinations
- * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}.
- * @see http://docs.python.org/2/library/itertools.html#itertools.combinations
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable from which to generate combinations.
- * @param {number} length The length of each combination.
- * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing
- *     combinations from the {@code iterable}.
- * @template VALUE
+ * @param {!olx.FrameState} frameState Frame state.
+ * @protected
  */
-goog.iter.combinations = function(iterable, length) {
-  var elements = goog.iter.toArray(iterable);
-  var indexes = goog.iter.range(elements.length);
-  var indexIterator = goog.iter.permutations(indexes, length);
-  // sortedIndexIterator will now give arrays of with the given length that
-  // indicate what indexes into "elements" should be returned on each iteration.
-  var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
-    return goog.array.isSorted(arr);
-  });
-
-  var iter = new goog.iter.Iterator();
-
-  function getIndexFromElements(index) {
-    return elements[index];
+ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers =
+    function(frameState) {
+  var layerKey;
+  for (layerKey in this.layerRenderers_) {
+    if (!(layerKey in frameState.layerStates)) {
+      frameState.postRenderFunctions.push(
+          goog.bind(this.removeUnusedLayerRenderers_, this));
+      return;
+    }
   }
-
-  iter.next = function() {
-    return goog.array.map(
-        /** @type {!Array.<number>} */
-        (sortedIndexIterator.next()), getIndexFromElements);
-  };
-
-  return iter;
 };
 
+goog.provide('ol.structs.PriorityQueue');
 
-/**
- * Creates an iterator that returns combinations of elements from
- * {@code iterable}, with repeated elements possible.
- *
- * Combinations are obtained by taking the Cartesian product of {@code length}
- * iterables and filtering those whose elements appear in the order they are
- * encountered in {@code iterable}. For example, the 2-length combinations of
- * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}.
- * @see http://docs.python.org/2/library/itertools.html#itertools.combinations_with_replacement
- * @see http://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition
- * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
- *     iterable to combine.
- * @param {number} length The length of each combination.
- * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing
- *     combinations from the {@code iterable}.
- * @template VALUE
- */
-goog.iter.combinationsWithReplacement = function(iterable, length) {
-  var elements = goog.iter.toArray(iterable);
-  var indexes = goog.array.range(elements.length);
-  var sets = goog.array.repeat(indexes, length);
-  var indexIterator = goog.iter.product.apply(undefined, sets);
-  // sortedIndexIterator will now give arrays of with the given length that
-  // indicate what indexes into "elements" should be returned on each iteration.
-  var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
-    return goog.array.isSorted(arr);
-  });
-
-  var iter = new goog.iter.Iterator();
-
-  function getIndexFromElements(index) {
-    return elements[index];
-  }
-
-  iter.next = function() {
-    return goog.array.map(
-        /** @type {!Array.<number>} */
-        (sortedIndexIterator.next()), getIndexFromElements);
-  };
+goog.require('goog.asserts');
+goog.require('goog.object');
 
-  return iter;
-};
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Datastructure: Hash Map.
+ * Priority queue.
  *
- * @author arv@google.com (Erik Arvidsson)
- * @author jonp@google.com (Jon Perlow) Optimized for IE6
+ * 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
  *
- * This file contains an implementation of a Map structure. It implements a lot
- * of the methods used in goog.structs so those functions work on hashes. This
- * is best suited for complex key types. For simple keys such as numbers and
- * strings, and where special names like __proto__ are not a concern, consider
- * using the lighter-weight utilities in goog.object.
- */
-
-
-goog.provide('goog.structs.Map');
-
-goog.require('goog.iter.Iterator');
-goog.require('goog.iter.StopIteration');
-goog.require('goog.object');
-
-
-
-/**
- * Class for Hash Map datastructure.
- * @param {*=} opt_map Map or Object to initialize the map with.
- * @param {...*} var_args If 2 or more arguments are present then they
- *     will be used as key-value pairs.
  * @constructor
- * @template K, V
+ * @param {function(T): number} priorityFunction Priority function.
+ * @param {function(T): string} keyFunction Key function.
+ * @struct
+ * @template T
  */
-goog.structs.Map = function(opt_map, var_args) {
+ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
 
   /**
-   * Underlying JS object used to implement the map.
-   * @private {!Object}
+   * @type {function(T): number}
+   * @private
    */
-  this.map_ = {};
+  this.priorityFunction_ = priorityFunction;
 
   /**
-   * An array of keys. This is necessary for two reasons:
-   *   1. Iterating the keys using for (var key in this.map_) allocates an
-   *      object for every key in IE which is really bad for IE6 GC perf.
-   *   2. Without a side data structure, we would need to escape all the keys
-   *      as that would be the only way we could tell during iteration if the
-   *      key was an internal key or a property of the object.
-   *
-   * This array can contain deleted keys so it's necessary to check the map
-   * as well to see if the key is still in the map (this doesn't require a
-   * memory allocation in IE).
-   * @private {!Array.<string>}
+   * @type {function(T): string}
+   * @private
    */
-  this.keys_ = [];
+  this.keyFunction_ = keyFunction;
 
   /**
-   * The number of key value pairs in the map.
-   * @private {number}
+   * @type {Array.<T>}
+   * @private
    */
-  this.count_ = 0;
+  this.elements_ = [];
 
   /**
-   * Version used to detect changes while iterating.
-   * @private {number}
+   * @type {Array.<number>}
+   * @private
    */
-  this.version_ = 0;
+  this.priorities_ = [];
 
-  var argLength = arguments.length;
+  /**
+   * @type {Object.<string, boolean>}
+   * @private
+   */
+  this.queuedElements_ = {};
 
-  if (argLength > 1) {
-    if (argLength % 2) {
-      throw Error('Uneven number of arguments');
-    }
-    for (var i = 0; i < argLength; i += 2) {
-      this.set(arguments[i], arguments[i + 1]);
-    }
-  } else if (opt_map) {
-    this.addAll(/** @type {Object} */ (opt_map));
-  }
 };
 
 
 /**
- * @return {number} The number of key-value pairs in the map.
+ * @const
+ * @type {number}
  */
-goog.structs.Map.prototype.getCount = function() {
-  return this.count_;
-};
+ol.structs.PriorityQueue.DROP = Infinity;
 
 
 /**
- * Returns the values of the map.
- * @return {!Array.<V>} The values in the map.
+ * FIXME empty desciption for jsdoc
  */
-goog.structs.Map.prototype.getValues = function() {
-  this.cleanupKeysArray_();
-
-  var rv = [];
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    rv.push(this.map_[key]);
+ol.structs.PriorityQueue.prototype.assertValid = function() {
+  var elements = this.elements_;
+  var priorities = this.priorities_;
+  var n = elements.length;
+  goog.asserts.assert(priorities.length == n);
+  var i, priority;
+  for (i = 0; i < (n >> 1) - 1; ++i) {
+    priority = priorities[i];
+    goog.asserts.assert(priority <= priorities[this.getLeftChildIndex_(i)]);
+    goog.asserts.assert(priority <= priorities[this.getRightChildIndex_(i)]);
   }
-  return rv;
-};
-
-
-/**
- * Returns the keys of the map.
- * @return {!Array.<string>} Array of string values.
- */
-goog.structs.Map.prototype.getKeys = function() {
-  this.cleanupKeysArray_();
-  return /** @type {!Array.<string>} */ (this.keys_.concat());
 };
 
 
 /**
- * Whether the map contains the given key.
- * @param {*} key The key to check for.
- * @return {boolean} Whether the map contains the key.
+ * FIXME empty description for jsdoc
  */
-goog.structs.Map.prototype.containsKey = function(key) {
-  return goog.structs.Map.hasKey_(this.map_, key);
+ol.structs.PriorityQueue.prototype.clear = function() {
+  this.elements_.length = 0;
+  this.priorities_.length = 0;
+  goog.object.clear(this.queuedElements_);
 };
 
 
 /**
- * Whether the map contains the given value. This is O(n).
- * @param {V} val The value to check for.
- * @return {boolean} Whether the map contains the value.
+ * Remove and return the highest-priority element. O(log N).
+ * @return {T} Element.
  */
-goog.structs.Map.prototype.containsValue = function(val) {
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
-      return true;
-    }
+ol.structs.PriorityQueue.prototype.dequeue = function() {
+  var elements = this.elements_;
+  goog.asserts.assert(elements.length > 0);
+  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);
   }
-  return false;
+  var elementKey = this.keyFunction_(element);
+  goog.asserts.assert(elementKey in this.queuedElements_);
+  delete this.queuedElements_[elementKey];
+  return element;
 };
 
 
 /**
- * Whether this map is equal to the argument map.
- * @param {goog.structs.Map} otherMap The map against which to test equality.
- * @param {function(V, V): boolean=} opt_equalityFn Optional equality function
- *     to test equality of values. If not specified, this will test whether
- *     the values contained in each map are identical objects.
- * @return {boolean} Whether the maps are equal.
+ * Enqueue an element. O(log N).
+ * @param {T} element Element.
  */
-goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
-  if (this === otherMap) {
-    return true;
-  }
-
-  if (this.count_ != otherMap.getCount()) {
-    return false;
-  }
-
-  var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
-
-  this.cleanupKeysArray_();
-  for (var key, i = 0; key = this.keys_[i]; i++) {
-    if (!equalityFn(this.get(key), otherMap.get(key))) {
-      return false;
-    }
+ol.structs.PriorityQueue.prototype.enqueue = function(element) {
+  goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_));
+  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;
 };
 
 
 /**
- * Default equality test for values.
- * @param {*} a The first value.
- * @param {*} b The second value.
- * @return {boolean} Whether a and b reference the same object.
+ * @return {number} Count.
  */
-goog.structs.Map.defaultEquals = function(a, b) {
-  return a === b;
+ol.structs.PriorityQueue.prototype.getCount = function() {
+  return this.elements_.length;
 };
 
 
 /**
- * @return {boolean} Whether the map is empty.
+ * Gets the index of the left child of the node at the given index.
+ * @param {number} index The index of the node to get the left child for.
+ * @return {number} The index of the left child.
+ * @private
  */
-goog.structs.Map.prototype.isEmpty = function() {
-  return this.count_ == 0;
+ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) {
+  return index * 2 + 1;
 };
 
 
 /**
- * Removes all key-value pairs from the map.
+ * Gets the index of the right child of the node at the given index.
+ * @param {number} index The index of the node to get the right child for.
+ * @return {number} The index of the right child.
+ * @private
  */
-goog.structs.Map.prototype.clear = function() {
-  this.map_ = {};
-  this.keys_.length = 0;
-  this.count_ = 0;
-  this.version_ = 0;
+ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) {
+  return index * 2 + 2;
 };
 
 
 /**
- * Removes a key-value pair based on the key. This is O(logN) amortized due to
- * updating the keys array whenever the count becomes half the size of the keys
- * in the keys array.
- * @param {*} key  The key to remove.
- * @return {boolean} Whether object was removed.
+ * 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
  */
-goog.structs.Map.prototype.remove = function(key) {
-  if (goog.structs.Map.hasKey_(this.map_, key)) {
-    delete this.map_[key];
-    this.count_--;
-    this.version_++;
-
-    // clean up the keys array if the threshhold is hit
-    if (this.keys_.length > 2 * this.count_) {
-      this.cleanupKeysArray_();
-    }
-
-    return true;
-  }
-  return false;
+ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
+  return (index - 1) >> 1;
 };
 
 
 /**
- * Cleans up the temp keys array by removing entries that are no longer in the
- * map.
+ * Make this a heap. O(N).
  * @private
  */
-goog.structs.Map.prototype.cleanupKeysArray_ = function() {
-  if (this.count_ != this.keys_.length) {
-    // First remove keys that are no longer in the map.
-    var srcIndex = 0;
-    var destIndex = 0;
-    while (srcIndex < this.keys_.length) {
-      var key = this.keys_[srcIndex];
-      if (goog.structs.Map.hasKey_(this.map_, key)) {
-        this.keys_[destIndex++] = key;
-      }
-      srcIndex++;
-    }
-    this.keys_.length = destIndex;
-  }
-
-  if (this.count_ != this.keys_.length) {
-    // If the count still isn't correct, that means we have duplicates. This can
-    // happen when the same key is added and removed multiple times. Now we have
-    // to allocate one extra Object to remove the duplicates. This could have
-    // been done in the first pass, but in the common case, we can avoid
-    // allocating an extra object by only doing this when necessary.
-    var seen = {};
-    var srcIndex = 0;
-    var destIndex = 0;
-    while (srcIndex < this.keys_.length) {
-      var key = this.keys_[srcIndex];
-      if (!(goog.structs.Map.hasKey_(seen, key))) {
-        this.keys_[destIndex++] = key;
-        seen[key] = 1;
-      }
-      srcIndex++;
-    }
-    this.keys_.length = destIndex;
+ol.structs.PriorityQueue.prototype.heapify_ = function() {
+  var i;
+  for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
+    this.siftUp_(i);
   }
 };
 
 
 /**
- * Returns the value for the given key.  If the key is not found and the default
- * value is not given this will return {@code undefined}.
- * @param {*} key The key to get the value for.
- * @param {DEFAULT=} opt_val The value to return if no item is found for the
- *     given key, defaults to undefined.
- * @return {V|DEFAULT} The value for the given key.
- * @template DEFAULT
+ * @return {boolean} Is empty.
  */
-goog.structs.Map.prototype.get = function(key, opt_val) {
-  if (goog.structs.Map.hasKey_(this.map_, key)) {
-    return this.map_[key];
-  }
-  return opt_val;
+ol.structs.PriorityQueue.prototype.isEmpty = function() {
+  return this.elements_.length === 0;
 };
 
 
 /**
- * Adds a key-value pair to the map.
- * @param {*} key The key.
- * @param {V} value The value to add.
- * @return {*} Some subclasses return a value.
+ * @param {string} key Key.
+ * @return {boolean} Is key queued.
  */
-goog.structs.Map.prototype.set = function(key, value) {
-  if (!(goog.structs.Map.hasKey_(this.map_, key))) {
-    this.count_++;
-    this.keys_.push(key);
-    // Only change the version if we add a new key.
-    this.version_++;
-  }
-  this.map_[key] = value;
+ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
+  return key in this.queuedElements_;
 };
 
 
 /**
- * Adds multiple key-value pairs from another goog.structs.Map or Object.
- * @param {Object} map  Object containing the data to add.
+ * @param {T} element Element.
+ * @return {boolean} Is queued.
  */
-goog.structs.Map.prototype.addAll = function(map) {
-  var keys, values;
-  if (map instanceof goog.structs.Map) {
-    keys = map.getKeys();
-    values = map.getValues();
-  } else {
-    keys = goog.object.getKeys(map);
-    values = goog.object.getValues(map);
-  }
-  // we could use goog.array.forEach here but I don't want to introduce that
-  // dependency just for this.
-  for (var i = 0; i < keys.length; i++) {
-    this.set(keys[i], values[i]);
-  }
+ol.structs.PriorityQueue.prototype.isQueued = function(element) {
+  return this.isKeyQueued(this.keyFunction_(element));
 };
 
 
 /**
- * Calls the given function on each entry in the map.
- * @param {function(this:T, V, K, goog.structs.Map.<K,V>)} f
- * @param {T=} opt_obj The value of "this" inside f.
- * @template T
+ * @param {number} index The index of the node to move down.
+ * @private
  */
-goog.structs.Map.prototype.forEach = function(f, opt_obj) {
-  var keys = this.getKeys();
-  for (var i = 0; i < keys.length; i++) {
-    var key = keys[i];
-    var value = this.get(key);
-    f.call(opt_obj, value, key, this);
+ol.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);
 };
 
 
 /**
- * Clones a map and returns a new map.
- * @return {!goog.structs.Map} A new map with the same key-value pairs.
+ * @param {number} startIndex The index of the root.
+ * @param {number} index The index of the node to move up.
+ * @private
  */
-goog.structs.Map.prototype.clone = function() {
-  return new goog.structs.Map(this);
+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;
 };
 
 
 /**
- * Returns a new map in which all the keys and values are interchanged
- * (keys become values and values become keys). If multiple keys map to the
- * same value, the chosen transposed value is implementation-dependent.
- *
- * It acts very similarly to {goog.object.transpose(Object)}.
- *
- * @return {!goog.structs.Map} The transposed map.
+ * FIXME empty description for jsdoc
  */
-goog.structs.Map.prototype.transpose = function() {
-  var transposed = new goog.structs.Map();
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    var value = this.map_[key];
-    transposed.set(value, key);
+ol.structs.PriorityQueue.prototype.reprioritize = function() {
+  var priorityFunction = this.priorityFunction_;
+  var elements = this.elements_;
+  var priorities = this.priorities_;
+  var index = 0;
+  var n = elements.length;
+  var element, i, priority;
+  for (i = 0; i < n; ++i) {
+    element = elements[i];
+    priority = priorityFunction(element);
+    if (priority == ol.structs.PriorityQueue.DROP) {
+      delete this.queuedElements_[this.keyFunction_(element)];
+    } else {
+      priorities[index] = priority;
+      elements[index++] = element;
+    }
   }
+  elements.length = index;
+  priorities.length = index;
+  this.heapify_();
+};
+
+goog.provide('ol.TilePriorityFunction');
+goog.provide('ol.TileQueue');
+
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.Coordinate');
+goog.require('ol.structs.PriorityQueue');
+
+
+/**
+ * @typedef {function(ol.Tile, string, ol.Coordinate, number): number}
+ */
+ol.TilePriorityFunction;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.structs.PriorityQueue.<Array>}
+ * @param {ol.TilePriorityFunction} tilePriorityFunction
+ *     Tile priority function.
+ * @param {function(): ?} tileChangeCallback
+ *     Function called on each tile change event.
+ * @struct
+ */
+ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
+
+  goog.base(
+      this,
+      /**
+       * @param {Array} element Element.
+       * @return {number} Priority.
+       */
+      function(element) {
+        return tilePriorityFunction.apply(null, element);
+      },
+      /**
+       * @param {Array} element Element.
+       * @return {string} Key.
+       */
+      function(element) {
+        return /** @type {ol.Tile} */ (element[0]).getKey();
+      });
+
+  /**
+   * @private
+   * @type {function(): ?}
+   */
+  this.tileChangeCallback_ = tileChangeCallback;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.tilesLoading_ = 0;
 
-  return transposed;
 };
+goog.inherits(ol.TileQueue, ol.structs.PriorityQueue);
 
 
 /**
- * @return {!Object} Object representation of the map.
+ * @return {number} Number of tiles loading.
  */
-goog.structs.Map.prototype.toObject = function() {
-  this.cleanupKeysArray_();
-  var obj = {};
-  for (var i = 0; i < this.keys_.length; i++) {
-    var key = this.keys_[i];
-    obj[key] = this.map_[key];
-  }
-  return obj;
+ol.TileQueue.prototype.getTilesLoading = function() {
+  return this.tilesLoading_;
 };
 
 
 /**
- * Returns an iterator that iterates over the keys in the map.  Removal of keys
- * while iterating might have undesired side effects.
- * @return {!goog.iter.Iterator} An iterator over the keys in the map.
+ * @protected
  */
-goog.structs.Map.prototype.getKeyIterator = function() {
-  return this.__iterator__(true);
+ol.TileQueue.prototype.handleTileChange = function() {
+  --this.tilesLoading_;
+  this.tileChangeCallback_();
 };
 
 
 /**
- * Returns an iterator that iterates over the values in the map.  Removal of
- * keys while iterating might have undesired side effects.
- * @return {!goog.iter.Iterator} An iterator over the values in the map.
+ * @param {number} maxTotalLoading Maximum number tiles to load simultaneously.
+ * @param {number} maxNewLoads Maximum number of new tiles to load.
  */
-goog.structs.Map.prototype.getValueIterator = function() {
-  return this.__iterator__(false);
+ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) {
+  var newLoads = Math.min(
+      maxTotalLoading - this.getTilesLoading(), maxNewLoads, this.getCount());
+  var i, tile;
+  for (i = 0; i < newLoads; ++i) {
+    tile = /** @type {ol.Tile} */ (this.dequeue()[0]);
+    goog.events.listenOnce(tile, goog.events.EventType.CHANGE,
+        this.handleTileChange, false, this);
+    tile.load();
+  }
+  this.tilesLoading_ += newLoads;
 };
 
+goog.provide('ol.Kinetic');
+
+goog.require('ol.Coordinate');
+goog.require('ol.PreRenderFunction');
+goog.require('ol.animation');
+
+
 
 /**
- * Returns an iterator that iterates over the values or the keys in the map.
- * This throws an exception if the map was mutated since the iterator was
- * created.
- * @param {boolean=} opt_keys True to iterate over the keys. False to iterate
- *     over the values.  The default value is false.
- * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
+ * @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
  */
-goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
-  // Clean up keys to minimize the risk of iterating over dead keys.
-  this.cleanupKeysArray_();
+ol.Kinetic = function(decay, minVelocity, delay) {
 
-  var i = 0;
-  var keys = this.keys_;
-  var map = this.map_;
-  var version = this.version_;
-  var selfObj = this;
+  /**
+   * @private
+   * @type {number}
+   */
+  this.decay_ = decay;
 
-  var newIter = new goog.iter.Iterator;
-  newIter.next = function() {
-    while (true) {
-      if (version != selfObj.version_) {
-        throw Error('The map has changed since the iterator was created');
-      }
-      if (i >= keys.length) {
-        throw goog.iter.StopIteration;
-      }
-      var key = keys[i++];
-      return opt_keys ? key : map[key];
-    }
-  };
-  return newIter;
+  /**
+   * @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;
 };
 
 
 /**
- * Safe way to test for hasOwnProperty.  It even allows testing for
- * 'hasOwnProperty'.
- * @param {Object} obj The object to test for presence of the given key.
- * @param {*} key The key to check for.
- * @return {boolean} Whether the object has the key.
- * @private
+ * FIXME empty description for jsdoc
  */
-goog.structs.Map.hasKey_ = function(obj, key) {
-  return Object.prototype.hasOwnProperty.call(obj, key);
+ol.Kinetic.prototype.begin = function() {
+  this.points_.length = 0;
+  this.angle_ = 0;
+  this.initialVelocity_ = 0;
 };
 
-// Copyright 2008 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Simple utilities for dealing with URI strings.
- *
- * This is intended to be a lightweight alternative to constructing goog.Uri
- * objects.  Whereas goog.Uri adds several kilobytes to the binary regardless
- * of how much of its functionality you use, this is designed to be a set of
- * mostly-independent utilities so that the compiler includes only what is
- * necessary for the task.  Estimated savings of porting is 5k pre-gzip and
- * 1.5k post-gzip.  To ensure the savings remain, future developers should
- * avoid adding new functionality to existing functions, but instead create
- * new ones and factor out shared code.
- *
- * Many of these utilities have limited functionality, tailored to common
- * cases.  The query parameter utilities assume that the parameter keys are
- * already encoded, since most keys are compile-time alphanumeric strings.  The
- * query parameter mutation utilities also do not tolerate fragment identifiers.
- *
- * By design, these functions can be slower than goog.Uri equivalents.
- * Repeated calls to some of functions may be quadratic in behavior for IE,
- * although the effect is somewhat limited given the 2kb limit.
- *
- * One advantage of the limited functionality here is that this approach is
- * less sensitive to differences in URI encodings than goog.Uri, since these
- * functions modify the strings in place, rather than decoding and
- * re-encoding.
- *
- * Uses features of RFC 3986 for parsing/formatting URIs:
- *   http://www.ietf.org/rfc/rfc3986.txt
- *
- * @author gboyer@google.com (Garrett Boyer) - The "lightened" design.
- * @author msamuel@google.com (Mike Samuel) - Domain knowledge and regexes.
+ * @param {number} x X.
+ * @param {number} y Y.
  */
+ol.Kinetic.prototype.update = function(x, y) {
+  this.points_.push(x, y, goog.now());
+};
 
-goog.provide('goog.uri.utils');
-goog.provide('goog.uri.utils.ComponentIndex');
-goog.provide('goog.uri.utils.QueryArray');
-goog.provide('goog.uri.utils.QueryValue');
-goog.provide('goog.uri.utils.StandardQueryParam');
 
-goog.require('goog.asserts');
-goog.require('goog.string');
-goog.require('goog.userAgent');
+/**
+ * @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 = goog.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_;
+};
 
 
 /**
- * Character codes inlined to avoid object allocations due to charCode.
- * @enum {number}
- * @private
+ * @param {ol.Coordinate} source Source coordinate for the animation.
+ * @return {ol.PreRenderFunction} Pre-render function for kinetic animation.
  */
-goog.uri.utils.CharCode_ = {
-  AMPERSAND: 38,
-  EQUAL: 61,
-  HASH: 35,
-  QUESTION: 63
+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
+  });
 };
 
 
 /**
- * Builds a URI string from already-encoded parts.
- *
- * No encoding is performed.  Any component may be omitted as either null or
- * undefined.
- *
- * @param {?string=} opt_scheme The scheme such as 'http'.
- * @param {?string=} opt_userInfo The user name before the '@'.
- * @param {?string=} opt_domain The domain such as 'www.google.com', already
- *     URI-encoded.
- * @param {(string|number|null)=} opt_port The port number.
- * @param {?string=} opt_path The path, already URI-encoded.  If it is not
- *     empty, it must begin with a slash.
- * @param {?string=} opt_queryData The URI-encoded query data.
- * @param {?string=} opt_fragment The URI-encoded fragment identifier.
- * @return {string} The fully combined URI.
+ * @private
+ * @return {number} Duration of animation (milliseconds).
  */
-goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo,
-    opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) {
-  var out = '';
+ol.Kinetic.prototype.getDuration_ = function() {
+  return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_;
+};
 
-  if (opt_scheme) {
-    out += opt_scheme + ':';
-  }
 
-  if (opt_domain) {
-    out += '//';
+/**
+ * @return {number} Total distance travelled (pixels).
+ */
+ol.Kinetic.prototype.getDistance = function() {
+  return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
+};
 
-    if (opt_userInfo) {
-      out += opt_userInfo + '@';
-    }
 
-    out += opt_domain;
+/**
+ * @return {number} Angle of the kinetic panning animation (radians).
+ */
+ol.Kinetic.prototype.getAngle = function() {
+  return this.angle_;
+};
 
-    if (opt_port) {
-      out += ':' + opt_port;
-    }
-  }
+// FIXME factor out key precondition (shift et. al)
 
-  if (opt_path) {
-    out += opt_path;
-  }
+goog.provide('ol.interaction.Interaction');
+goog.provide('ol.interaction.InteractionProperty');
 
-  if (opt_queryData) {
-    out += '?' + opt_queryData;
-  }
+goog.require('goog.asserts');
+goog.require('ol.MapBrowserEvent');
+goog.require('ol.Object');
+goog.require('ol.animation');
+goog.require('ol.easing');
 
-  if (opt_fragment) {
-    out += '#' + opt_fragment;
-  }
 
-  return out;
+/**
+ * @enum {string}
+ */
+ol.interaction.InteractionProperty = {
+  ACTIVE: 'active'
 };
 
 
+
 /**
- * A regular expression for breaking a URI into its component parts.
- *
- * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B
- * As the "first-match-wins" algorithm is identical to the "greedy"
- * disambiguation method used by POSIX regular expressions, it is natural and
- * commonplace to use a regular expression for parsing the potential five
- * components of a URI reference.
- *
- * The following line is the regular expression for breaking-down a
- * well-formed URI reference into its components.
- *
- * <pre>
- * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
- *  12            3  4          5       6  7        8 9
- * </pre>
+ * @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.
  *
- * The numbers in the second line above are only to assist readability; they
- * indicate the reference points for each subexpression (i.e., each paired
- * parenthesis). We refer to the value matched for subexpression <n> as $<n>.
- * For example, matching the above expression to
- * <pre>
- *     http://www.ics.uci.edu/pub/ietf/uri/#Related
- * </pre>
- * results in the following subexpression matches:
- * <pre>
- *    $1 = http:
- *    $2 = http
- *    $3 = //www.ics.uci.edu
- *    $4 = www.ics.uci.edu
- *    $5 = /pub/ietf/uri/
- *    $6 = <undefined>
- *    $7 = <undefined>
- *    $8 = #Related
- *    $9 = Related
- * </pre>
- * where <undefined> indicates that the component is not present, as is the
- * case for the query component in the above example. Therefore, we can
- * determine the value of the five components as
- * <pre>
- *    scheme    = $2
- *    authority = $4
- *    path      = $5
- *    query     = $7
- *    fragment  = $9
- * </pre>
- *
- * The regular expression has been modified slightly to expose the
- * userInfo, domain, and port separately from the authority.
- * The modified version yields
- * <pre>
- *    $1 = http              scheme
- *    $2 = <undefined>       userInfo -\
- *    $3 = www.ics.uci.edu   domain     | authority
- *    $4 = <undefined>       port     -/
- *    $5 = /pub/ietf/uri/    path
- *    $6 = <undefined>       query without ?
- *    $7 = Related           fragment without #
- * </pre>
- * @type {!RegExp}
- * @private
+ * @constructor
+ * @param {olx.interaction.InteractionOptions} options Options.
+ * @extends {ol.Object}
+ * @api
  */
-goog.uri.utils.splitRe_ = new RegExp(
-    '^' +
-    '(?:' +
-        '([^:/?#.]+)' +                  // scheme - ignore special characters
-                                         // used by other URL parts such as :,
-                                         // ?, /, #, and .
-    ':)?' +
-    '(?://' +
-        '(?:([^/?#]*)@)?' +              // userInfo
-        '([^/#?]*?)' +                   // domain
-        '(?::([0-9]+))?' +               // port
-        '(?=[/#?]|$)' +                  // authority-terminating character
-    ')?' +
-    '([^?#]+)?' +                        // path
-    '(?:\\?([^#]*))?' +                  // query
-    '(?:#(.*))?' +                       // fragment
-    '$');
+ol.interaction.Interaction = function(options) {
 
+  goog.base(this);
+
+  /**
+   * @private
+   * @type {ol.Map}
+   */
+  this.map_ = null;
+
+  this.setActive(true);
+
+  /**
+   * @type {function(ol.MapBrowserEvent):boolean}
+   */
+  this.handleEvent = options.handleEvent;
 
-/**
- * The index of each URI component in the return value of goog.uri.utils.split.
- * @enum {number}
- */
-goog.uri.utils.ComponentIndex = {
-  SCHEME: 1,
-  USER_INFO: 2,
-  DOMAIN: 3,
-  PORT: 4,
-  PATH: 5,
-  QUERY_DATA: 6,
-  FRAGMENT: 7
 };
+goog.inherits(ol.interaction.Interaction, ol.Object);
 
 
 /**
- * Splits a URI into its component parts.
- *
- * Each component can be accessed via the component indices; for example:
- * <pre>
- * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
- * </pre>
- *
- * @param {string} uri The URI string to examine.
- * @return {!Array.<string|undefined>} Each component still URI-encoded.
- *     Each component that is present will contain the encoded value, whereas
- *     components that are not present will be undefined or empty, depending
- *     on the browser's regular expression implementation.  Never null, since
- *     arbitrary strings may still look like path names.
+ * @return {boolean} `true` if the interaction is active, `false` otherwise.
+ * @observable
+ * @api
  */
-goog.uri.utils.split = function(uri) {
-  goog.uri.utils.phishingProtection_();
-
-  // See @return comment -- never null.
-  return /** @type {!Array.<string|undefined>} */ (
-      uri.match(goog.uri.utils.splitRe_));
+ol.interaction.Interaction.prototype.getActive = function() {
+  return /** @type {boolean} */ (
+      this.get(ol.interaction.InteractionProperty.ACTIVE));
 };
+goog.exportProperty(
+    ol.interaction.Interaction.prototype,
+    'getActive',
+    ol.interaction.Interaction.prototype.getActive);
 
 
 /**
- * Safari has a nasty bug where if you have an http URL with a username, e.g.,
- * http://evil.com%2F@google.com/
- * Safari will report that window.location.href is
- * http://evil.com/google.com/
- * so that anyone who tries to parse the domain of that URL will get
- * the wrong domain. We've seen exploits where people use this to trick
- * Safari into loading resources from evil domains.
- *
- * To work around this, we run a little "Safari phishing check", and throw
- * an exception if we see this happening.
- *
- * There is no convenient place to put this check. We apply it to
- * anyone doing URI parsing on Webkit. We're not happy about this, but
- * it fixes the problem.
- *
- * This should be removed once Safari fixes their bug.
- *
- * Exploit reported by Masato Kinugawa.
- *
- * @type {boolean}
- * @private
+ * Get the map associated with this interaction.
+ * @return {ol.Map} Map.
  */
-goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT;
+ol.interaction.Interaction.prototype.getMap = function() {
+  return this.map_;
+};
 
 
 /**
- * Check to see if the user is being phished.
- * @private
+ * Activate or deactivate the interaction.
+ * @param {boolean} active Active.
+ * @observable
+ * @api
  */
-goog.uri.utils.phishingProtection_ = function() {
-  if (goog.uri.utils.needsPhishingProtection_) {
-    // Turn protection off, so that we don't recurse.
-    goog.uri.utils.needsPhishingProtection_ = false;
-
-    // Use quoted access, just in case the user isn't using location externs.
-    var location = goog.global['location'];
-    if (location) {
-      var href = location['href'];
-      if (href) {
-        var domain = goog.uri.utils.getDomain(href);
-        if (domain && domain != location['hostname']) {
-          // Phishing attack
-          goog.uri.utils.needsPhishingProtection_ = true;
-          throw Error();
-        }
-      }
-    }
-  }
+ol.interaction.Interaction.prototype.setActive = function(active) {
+  this.set(ol.interaction.InteractionProperty.ACTIVE, active);
 };
+goog.exportProperty(
+    ol.interaction.Interaction.prototype,
+    'setActive',
+    ol.interaction.Interaction.prototype.setActive);
 
 
 /**
- * @param {?string} uri A possibly null string.
- * @return {?string} The string URI-decoded, or null if uri is null.
- * @private
+ * 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.
  */
-goog.uri.utils.decodeIfPossible_ = function(uri) {
-  return uri && decodeURIComponent(uri);
+ol.interaction.Interaction.prototype.setMap = function(map) {
+  this.map_ = map;
 };
 
 
 /**
- * Gets a URI component by index.
- *
- * It is preferred to use the getPathEncoded() variety of functions ahead,
- * since they are more readable.
- *
- * @param {goog.uri.utils.ComponentIndex} componentIndex The component index.
- * @param {string} uri The URI to examine.
- * @return {?string} The still-encoded component, or null if the component
- *     is not present.
- * @private
+ * @param {ol.Map} map Map.
+ * @param {ol.View} view View.
+ * @param {ol.Coordinate} delta Delta.
+ * @param {number=} opt_duration Duration.
  */
-goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) {
-  // Convert undefined, null, and empty string into null.
-  return goog.uri.utils.split(uri)[componentIndex] || null;
+ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) {
+  var currentCenter = view.getCenter();
+  if (goog.isDef(currentCenter)) {
+    if (goog.isDef(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 {string} uri The URI to examine.
- * @return {?string} The protocol or scheme, or null if none.  Does not
- *     include trailing colons or slashes.
+ * @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.
  */
-goog.uri.utils.getScheme = function(uri) {
-  return goog.uri.utils.getComponentByIndex_(
-      goog.uri.utils.ComponentIndex.SCHEME, uri);
+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);
 };
 
 
 /**
- * Gets the effective scheme for the URL.  If the URL is relative then the
- * scheme is derived from the page's location.
- * @param {string} uri The URI to examine.
- * @return {string} The protocol or scheme, always lower case.
+ * @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.
  */
-goog.uri.utils.getEffectiveScheme = function(uri) {
-  var scheme = goog.uri.utils.getScheme(uri);
-  if (!scheme && self.location) {
-    var protocol = self.location.protocol;
-    scheme = protocol.substr(0, protocol.length - 1);
+ol.interaction.Interaction.rotateWithoutConstraints =
+    function(map, view, rotation, opt_anchor, opt_duration) {
+  if (goog.isDefAndNotNull(rotation)) {
+    var currentRotation = view.getRotation();
+    var currentCenter = view.getCenter();
+    if (goog.isDef(currentRotation) && goog.isDef(currentCenter) &&
+        goog.isDef(opt_duration) && opt_duration > 0) {
+      map.beforeRender(ol.animation.rotate({
+        rotation: currentRotation,
+        duration: opt_duration,
+        easing: ol.easing.easeOut
+      }));
+      if (goog.isDef(opt_anchor)) {
+        map.beforeRender(ol.animation.pan({
+          source: currentCenter,
+          duration: opt_duration,
+          easing: ol.easing.easeOut
+        }));
+      }
+    }
+    view.rotate(rotation, opt_anchor);
   }
-  // NOTE: When called from a web worker in Firefox 3.5, location maybe null.
-  // All other browsers with web workers support self.location from the worker.
-  return scheme ? scheme.toLowerCase() : '';
 };
 
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?string} The user name still encoded, or null if none.
+ * @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.
  */
-goog.uri.utils.getUserInfoEncoded = function(uri) {
-  return goog.uri.utils.getComponentByIndex_(
-      goog.uri.utils.ComponentIndex.USER_INFO, uri);
+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 {string} uri The URI to examine.
- * @return {?string} The decoded user info, or null if none.
+ * @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.
  */
-goog.uri.utils.getUserInfo = function(uri) {
-  return goog.uri.utils.decodeIfPossible_(
-      goog.uri.utils.getUserInfoEncoded(uri));
+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 {string} uri The URI to examine.
- * @return {?string} The domain name still encoded, or null if none.
+ * @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.
  */
-goog.uri.utils.getDomainEncoded = function(uri) {
-  return goog.uri.utils.getComponentByIndex_(
-      goog.uri.utils.ComponentIndex.DOMAIN, uri);
+ol.interaction.Interaction.zoomWithoutConstraints =
+    function(map, view, resolution, opt_anchor, opt_duration) {
+  if (goog.isDefAndNotNull(resolution)) {
+    var currentResolution = view.getResolution();
+    var currentCenter = view.getCenter();
+    if (goog.isDef(currentResolution) && goog.isDef(currentCenter) &&
+        goog.isDef(opt_duration) && opt_duration > 0) {
+      map.beforeRender(ol.animation.zoom({
+        resolution: currentResolution,
+        duration: opt_duration,
+        easing: ol.easing.easeOut
+      }));
+      if (goog.isDef(opt_anchor)) {
+        map.beforeRender(ol.animation.pan({
+          source: currentCenter,
+          duration: opt_duration,
+          easing: ol.easing.easeOut
+        }));
+      }
+    }
+    if (goog.isDefAndNotNull(opt_anchor)) {
+      var center = view.calculateCenterZoom(resolution, opt_anchor);
+      view.setCenter(center);
+    }
+    view.setResolution(resolution);
+  }
 };
 
+goog.provide('ol.interaction.DoubleClickZoom');
+
+goog.require('goog.asserts');
+goog.require('ol.MapBrowserEvent');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.interaction.Interaction');
+
+
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?string} The decoded domain, or null if none.
+ * @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
  */
-goog.uri.utils.getDomain = function(uri) {
-  return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getDomainEncoded(uri));
+ol.interaction.DoubleClickZoom = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.delta_ = goog.isDef(options.delta) ? options.delta : 1;
+
+  goog.base(this, {
+    handleEvent: ol.interaction.DoubleClickZoom.handleEvent
+  });
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
+
 };
+goog.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction);
 
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?number} The port number, or null if none.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.DoubleClickZoom}
+ * @api
  */
-goog.uri.utils.getPort = function(uri) {
-  // Coerce to a number.  If the result of getComponentByIndex_ is null or
-  // non-numeric, the number coersion yields NaN.  This will then return
-  // null for all non-numeric cases (though also zero, which isn't a relevant
-  // port number).
-  return Number(goog.uri.utils.getComponentByIndex_(
-      goog.uri.utils.ComponentIndex.PORT, uri)) || null;
+ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) {
+  var stopEvent = false;
+  var browserEvent = mapBrowserEvent.browserEvent;
+  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) {
+    var map = mapBrowserEvent.map;
+    var anchor = mapBrowserEvent.coordinate;
+    var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_;
+    var view = map.getView();
+    goog.asserts.assert(!goog.isNull(view));
+    ol.interaction.Interaction.zoomByDelta(
+        map, view, delta, anchor, this.duration_);
+    mapBrowserEvent.preventDefault();
+    stopEvent = true;
+  }
+  return !stopEvent;
 };
 
+goog.provide('ol.events.ConditionType');
+goog.provide('ol.events.condition');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.TagName');
+goog.require('goog.functions');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.MapBrowserPointerEvent');
+
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?string} The path still encoded, or null if none. Includes the
- *     leading slash, if any.
+ * A function that takes an {@link ol.MapBrowserEvent} and returns a
+ * `{boolean}`. If the condition is met, true should be returned.
+ *
+ * @typedef {function(ol.MapBrowserEvent): boolean}
+ * @api stable
  */
-goog.uri.utils.getPathEncoded = function(uri) {
-  return goog.uri.utils.getComponentByIndex_(
-      goog.uri.utils.ComponentIndex.PATH, uri);
-};
+ol.events.ConditionType;
 
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?string} The decoded path, or null if none.  Includes the leading
- *     slash, if any.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if only the alt key is pressed.
+ * @api stable
  */
-goog.uri.utils.getPath = function(uri) {
-  return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getPathEncoded(uri));
+ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
+  var browserEvent = mapBrowserEvent.browserEvent;
+  return (
+      browserEvent.altKey &&
+      !browserEvent.platformModifierKey &&
+      !browserEvent.shiftKey);
 };
 
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?string} The query data still encoded, or null if none.  Does not
- *     include the question mark itself.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if only the alt and shift keys are pressed.
+ * @api stable
  */
-goog.uri.utils.getQueryData = function(uri) {
-  return goog.uri.utils.getComponentByIndex_(
-      goog.uri.utils.ComponentIndex.QUERY_DATA, uri);
+ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
+  var browserEvent = mapBrowserEvent.browserEvent;
+  return (
+      browserEvent.altKey &&
+      !browserEvent.platformModifierKey &&
+      browserEvent.shiftKey);
 };
 
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?string} The fragment identifier, or null if none.  Does not
- *     include the hash mark itself.
+ * Always true.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True.
+ * @function
+ * @api stable
  */
-goog.uri.utils.getFragmentEncoded = function(uri) {
-  // The hash mark may not appear in any other part of the URL.
-  var hashIndex = uri.indexOf('#');
-  return hashIndex < 0 ? null : uri.substr(hashIndex + 1);
-};
+ol.events.condition.always = goog.functions.TRUE;
 
 
 /**
- * @param {string} uri The URI to examine.
- * @param {?string} fragment The encoded fragment identifier, or null if none.
- *     Does not include the hash mark itself.
- * @return {string} The URI with the fragment set.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if the event is a map `click` event.
+ * @api stable
  */
-goog.uri.utils.setFragmentEncoded = function(uri, fragment) {
-  return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : '');
+ol.events.condition.click = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK;
 };
 
 
 /**
- * @param {string} uri The URI to examine.
- * @return {?string} The decoded fragment identifier, or null if none.  Does
- *     not include the hash mark.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if the browser event is a `mousemove` event.
+ * @api
  */
-goog.uri.utils.getFragment = function(uri) {
-  return goog.uri.utils.decodeIfPossible_(
-      goog.uri.utils.getFragmentEncoded(uri));
+ol.events.condition.mouseMove = function(mapBrowserEvent) {
+  return mapBrowserEvent.originalEvent.type == 'mousemove';
 };
 
 
 /**
- * Extracts everything up to the port of the URI.
- * @param {string} uri The URI string.
- * @return {string} Everything up to and including the port.
+ * Always false.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} False.
+ * @function
+ * @api stable
  */
-goog.uri.utils.getHost = function(uri) {
-  var pieces = goog.uri.utils.split(uri);
-  return goog.uri.utils.buildFromEncodedParts(
-      pieces[goog.uri.utils.ComponentIndex.SCHEME],
-      pieces[goog.uri.utils.ComponentIndex.USER_INFO],
-      pieces[goog.uri.utils.ComponentIndex.DOMAIN],
-      pieces[goog.uri.utils.ComponentIndex.PORT]);
-};
+ol.events.condition.never = goog.functions.FALSE;
 
 
 /**
- * Extracts the path of the URL and everything after.
- * @param {string} uri The URI string.
- * @return {string} The URI, starting at the path and including the query
- *     parameters and fragment identifier.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if the event is a map `singleclick` event.
+ * @api stable
  */
-goog.uri.utils.getPathAndAfter = function(uri) {
-  var pieces = goog.uri.utils.split(uri);
-  return goog.uri.utils.buildFromEncodedParts(null, null, null, null,
-      pieces[goog.uri.utils.ComponentIndex.PATH],
-      pieces[goog.uri.utils.ComponentIndex.QUERY_DATA],
-      pieces[goog.uri.utils.ComponentIndex.FRAGMENT]);
+ol.events.condition.singleClick = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK;
 };
 
 
 /**
- * Gets the URI with the fragment identifier removed.
- * @param {string} uri The URI to examine.
- * @return {string} Everything preceding the hash mark.
- */
-goog.uri.utils.removeFragment = function(uri) {
-  // The hash mark may not appear in any other part of the URL.
-  var hashIndex = uri.indexOf('#');
-  return hashIndex < 0 ? uri : uri.substr(0, hashIndex);
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True only if there no modifier keys are pressed.
+ * @api stable
+ */
+ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
+  var browserEvent = mapBrowserEvent.browserEvent;
+  return (
+      !browserEvent.altKey &&
+      !browserEvent.platformModifierKey &&
+      !browserEvent.shiftKey);
 };
 
 
 /**
- * Ensures that two URI's have the exact same domain, scheme, and port.
- *
- * Unlike the version in goog.Uri, this checks protocol, and therefore is
- * suitable for checking against the browser's same-origin policy.
- *
- * @param {string} uri1 The first URI.
- * @param {string} uri2 The second URI.
- * @return {boolean} Whether they have the same domain and port.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if only the platform modifier key is pressed.
+ * @api stable
  */
-goog.uri.utils.haveSameDomain = function(uri1, uri2) {
-  var pieces1 = goog.uri.utils.split(uri1);
-  var pieces2 = goog.uri.utils.split(uri2);
-  return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
-             pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
-         pieces1[goog.uri.utils.ComponentIndex.SCHEME] ==
-             pieces2[goog.uri.utils.ComponentIndex.SCHEME] &&
-         pieces1[goog.uri.utils.ComponentIndex.PORT] ==
-             pieces2[goog.uri.utils.ComponentIndex.PORT];
+ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
+  var browserEvent = mapBrowserEvent.browserEvent;
+  return (
+      !browserEvent.altKey &&
+      browserEvent.platformModifierKey &&
+      !browserEvent.shiftKey);
 };
 
 
 /**
- * Asserts that there are no fragment or query identifiers, only in uncompiled
- * mode.
- * @param {string} uri The URI to examine.
- * @private
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if only the shift key is pressed.
+ * @api stable
  */
-goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) {
-  // NOTE: would use goog.asserts here, but jscompiler doesn't know that
-  // indexOf has no side effects.
-  if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) {
-    throw Error('goog.uri.utils: Fragment or query identifiers are not ' +
-        'supported: [' + uri + ']');
-  }
+ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
+  var browserEvent = mapBrowserEvent.browserEvent;
+  return (
+      !browserEvent.altKey &&
+      !browserEvent.platformModifierKey &&
+      browserEvent.shiftKey);
 };
 
 
 /**
- * Supported query parameter values by the parameter serializing utilities.
- *
- * If a value is null or undefined, the key-value pair is skipped, as an easy
- * way to omit parameters conditionally.  Non-array parameters are converted
- * to a string and URI encoded.  Array values are expanded into multiple
- * &key=value pairs, with each element stringized and URI-encoded.
- *
- * @typedef {*}
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True only if the target element is not editable.
+ * @api
  */
-goog.uri.utils.QueryValue;
+ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
+  var target = mapBrowserEvent.browserEvent.target;
+  goog.asserts.assertInstanceof(target, Element);
+  var tagName = target.tagName;
+  return (
+      tagName !== goog.dom.TagName.INPUT &&
+      tagName !== goog.dom.TagName.SELECT &&
+      tagName !== goog.dom.TagName.TEXTAREA);
+};
 
 
 /**
- * An array representing a set of query parameters with alternating keys
- * and values.
- *
- * Keys are assumed to be URI encoded already and live at even indices.  See
- * goog.uri.utils.QueryValue for details on how parameter values are encoded.
- *
- * Example:
- * <pre>
- * var data = [
- *   // Simple param: ?name=BobBarker
- *   'name', 'BobBarker',
- *   // Conditional param -- may be omitted entirely.
- *   'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null,
- *   // Multi-valued param: &house=LosAngeles&house=NewYork&house=null
- *   'house', ['LosAngeles', 'NewYork', null]
- * ];
- * </pre>
- *
- * @typedef {!Array.<string|goog.uri.utils.QueryValue>}
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True if the event originates from a mouse device.
+ * @api stable
  */
-goog.uri.utils.QueryArray;
+ol.events.condition.mouseOnly = function(mapBrowserEvent) {
+  goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent);
+  /* pointerId must be 1 for mouse devices,
+   * see: http://www.w3.org/Submission/pointer-events/#pointerevent-interface
+   */
+  return mapBrowserEvent.pointerEvent.pointerId == 1;
+};
 
+goog.provide('ol.interaction.Pointer');
 
-/**
- * Appends a URI and query data in a string buffer with special preconditions.
- *
- * Internal implementation utility, performing very few object allocations.
- *
- * @param {!Array.<string|undefined>} buffer A string buffer.  The first element
- *     must be the base URI, and may have a fragment identifier.  If the array
- *     contains more than one element, the second element must be an ampersand,
- *     and may be overwritten, depending on the base URI.  Undefined elements
- *     are treated as empty-string.
- * @return {string} The concatenated URI and query data.
- * @private
- */
-goog.uri.utils.appendQueryData_ = function(buffer) {
-  if (buffer[1]) {
-    // At least one query parameter was added.  We need to check the
-    // punctuation mark, which is currently an ampersand, and also make sure
-    // there aren't any interfering fragment identifiers.
-    var baseUri = /** @type {string} */ (buffer[0]);
-    var hashIndex = baseUri.indexOf('#');
-    if (hashIndex >= 0) {
-      // Move the fragment off the base part of the URI into the end.
-      buffer.push(baseUri.substr(hashIndex));
-      buffer[0] = baseUri = baseUri.substr(0, hashIndex);
-    }
-    var questionIndex = baseUri.indexOf('?');
-    if (questionIndex < 0) {
-      // No question mark, so we need a question mark instead of an ampersand.
-      buffer[1] = '?';
-    } else if (questionIndex == baseUri.length - 1) {
-      // Question mark is the very last character of the existing URI, so don't
-      // append an additional delimiter.
-      buffer[1] = undefined;
-    }
-  }
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.Pixel');
+goog.require('ol.interaction.Interaction');
 
-  return buffer.join('');
-};
 
 
 /**
- * Appends key=value pairs to an array, supporting multi-valued objects.
- * @param {string} key The key prefix.
- * @param {goog.uri.utils.QueryValue} value The value to serialize.
- * @param {!Array.<string>} pairs The array to which the 'key=value' strings
- *     should be appended.
- * @private
+ * @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
  */
-goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) {
-  if (goog.isArray(value)) {
-    // Convince the compiler it's an array.
-    goog.asserts.assertArray(value);
-    for (var j = 0; j < value.length; j++) {
-      // Convert to string explicitly, to short circuit the null and array
-      // logic in this function -- this ensures that null and undefined get
-      // written as literal 'null' and 'undefined', and arrays don't get
-      // expanded out but instead encoded in the default way.
-      goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs);
-    }
-  } else if (value != null) {
-    // Skip a top-level null or undefined entirely.
-    pairs.push('&', key,
-        // Check for empty string. Zero gets encoded into the url as literal
-        // strings.  For empty string, skip the equal sign, to be consistent
-        // with UriBuilder.java.
-        value === '' ? '' : '=',
-        goog.string.urlEncode(value));
-  }
-};
+ol.interaction.Pointer = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * Builds a buffer of query data from a sequence of alternating keys and values.
- *
- * @param {!Array.<string|undefined>} buffer A string buffer to append to.  The
- *     first element appended will be an '&', and may be replaced by the caller.
- * @param {goog.uri.utils.QueryArray|Arguments} keysAndValues An array with
- *     alternating keys and values -- see the typedef.
- * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
- * @return {!Array.<string|undefined>} The buffer argument.
- * @private
- */
-goog.uri.utils.buildQueryDataBuffer_ = function(
-    buffer, keysAndValues, opt_startIndex) {
-  goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0),
-      0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.');
+  var handleEvent = goog.isDef(options.handleEvent) ?
+      options.handleEvent : ol.interaction.Pointer.handleEvent;
 
-  for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) {
-    goog.uri.utils.appendKeyValuePairs_(
-        keysAndValues[i], keysAndValues[i + 1], buffer);
-  }
+  goog.base(this, {
+    handleEvent: handleEvent
+  });
 
-  return buffer;
-};
+  /**
+   * @type {function(ol.MapBrowserPointerEvent):boolean}
+   * @private
+   */
+  this.handleDownEvent_ = goog.isDef(options.handleDownEvent) ?
+      options.handleDownEvent : ol.interaction.Pointer.handleDownEvent;
 
+  /**
+   * @type {function(ol.MapBrowserPointerEvent)}
+   * @private
+   */
+  this.handleDragEvent_ = goog.isDef(options.handleDragEvent) ?
+      options.handleDragEvent : ol.interaction.Pointer.handleDragEvent;
 
-/**
- * Builds a query data string from a sequence of alternating keys and values.
- * Currently generates "&key&" for empty args.
- *
- * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and
- *     values.  See the typedef.
- * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
- * @return {string} The encoded query string, in the form 'a=1&b=2'.
- */
-goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) {
-  var buffer = goog.uri.utils.buildQueryDataBuffer_(
-      [], keysAndValues, opt_startIndex);
-  buffer[0] = ''; // Remove the leading ampersand.
-  return buffer.join('');
-};
+  /**
+   * @type {function(ol.MapBrowserPointerEvent)}
+   * @private
+   */
+  this.handleMoveEvent_ = goog.isDef(options.handleMoveEvent) ?
+      options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent;
 
+  /**
+   * @type {function(ol.MapBrowserPointerEvent):boolean}
+   * @private
+   */
+  this.handleUpEvent_ = goog.isDef(options.handleUpEvent) ?
+      options.handleUpEvent : ol.interaction.Pointer.handleUpEvent;
 
-/**
- * Builds a buffer of query data from a map.
- *
- * @param {!Array.<string|undefined>} buffer A string buffer to append to.  The
- *     first element appended will be an '&', and may be replaced by the caller.
- * @param {Object.<goog.uri.utils.QueryValue>} map An object where keys are
- *     URI-encoded parameter keys, and the values conform to the contract
- *     specified in the goog.uri.utils.QueryValue typedef.
- * @return {!Array.<string|undefined>} The buffer argument.
- * @private
- */
-goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) {
-  for (var key in map) {
-    goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer);
-  }
+  /**
+   * @type {boolean}
+   * @protected
+   */
+  this.handlingDownUpSequence = false;
 
-  return buffer;
-};
+  /**
+   * @type {Object.<number, ol.pointer.PointerEvent>}
+   * @private
+   */
+  this.trackedPointers_ = {};
 
+  /**
+   * @type {Array.<ol.pointer.PointerEvent>}
+   * @protected
+   */
+  this.targetPointers = [];
 
-/**
- * Builds a query data string from a map.
- * Currently generates "&key&" for empty args.
- *
- * @param {Object} map An object where keys are URI-encoded parameter keys,
- *     and the values are arbitrary types or arrays.  Keys with a null value
- *     are dropped.
- * @return {string} The encoded query string, in the form 'a=1&b=2'.
- */
-goog.uri.utils.buildQueryDataFromMap = function(map) {
-  var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map);
-  buffer[0] = '';
-  return buffer.join('');
 };
+goog.inherits(ol.interaction.Pointer, ol.interaction.Interaction);
 
 
 /**
- * Appends URI parameters to an existing URI.
- *
- * The variable arguments may contain alternating keys and values.  Keys are
- * assumed to be already URI encoded.  The values should not be URI-encoded,
- * and will instead be encoded by this function.
- * <pre>
- * appendParams('http://www.foo.com?existing=true',
- *     'key1', 'value1',
- *     'key2', 'value?willBeEncoded',
- *     'key3', ['valueA', 'valueB', 'valueC'],
- *     'key4', null);
- * result: 'http://www.foo.com?existing=true&' +
- *     'key1=value1&' +
- *     'key2=value%3FwillBeEncoded&' +
- *     'key3=valueA&key3=valueB&key3=valueC'
- * </pre>
- *
- * A single call to this function will not exhibit quadratic behavior in IE,
- * whereas multiple repeated calls may, although the effect is limited by
- * fact that URL's generally can't exceed 2kb.
- *
- * @param {string} uri The original URI, which may already have query data.
- * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args
- *     An array or argument list conforming to goog.uri.utils.QueryArray.
- * @return {string} The URI with all query parameters added.
+ * @param {Array.<ol.pointer.PointerEvent>} pointerEvents
+ * @return {ol.Pixel} Centroid pixel.
  */
-goog.uri.utils.appendParams = function(uri, var_args) {
-  return goog.uri.utils.appendQueryData_(
-      arguments.length == 2 ?
-      goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) :
-      goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1));
+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];
 };
 
 
 /**
- * Appends query parameters from a map.
- *
- * @param {string} uri The original URI, which may already have query data.
- * @param {Object} map An object where keys are URI-encoded parameter keys,
- *     and the values are arbitrary types or arrays.  Keys with a null value
- *     are dropped.
- * @return {string} The new parameters.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Whether the event is a pointerdown, pointerdrag
+ *     or pointerup event.
+ * @private
  */
-goog.uri.utils.appendParamsFromMap = function(uri, map) {
-  return goog.uri.utils.appendQueryData_(
-      goog.uri.utils.buildQueryDataBufferFromMap_([uri], map));
+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);
 };
 
 
 /**
- * Appends a single URI parameter.
- *
- * Repeated calls to this can exhibit quadratic behavior in IE6 due to the
- * way string append works, though it should be limited given the 2kb limit.
- *
- * @param {string} uri The original URI, which may already have query data.
- * @param {string} key The key, which must already be URI encoded.
- * @param {*=} opt_value The value, which will be stringized and encoded
- *     (assumed not already to be encoded).  If omitted, undefined, or null, the
- *     key will be added as a valueless parameter.
- * @return {string} The URI with the query parameter added.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @private
  */
-goog.uri.utils.appendParam = function(uri, key, opt_value) {
-  var paramArr = [uri, '&', key];
-  if (goog.isDefAndNotNull(opt_value)) {
-    paramArr.push('=', goog.string.urlEncode(opt_value));
+ol.interaction.Pointer.prototype.updateTrackedPointers_ =
+    function(mapBrowserEvent) {
+  if (this.isPointerDraggingEvent_(mapBrowserEvent)) {
+    var event = mapBrowserEvent.pointerEvent;
+
+    if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) {
+      delete this.trackedPointers_[event.pointerId];
+    } else if (mapBrowserEvent.type ==
+        ol.MapBrowserEvent.EventType.POINTERDOWN) {
+      this.trackedPointers_[event.pointerId] = event;
+    } else if (event.pointerId in this.trackedPointers_) {
+      // update only when there was a pointerdown event for this pointer
+      this.trackedPointers_[event.pointerId] = event;
+    }
+    this.targetPointers = goog.object.getValues(this.trackedPointers_);
   }
-  return goog.uri.utils.appendQueryData_(paramArr);
 };
 
 
 /**
- * Finds the next instance of a query parameter with the specified name.
- *
- * Does not instantiate any objects.
- *
- * @param {string} uri The URI to search.  May contain a fragment identifier
- *     if opt_hashIndex is specified.
- * @param {number} startIndex The index to begin searching for the key at.  A
- *     match may be found even if this is one character after the ampersand.
- * @param {string} keyEncoded The URI-encoded key.
- * @param {number} hashOrEndIndex Index to stop looking at.  If a hash
- *     mark is present, it should be its index, otherwise it should be the
- *     length of the string.
- * @return {number} The position of the first character in the key's name,
- *     immediately after either a question mark or a dot.
- * @private
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
  */
-goog.uri.utils.findParam_ = function(
-    uri, startIndex, keyEncoded, hashOrEndIndex) {
-  var index = startIndex;
-  var keyLength = keyEncoded.length;
+ol.interaction.Pointer.handleDragEvent = goog.nullFunction;
 
-  // Search for the key itself and post-filter for surronuding punctuation,
-  // rather than expensively building a regexp.
-  while ((index = uri.indexOf(keyEncoded, index)) >= 0 &&
-      index < hashOrEndIndex) {
-    var precedingChar = uri.charCodeAt(index - 1);
-    // Ensure that the preceding character is '&' or '?'.
-    if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND ||
-        precedingChar == goog.uri.utils.CharCode_.QUESTION) {
-      // Ensure the following character is '&', '=', '#', or NaN
-      // (end of string).
-      var followingChar = uri.charCodeAt(index + keyLength);
-      if (!followingChar ||
-          followingChar == goog.uri.utils.CharCode_.EQUAL ||
-          followingChar == goog.uri.utils.CharCode_.AMPERSAND ||
-          followingChar == goog.uri.utils.CharCode_.HASH) {
-        return index;
-      }
-    }
-    index += keyLength + 1;
-  }
 
-  return -1;
-};
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
+ */
+ol.interaction.Pointer.handleUpEvent = goog.functions.FALSE;
 
 
 /**
- * Regular expression for finding a hash mark or end of string.
- * @type {RegExp}
- * @private
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
  */
-goog.uri.utils.hashOrEndRe_ = /#|$/;
+ol.interaction.Pointer.handleDownEvent = goog.functions.FALSE;
 
 
 /**
- * Determines if the URI contains a specific key.
- *
- * Performs no object instantiations.
- *
- * @param {string} uri The URI to process.  May contain a fragment
- *     identifier.
- * @param {string} keyEncoded The URI-encoded key.  Case-sensitive.
- * @return {boolean} Whether the key is present.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
  */
-goog.uri.utils.hasParam = function(uri, keyEncoded) {
-  return goog.uri.utils.findParam_(uri, 0, keyEncoded,
-      uri.search(goog.uri.utils.hashOrEndRe_)) >= 0;
-};
+ol.interaction.Pointer.handleMoveEvent = goog.nullFunction;
 
 
 /**
- * Gets the first value of a query parameter.
- * @param {string} uri The URI to process.  May contain a fragment.
- * @param {string} keyEncoded The URI-encoded key.  Case-sensitive.
- * @return {?string} The first value of the parameter (URI-decoded), or null
- *     if the parameter is not found.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.Pointer}
+ * @api
  */
-goog.uri.utils.getParamValue = function(uri, keyEncoded) {
-  var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
-  var foundIndex = goog.uri.utils.findParam_(
-      uri, 0, keyEncoded, hashOrEndIndex);
+ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) {
+  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
+    return true;
+  }
 
-  if (foundIndex < 0) {
-    return null;
-  } else {
-    var endPosition = uri.indexOf('&', foundIndex);
-    if (endPosition < 0 || endPosition > hashOrEndIndex) {
-      endPosition = hashOrEndIndex;
+  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);
     }
-    // Progress forth to the end of the "key=" or "key&" substring.
-    foundIndex += keyEncoded.length + 1;
-    // Use substr, because it (unlike substring) will return empty string
-    // if foundIndex > endPosition.
-    return goog.string.urlDecode(
-        uri.substr(foundIndex, endPosition - foundIndex));
   }
+  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;
 };
 
 
 /**
- * Gets all values of a query parameter.
- * @param {string} uri The URI to process.  May contain a framgnet.
- * @param {string} keyEncoded The URI-encoded key.  Case-snsitive.
- * @return {!Array.<string>} All URI-decoded values with the given key.
- *     If the key is not found, this will have length 0, but never be null.
+ * 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
  */
-goog.uri.utils.getParamValues = function(uri, keyEncoded) {
-  var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
-  var position = 0;
-  var foundIndex;
-  var result = [];
+ol.interaction.Pointer.prototype.shouldStopEvent = goog.functions.identity;
 
-  while ((foundIndex = goog.uri.utils.findParam_(
-      uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
-    // Find where this parameter ends, either the '&' or the end of the
-    // query parameters.
-    position = uri.indexOf('&', foundIndex);
-    if (position < 0 || position > hashOrEndIndex) {
-      position = hashOrEndIndex;
-    }
+goog.provide('ol.interaction.DragPan');
 
-    // Progress forth to the end of the "key=" or "key&" substring.
-    foundIndex += keyEncoded.length + 1;
-    // Use substr, because it (unlike substring) will return empty string
-    // if foundIndex > position.
-    result.push(goog.string.urlDecode(uri.substr(
-        foundIndex, position - foundIndex)));
-  }
+goog.require('goog.asserts');
+goog.require('ol.Kinetic');
+goog.require('ol.Pixel');
+goog.require('ol.PreRenderFunction');
+goog.require('ol.ViewHint');
+goog.require('ol.coordinate');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Pointer');
 
-  return result;
-};
 
 
 /**
- * Regexp to find trailing question marks and ampersands.
- * @type {RegExp}
- * @private
- */
-goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/;
-
-
-/**
- * Removes all instances of a query parameter.
- * @param {string} uri The URI to process.  Must not contain a fragment.
- * @param {string} keyEncoded The URI-encoded key.
- * @return {string} The URI with all instances of the parameter removed.
+ * @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
  */
-goog.uri.utils.removeParam = function(uri, keyEncoded) {
-  var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
-  var position = 0;
-  var foundIndex;
-  var buffer = [];
+ol.interaction.DragPan = function(opt_options) {
 
-  // Look for a query parameter.
-  while ((foundIndex = goog.uri.utils.findParam_(
-      uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
-    // Get the portion of the query string up to, but not including, the ?
-    // or & starting the parameter.
-    buffer.push(uri.substring(position, foundIndex));
-    // Progress to immediately after the '&'.  If not found, go to the end.
-    // Avoid including the hash mark.
-    position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex,
-        hashOrEndIndex);
-  }
+  goog.base(this, {
+    handleDownEvent: ol.interaction.DragPan.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragPan.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragPan.handleUpEvent_
+  });
 
-  // Append everything that is remaining.
-  buffer.push(uri.substr(position));
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  // Join the buffer, and remove trailing punctuation that remains.
-  return buffer.join('').replace(
-      goog.uri.utils.trailingQueryPunctuationRe_, '$1');
-};
+  /**
+   * @private
+   * @type {ol.Kinetic|undefined}
+   */
+  this.kinetic_ = options.kinetic;
 
+  /**
+   * @private
+   * @type {?ol.PreRenderFunction}
+   */
+  this.kineticPreRenderFn_ = null;
 
-/**
- * Replaces all existing definitions of a parameter with a single definition.
- *
- * Repeated calls to this can exhibit quadratic behavior due to the need to
- * find existing instances and reconstruct the string, though it should be
- * limited given the 2kb limit.  Consider using appendParams to append multiple
- * parameters in bulk.
- *
- * @param {string} uri The original URI, which may already have query data.
- * @param {string} keyEncoded The key, which must already be URI encoded.
- * @param {*} value The value, which will be stringized and encoded (assumed
- *     not already to be encoded).
- * @return {string} The URI with the query parameter added.
- */
-goog.uri.utils.setParam = function(uri, keyEncoded, value) {
-  return goog.uri.utils.appendParam(
-      goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value);
-};
+  /**
+   * @type {ol.Pixel}
+   */
+  this.lastCentroid = null;
 
+  /**
+   * @private
+   * @type {ol.events.ConditionType}
+   */
+  this.condition_ = goog.isDef(options.condition) ?
+      options.condition : ol.events.condition.noModifierKeys;
 
-/**
- * Generates a URI path using a given URI and a path with checks to
- * prevent consecutive "//". The baseUri passed in must not contain
- * query or fragment identifiers. The path to append may not contain query or
- * fragment identifiers.
- *
- * @param {string} baseUri URI to use as the base.
- * @param {string} path Path to append.
- * @return {string} Updated URI.
- */
-goog.uri.utils.appendPath = function(baseUri, path) {
-  goog.uri.utils.assertNoFragmentsOrQueries_(baseUri);
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.noKinetic_ = false;
 
-  // Remove any trailing '/'
-  if (goog.string.endsWith(baseUri, '/')) {
-    baseUri = baseUri.substr(0, baseUri.length - 1);
-  }
-  // Remove any leading '/'
-  if (goog.string.startsWith(path, '/')) {
-    path = path.substr(1);
-  }
-  return goog.string.buildString(baseUri, '/', path);
 };
+goog.inherits(ol.interaction.DragPan, ol.interaction.Pointer);
 
 
 /**
- * Replaces the path.
- * @param {string} uri URI to use as the base.
- * @param {string} path New path.
- * @return {string} Updated URI.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-goog.uri.utils.setPath = function(uri, path) {
-  // Add any missing '/'.
-  if (!goog.string.startsWith(path, '/')) {
-    path = '/' + path;
+ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) {
+  goog.asserts.assert(this.targetPointers.length >= 1);
+  var centroid =
+      ol.interaction.Pointer.centroid(this.targetPointers);
+  if (this.kinetic_) {
+    this.kinetic_.update(centroid[0], centroid[1]);
   }
-  var parts = goog.uri.utils.split(uri);
-  return goog.uri.utils.buildFromEncodedParts(
-      parts[goog.uri.utils.ComponentIndex.SCHEME],
-      parts[goog.uri.utils.ComponentIndex.USER_INFO],
-      parts[goog.uri.utils.ComponentIndex.DOMAIN],
-      parts[goog.uri.utils.ComponentIndex.PORT],
-      path,
-      parts[goog.uri.utils.ComponentIndex.QUERY_DATA],
-      parts[goog.uri.utils.ComponentIndex.FRAGMENT]);
+  if (!goog.isNull(this.lastCentroid)) {
+    var deltaX = this.lastCentroid[0] - centroid[0];
+    var deltaY = centroid[1] - this.lastCentroid[1];
+    var map = mapBrowserEvent.map;
+    var view = map.getView();
+    var viewState = view.getState();
+    var center = [deltaX, deltaY];
+    ol.coordinate.scale(center, viewState.resolution);
+    ol.coordinate.rotate(center, viewState.rotation);
+    ol.coordinate.add(center, viewState.center);
+    center = view.constrainCenter(center);
+    map.render();
+    view.setCenter(center);
+  }
+  this.lastCentroid = centroid;
 };
 
 
 /**
- * Standard supported query parameters.
- * @enum {string}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-goog.uri.utils.StandardQueryParam = {
-
-  /** Unused parameter for unique-ifying. */
-  RANDOM: 'zx'
+ol.interaction.DragPan.handleUpEvent_ = function(mapBrowserEvent) {
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  if (this.targetPointers.length === 0) {
+    if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) {
+      var distance = this.kinetic_.getDistance();
+      var angle = this.kinetic_.getAngle();
+      var center = view.getCenter();
+      goog.asserts.assert(goog.isDef(center));
+      this.kineticPreRenderFn_ = this.kinetic_.pan(center);
+      map.beforeRender(this.kineticPreRenderFn_);
+      var centerpx = map.getPixelFromCoordinate(center);
+      var dest = map.getCoordinateFromPixel([
+        centerpx[0] - distance * Math.cos(angle),
+        centerpx[1] - distance * Math.sin(angle)
+      ]);
+      dest = view.constrainCenter(dest);
+      view.setCenter(dest);
+    }
+    view.setHint(ol.ViewHint.INTERACTING, -1);
+    map.render();
+    return false;
+  } else {
+    this.lastCentroid = null;
+    return true;
+  }
 };
 
 
 /**
- * Sets the zx parameter of a URI to a random value.
- * @param {string} uri Any URI.
- * @return {string} That URI with the "zx" parameter added or replaced to
- *     contain a random string.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-goog.uri.utils.makeUnique = function(uri) {
-  return goog.uri.utils.setParam(uri,
-      goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString());
+ol.interaction.DragPan.handleDownEvent_ = function(mapBrowserEvent) {
+  if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) {
+    var map = mapBrowserEvent.map;
+    var view = map.getView();
+    this.lastCentroid = null;
+    if (!this.handlingDownUpSequence) {
+      view.setHint(ol.ViewHint.INTERACTING, 1);
+    }
+    map.render();
+    if (!goog.isNull(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;
+  }
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Class for parsing and formatting URIs.
- *
- * Use goog.Uri(string) to parse a URI string.  Use goog.Uri.create(...) to
- * create a new instance of the goog.Uri object from Uri parts.
- *
- * e.g: <code>var myUri = new goog.Uri(window.location);</code>
- *
- * Implements RFC 3986 for parsing/formatting URIs.
- * http://www.ietf.org/rfc/rfc3986.txt
- *
- * Some changes have been made to the interface (more like .NETs), though the
- * internal representation is now of un-encoded parts, this will change the
- * behavior slightly.
- *
+ * @inheritDoc
  */
+ol.interaction.DragPan.prototype.shouldStopEvent = goog.functions.FALSE;
 
-goog.provide('goog.Uri');
-goog.provide('goog.Uri.QueryData');
+goog.provide('ol.interaction.DragRotate');
 
-goog.require('goog.array');
-goog.require('goog.string');
-goog.require('goog.structs');
-goog.require('goog.structs.Map');
-goog.require('goog.uri.utils');
-goog.require('goog.uri.utils.ComponentIndex');
-goog.require('goog.uri.utils.StandardQueryParam');
+goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.events.ConditionType');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
 
 
 
 /**
- * This class contains setters and getters for the parts of the URI.
- * The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part
- * -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the
- * decoded path, <code>/foo bar</code>.
- *
- * The constructor accepts an optional unparsed, raw URI string.  The parser
- * is relaxed, so special characters that aren't escaped but don't cause
- * ambiguities will not cause parse failures.
- *
- * All setters return <code>this</code> and so may be chained, a la
- * <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>.
+ * @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.
  *
- * @param {*=} opt_uri Optional string URI to parse
- *        (use goog.Uri.create() to create a URI from parts), or if
- *        a goog.Uri is passed, a clone is created.
- * @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore
- * the case of the parameter name.
+ * This interaction is only supported for mouse devices.
  *
  * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.DragRotateOptions=} opt_options Options.
+ * @api stable
  */
-goog.Uri = function(opt_uri, opt_ignoreCase) {
-  // Parse in the uri string
-  var m;
-  if (opt_uri instanceof goog.Uri) {
-    this.ignoreCase_ = goog.isDef(opt_ignoreCase) ?
-        opt_ignoreCase : opt_uri.getIgnoreCase();
-    this.setScheme(opt_uri.getScheme());
-    this.setUserInfo(opt_uri.getUserInfo());
-    this.setDomain(opt_uri.getDomain());
-    this.setPort(opt_uri.getPort());
-    this.setPath(opt_uri.getPath());
-    this.setQueryData(opt_uri.getQueryData().clone());
-    this.setFragment(opt_uri.getFragment());
-  } else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) {
-    this.ignoreCase_ = !!opt_ignoreCase;
-
-    // Set the parts -- decoding as we do so.
-    // COMPATABILITY NOTE - In IE, unmatched fields may be empty strings,
-    // whereas in other browsers they will be undefined.
-    this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true);
-    this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true);
-    this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true);
-    this.setPort(m[goog.uri.utils.ComponentIndex.PORT]);
-    this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true);
-    this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true);
-    this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true);
+ol.interaction.DragRotate = function(opt_options) {
 
-  } else {
-    this.ignoreCase_ = !!opt_ignoreCase;
-    this.queryData_ = new goog.Uri.QueryData(null, null, this.ignoreCase_);
-  }
-};
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
+  goog.base(this, {
+    handleDownEvent: ol.interaction.DragRotate.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragRotate.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragRotate.handleUpEvent_
+  });
 
-/**
- * If true, we preserve the type of query parameters set programmatically.
- *
- * This means that if you set a parameter to a boolean, and then call
- * getParameterValue, you will get a boolean back.
- *
- * If false, we will coerce parameters to strings, just as they would
- * appear in real URIs.
- *
- * TODO(nicksantos): Remove this once people have time to fix all tests.
- *
- * @type {boolean}
- */
-goog.Uri.preserveParameterTypesCompatibilityFlag = false;
+  /**
+   * @private
+   * @type {ol.events.ConditionType}
+   */
+  this.condition_ = goog.isDef(options.condition) ?
+      options.condition : ol.events.condition.altShiftKeysOnly;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.lastAngle_ = undefined;
 
-/**
- * Parameter name added to stop caching.
- * @type {string}
- */
-goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;
+};
+goog.inherits(ol.interaction.DragRotate, ol.interaction.Pointer);
 
 
 /**
- * Scheme such as "http".
- * @type {string}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragRotate}
  * @private
  */
-goog.Uri.prototype.scheme_ = '';
-
+ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return;
+  }
 
-/**
- * User credentials in the form "username:password".
- * @type {string}
- * @private
- */
-goog.Uri.prototype.userInfo_ = '';
+  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 (goog.isDef(this.lastAngle_)) {
+    var delta = theta - this.lastAngle_;
+    var view = map.getView();
+    var viewState = view.getState();
+    map.render();
+    ol.interaction.Interaction.rotateWithoutConstraints(
+        map, view, viewState.rotation - delta);
+  }
+  this.lastAngle_ = theta;
+};
 
 
 /**
- * Domain part, e.g. "www.google.com".
- * @type {string}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragRotate}
  * @private
  */
-goog.Uri.prototype.domain_ = '';
-
+ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return true;
+  }
 
-/**
- * Port, e.g. 8080.
- * @type {?number}
- * @private
- */
-goog.Uri.prototype.port_ = null;
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  view.setHint(ol.ViewHint.INTERACTING, -1);
+  var viewState = view.getState();
+  ol.interaction.Interaction.rotate(map, view, viewState.rotation,
+      undefined, ol.DRAGROTATE_ANIMATION_DURATION);
+  return false;
+};
 
 
 /**
- * Path, e.g. "/tests/img.png".
- * @type {string}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotate}
  * @private
  */
-goog.Uri.prototype.path_ = '';
+ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return false;
+  }
+
+  var browserEvent = mapBrowserEvent.browserEvent;
+  if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) {
+    var map = mapBrowserEvent.map;
+    map.getView().setHint(ol.ViewHint.INTERACTING, 1);
+    map.render();
+    this.lastAngle_ = undefined;
+    return true;
+  } else {
+    return false;
+  }
+};
 
 
 /**
- * Object representing query data.
- * @type {!goog.Uri.QueryData}
- * @private
+ * @inheritDoc
  */
-goog.Uri.prototype.queryData_;
+ol.interaction.DragRotate.prototype.shouldStopEvent = goog.functions.FALSE;
 
+goog.provide('ol.geom.Geometry');
+goog.provide('ol.geom.GeometryType');
 
-/**
- * The fragment without the #.
- * @type {string}
- * @private
- */
-goog.Uri.prototype.fragment_ = '';
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('ol.Observable');
+goog.require('ol.proj');
 
 
 /**
- * Whether or not this Uri should be treated as Read Only.
- * @type {boolean}
- * @private
+ * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
+ * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
+ * `'GeometryCollection'`, `'Circle'`.
+ * @enum {string}
+ * @api stable
  */
-goog.Uri.prototype.isReadOnly_ = false;
+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'
+};
 
 
 /**
- * Whether or not to ignore case when comparing query params.
- * @type {boolean}
- * @private
+ * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z')
+ * or measure ('M') coordinate is available. Supported values are `'XY'`,
+ * `'XYZ'`, `'XYM'`, `'XYZM'`.
+ * @enum {string}
+ * @api stable
  */
-goog.Uri.prototype.ignoreCase_ = false;
+ol.geom.GeometryLayout = {
+  XY: 'XY',
+  XYZ: 'XYZ',
+  XYM: 'XYM',
+  XYZM: 'XYZM'
+};
+
 
 
 /**
- * @return {string} The string form of the url.
- * @override
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for vector geometries.
+ *
+ * @constructor
+ * @extends {ol.Observable}
+ * @fires change Triggered when the geometry changes.
+ * @api stable
  */
-goog.Uri.prototype.toString = function() {
-  var out = [];
+ol.geom.Geometry = function() {
 
-  var scheme = this.getScheme();
-  if (scheme) {
-    out.push(goog.Uri.encodeSpecialChars_(
-        scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_), ':');
-  }
+  goog.base(this);
 
-  var domain = this.getDomain();
-  if (domain) {
-    out.push('//');
+  /**
+   * @protected
+   * @type {ol.Extent|undefined}
+   */
+  this.extent = undefined;
 
-    var userInfo = this.getUserInfo();
-    if (userInfo) {
-      out.push(goog.Uri.encodeSpecialChars_(
-          userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_), '@');
-    }
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.extentRevision = -1;
 
-    out.push(goog.string.urlEncode(domain));
+  /**
+   * @protected
+   * @type {Object.<string, ol.geom.Geometry>}
+   */
+  this.simplifiedGeometryCache = {};
 
-    var port = this.getPort();
-    if (port != null) {
-      out.push(':', String(port));
-    }
-  }
-
-  var path = this.getPath();
-  if (path) {
-    if (this.hasDomain() && path.charAt(0) != '/') {
-      out.push('/');
-    }
-    out.push(goog.Uri.encodeSpecialChars_(
-        path,
-        path.charAt(0) == '/' ?
-            goog.Uri.reDisallowedInAbsolutePath_ :
-            goog.Uri.reDisallowedInRelativePath_));
-  }
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.simplifiedGeometryMaxMinSquaredTolerance = 0;
 
-  var query = this.getEncodedQuery();
-  if (query) {
-    out.push('?', query);
-  }
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.simplifiedGeometryRevision = 0;
 
-  var fragment = this.getFragment();
-  if (fragment) {
-    out.push('#', goog.Uri.encodeSpecialChars_(
-        fragment, goog.Uri.reDisallowedInFragment_));
-  }
-  return out.join('');
 };
+goog.inherits(ol.geom.Geometry, ol.Observable);
 
 
 /**
- * Resolves the given relative URI (a goog.Uri object), using the URI
- * represented by this instance as the base URI.
- *
- * There are several kinds of relative URIs:<br>
- * 1. foo - replaces the last part of the path, the whole query and fragment<br>
- * 2. /foo - replaces the the path, the query and fragment<br>
- * 3. //foo - replaces everything from the domain on.  foo is a domain name<br>
- * 4. ?foo - replace the query and fragment<br>
- * 5. #foo - replace the fragment only
- *
- * Additionally, if relative URI has a non-empty path, all ".." and "."
- * segments will be resolved, as described in RFC 3986.
- *
- * @param {goog.Uri} relativeUri The relative URI to resolve.
- * @return {!goog.Uri} The resolved URI.
+ * Make a complete copy of the geometry.
+ * @function
+ * @return {!ol.geom.Geometry} Clone.
+ * @api stable
  */
-goog.Uri.prototype.resolve = function(relativeUri) {
-
-  var absoluteUri = this.clone();
-
-  // we satisfy these conditions by looking for the first part of relativeUri
-  // that is not blank and applying defaults to the rest
-
-  var overridden = relativeUri.hasScheme();
-
-  if (overridden) {
-    absoluteUri.setScheme(relativeUri.getScheme());
-  } else {
-    overridden = relativeUri.hasUserInfo();
-  }
-
-  if (overridden) {
-    absoluteUri.setUserInfo(relativeUri.getUserInfo());
-  } else {
-    overridden = relativeUri.hasDomain();
-  }
-
-  if (overridden) {
-    absoluteUri.setDomain(relativeUri.getDomain());
-  } else {
-    overridden = relativeUri.hasPort();
-  }
-
-  var path = relativeUri.getPath();
-  if (overridden) {
-    absoluteUri.setPort(relativeUri.getPort());
-  } else {
-    overridden = relativeUri.hasPath();
-    if (overridden) {
-      // resolve path properly
-      if (path.charAt(0) != '/') {
-        // path is relative
-        if (this.hasDomain() && !this.hasPath()) {
-          // RFC 3986, section 5.2.3, case 1
-          path = '/' + path;
-        } else {
-          // RFC 3986, section 5.2.3, case 2
-          var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/');
-          if (lastSlashIndex != -1) {
-            path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path;
-          }
-        }
-      }
-      path = goog.Uri.removeDotSegments(path);
-    }
-  }
+ol.geom.Geometry.prototype.clone = goog.abstractMethod;
 
-  if (overridden) {
-    absoluteUri.setPath(path);
-  } else {
-    overridden = relativeUri.hasQuery();
-  }
 
-  if (overridden) {
-    absoluteUri.setQueryData(relativeUri.getDecodedQuery());
-  } else {
-    overridden = relativeUri.hasFragment();
-  }
+/**
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {ol.Coordinate} closestPoint Closest point.
+ * @param {number} minSquaredDistance Minimum squared distance.
+ * @return {number} Minimum squared distance.
+ */
+ol.geom.Geometry.prototype.closestPointXY = goog.abstractMethod;
 
-  if (overridden) {
-    absoluteUri.setFragment(relativeUri.getFragment());
-  }
 
-  return absoluteUri;
+/**
+ * @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 = goog.isDef(opt_closestPoint) ?
+      opt_closestPoint : [NaN, NaN];
+  this.closestPointXY(point[0], point[1], closestPoint, Infinity);
+  return closestPoint;
 };
 
 
 /**
- * Clones the URI instance.
- * @return {!goog.Uri} New instance of the URI object.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {boolean} Contains coordinate.
  */
-goog.Uri.prototype.clone = function() {
-  return new goog.Uri(this);
+ol.geom.Geometry.prototype.containsCoordinate = function(coordinate) {
+  return this.containsXY(coordinate[0], coordinate[1]);
 };
 
 
 /**
- * @return {string} The encoded scheme/protocol for the URI.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {boolean} Contains (x, y).
  */
-goog.Uri.prototype.getScheme = function() {
-  return this.scheme_;
-};
+ol.geom.Geometry.prototype.containsXY = goog.functions.FALSE;
 
 
 /**
- * Sets the scheme/protocol.
- * @param {string} newScheme New scheme value.
- * @param {boolean=} opt_decode Optional param for whether to decode new value.
- * @return {!goog.Uri} Reference to this URI object.
+ * Get the extent of the geometry.
+ * @function
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} extent Extent.
+ * @api stable
  */
-goog.Uri.prototype.setScheme = function(newScheme, opt_decode) {
-  this.enforceReadOnly();
-  this.scheme_ = opt_decode ? goog.Uri.decodeOrEmpty_(newScheme) : newScheme;
-
-  // remove an : at the end of the scheme so somebody can pass in
-  // window.location.protocol
-  if (this.scheme_) {
-    this.scheme_ = this.scheme_.replace(/:$/, '');
-  }
-  return this;
-};
+ol.geom.Geometry.prototype.getExtent = goog.abstractMethod;
 
 
 /**
- * @return {boolean} Whether the scheme has been set.
+ * Create a simplified version of this geometry using the Douglas Peucker
+ * algorithm.
+ * @see http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
+ * @function
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.Geometry} Simplified geometry.
  */
-goog.Uri.prototype.hasScheme = function() {
-  return !!this.scheme_;
-};
+ol.geom.Geometry.prototype.getSimplifiedGeometry = goog.abstractMethod;
 
 
 /**
- * @return {string} The decoded user info.
+ * Get the type of this geometry.
+ * @function
+ * @return {ol.geom.GeometryType} Geometry type.
+ * @api stable
  */
-goog.Uri.prototype.getUserInfo = function() {
-  return this.userInfo_;
-};
+ol.geom.Geometry.prototype.getType = goog.abstractMethod;
 
 
 /**
- * Sets the userInfo.
- * @param {string} newUserInfo New userInfo value.
- * @param {boolean=} opt_decode Optional param for whether to decode new value.
- * @return {!goog.Uri} Reference to this URI object.
+ * Apply a transform function to each coordinate of the geometry.
+ * The geometry is modified in place.
+ * If you do not want the geometry modified in place, first clone() it and
+ * then use this function on the clone.
+ * @function
+ * @param {ol.TransformFunction} transformFn Transform.
+ * @api stable
  */
-goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) {
-  this.enforceReadOnly();
-  this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) :
-                   newUserInfo;
-  return this;
-};
+ol.geom.Geometry.prototype.applyTransform = goog.abstractMethod;
 
 
 /**
- * @return {boolean} Whether the user info has been set.
+ * Test if the geometry and the passed extent intersect.
+ * @param {ol.Extent} extent Extent.
+ * @return {boolean} `true` if the geometry and the extent intersect.
+ * @function
+ * @api
  */
-goog.Uri.prototype.hasUserInfo = function() {
-  return !!this.userInfo_;
-};
+ol.geom.Geometry.prototype.intersectsExtent = goog.abstractMethod;
 
 
 /**
- * @return {string} The decoded domain.
+ * Translate the geometry.
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ * @function
+ * @api
  */
-goog.Uri.prototype.getDomain = function() {
-  return this.domain_;
-};
+ol.geom.Geometry.prototype.translate = goog.abstractMethod;
 
 
 /**
- * Sets the domain.
- * @param {string} newDomain New domain value.
- * @param {boolean=} opt_decode Optional param for whether to decode new value.
- * @return {!goog.Uri} Reference to this URI object.
+ * Transform each coordinate of the geometry from one coordinate reference
+ * system to another. The geometry is modified in place.
+ * For example, a line will be transformed to a line and a circle to a circle.
+ * If you do not want the geometry modified in place, first clone() it and
+ * then use this function on the clone.
+ *
+ * @param {ol.proj.ProjectionLike} source The current projection.  Can be a
+ *     string identifier or a {@link ol.proj.Projection} object.
+ * @param {ol.proj.ProjectionLike} destination The desired projection.  Can be a
+ *     string identifier or a {@link ol.proj.Projection} object.
+ * @return {ol.geom.Geometry} This geometry.  Note that original geometry is
+ *     modified in place.
+ * @api stable
  */
-goog.Uri.prototype.setDomain = function(newDomain, opt_decode) {
-  this.enforceReadOnly();
-  this.domain_ = opt_decode ? goog.Uri.decodeOrEmpty_(newDomain) : newDomain;
+ol.geom.Geometry.prototype.transform = function(source, destination) {
+  this.applyTransform(ol.proj.getTransform(source, destination));
   return this;
 };
 
+goog.provide('ol.geom.flat.transform');
+
+goog.require('goog.vec.Mat4');
+
 
 /**
- * @return {boolean} Whether the domain has been set.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed coordinates.
  */
-goog.Uri.prototype.hasDomain = function() {
-  return !!this.domain_;
+ol.geom.flat.transform.transform2D =
+    function(flatCoordinates, offset, end, stride, transform, opt_dest) {
+  var m00 = goog.vec.Mat4.getElement(transform, 0, 0);
+  var m10 = goog.vec.Mat4.getElement(transform, 1, 0);
+  var m01 = goog.vec.Mat4.getElement(transform, 0, 1);
+  var m11 = goog.vec.Mat4.getElement(transform, 1, 1);
+  var m03 = goog.vec.Mat4.getElement(transform, 0, 3);
+  var m13 = goog.vec.Mat4.getElement(transform, 1, 3);
+  var dest = goog.isDef(opt_dest) ? opt_dest : [];
+  var i = 0;
+  var j;
+  for (j = offset; j < end; j += stride) {
+    var x = flatCoordinates[j];
+    var y = flatCoordinates[j + 1];
+    dest[i++] = m00 * x + m01 * y + m03;
+    dest[i++] = m10 * x + m11 * y + m13;
+  }
+  if (goog.isDef(opt_dest) && dest.length != i) {
+    dest.length = i;
+  }
+  return dest;
 };
 
 
 /**
- * @return {?number} The port number.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed coordinates.
  */
-goog.Uri.prototype.getPort = function() {
-  return this.port_;
+ol.geom.flat.transform.translate =
+    function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) {
+  var dest = goog.isDef(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 (goog.isDef(opt_dest) && dest.length != i) {
+    dest.length = i;
+  }
+  return dest;
 };
 
+goog.provide('ol.geom.SimpleGeometry');
+
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('ol.extent');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.flat.transform');
+
+
 
 /**
- * Sets the port number.
- * @param {*} newPort Port number. Will be explicitly casted to a number.
- * @return {!goog.Uri} Reference to this URI object.
+ * @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
  */
-goog.Uri.prototype.setPort = function(newPort) {
-  this.enforceReadOnly();
+ol.geom.SimpleGeometry = function() {
 
-  if (newPort) {
-    newPort = Number(newPort);
-    if (isNaN(newPort) || newPort < 0) {
-      throw Error('Bad port number ' + newPort);
-    }
-    this.port_ = newPort;
-  } else {
-    this.port_ = null;
-  }
+  goog.base(this);
+
+  /**
+   * @protected
+   * @type {ol.geom.GeometryLayout}
+   */
+  this.layout = ol.geom.GeometryLayout.XY;
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.stride = 2;
+
+  /**
+   * @protected
+   * @type {Array.<number>}
+   */
+  this.flatCoordinates = null;
 
-  return this;
 };
+goog.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
 
 
 /**
- * @return {boolean} Whether the port has been set.
+ * @param {number} stride Stride.
+ * @private
+ * @return {ol.geom.GeometryLayout} layout Layout.
  */
-goog.Uri.prototype.hasPort = function() {
-  return this.port_ != null;
+ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) {
+  if (stride == 2) {
+    return ol.geom.GeometryLayout.XY;
+  } else if (stride == 3) {
+    return ol.geom.GeometryLayout.XYZ;
+  } else if (stride == 4) {
+    return ol.geom.GeometryLayout.XYZM;
+  } else {
+    goog.asserts.fail('unsupported stride: ' + stride);
+  }
 };
 
 
 /**
-  * @return {string} The decoded path.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @private
+ * @return {number} Stride.
  */
-goog.Uri.prototype.getPath = function() {
-  return this.path_;
+ol.geom.SimpleGeometry.getStrideForLayout_ = function(layout) {
+  if (layout == ol.geom.GeometryLayout.XY) {
+    return 2;
+  } else if (layout == ol.geom.GeometryLayout.XYZ) {
+    return 3;
+  } else if (layout == ol.geom.GeometryLayout.XYM) {
+    return 3;
+  } else if (layout == ol.geom.GeometryLayout.XYZM) {
+    return 4;
+  } else {
+    goog.asserts.fail('unsupported layout: ' + layout);
+  }
 };
 
 
 /**
- * Sets the path.
- * @param {string} newPath New path value.
- * @param {boolean=} opt_decode Optional param for whether to decode new value.
- * @return {!goog.Uri} Reference to this URI object.
+ * @inheritDoc
  */
-goog.Uri.prototype.setPath = function(newPath, opt_decode) {
-  this.enforceReadOnly();
-  this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath) : newPath;
-  return this;
-};
+ol.geom.SimpleGeometry.prototype.containsXY = goog.functions.FALSE;
 
 
 /**
- * @return {boolean} Whether the path has been set.
+ * @inheritDoc
+ * @api stable
  */
-goog.Uri.prototype.hasPath = function() {
-  return !!this.path_;
+ol.geom.SimpleGeometry.prototype.getExtent = function(opt_extent) {
+  if (this.extentRevision != this.getRevision()) {
+    this.extent = ol.extent.createOrUpdateFromFlatCoordinates(
+        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+        this.extent);
+    this.extentRevision = this.getRevision();
+  }
+  goog.asserts.assert(goog.isDef(this.extent));
+  return ol.extent.returnOrUpdate(this.extent, opt_extent);
 };
 
 
 /**
- * @return {boolean} Whether the query string has been set.
+ * @return {ol.Coordinate} First coordinate.
+ * @api stable
  */
-goog.Uri.prototype.hasQuery = function() {
-  return this.queryData_.toString() !== '';
+ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
+  return this.flatCoordinates.slice(0, this.stride);
 };
 
 
 /**
- * Sets the query data.
- * @param {goog.Uri.QueryData|string|undefined} queryData QueryData object.
- * @param {boolean=} opt_decode Optional param for whether to decode new value.
- *     Applies only if queryData is a string.
- * @return {!goog.Uri} Reference to this URI object.
+ * @return {Array.<number>} Flat coordinates.
  */
-goog.Uri.prototype.setQueryData = function(queryData, opt_decode) {
-  this.enforceReadOnly();
-
-  if (queryData instanceof goog.Uri.QueryData) {
-    this.queryData_ = queryData;
-    this.queryData_.setIgnoreCase(this.ignoreCase_);
-  } else {
-    if (!opt_decode) {
-      // QueryData accepts encoded query string, so encode it if
-      // opt_decode flag is not true.
-      queryData = goog.Uri.encodeSpecialChars_(queryData,
-                                               goog.Uri.reDisallowedInQuery_);
-    }
-    this.queryData_ = new goog.Uri.QueryData(queryData, null, this.ignoreCase_);
-  }
-
-  return this;
+ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
+  return this.flatCoordinates;
 };
 
 
 /**
- * Sets the URI query.
- * @param {string} newQuery New query value.
- * @param {boolean=} opt_decode Optional param for whether to decode new value.
- * @return {!goog.Uri} Reference to this URI object.
+ * @return {ol.Coordinate} Last point.
+ * @api stable
  */
-goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {
-  return this.setQueryData(newQuery, opt_decode);
+ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
+  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
 };
 
 
 /**
- * @return {string} The encoded URI query, not including the ?.
+ * @return {ol.geom.GeometryLayout} Layout.
+ * @api stable
  */
-goog.Uri.prototype.getEncodedQuery = function() {
-  return this.queryData_.toString();
+ol.geom.SimpleGeometry.prototype.getLayout = function() {
+  return this.layout;
 };
 
 
 /**
- * @return {string} The decoded URI query, not including the ?.
+ * @inheritDoc
  */
-goog.Uri.prototype.getDecodedQuery = function() {
-  return this.queryData_.toDecodedString();
+ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry =
+    function(squaredTolerance) {
+  if (this.simplifiedGeometryRevision != this.getRevision()) {
+    goog.object.clear(this.simplifiedGeometryCache);
+    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
+    this.simplifiedGeometryRevision = this.getRevision();
+  }
+  // If squaredTolerance is negative or if we know that simplification will not
+  // have any effect then just return this.
+  if (squaredTolerance < 0 ||
+      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
+       squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) {
+    return this;
+  }
+  var key = squaredTolerance.toString();
+  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
+    return this.simplifiedGeometryCache[key];
+  } else {
+    var simplifiedGeometry =
+        this.getSimplifiedGeometryInternal(squaredTolerance);
+    var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates();
+    if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) {
+      this.simplifiedGeometryCache[key] = simplifiedGeometry;
+      return simplifiedGeometry;
+    } else {
+      // Simplification did not actually remove any coordinates.  We now know
+      // that any calls to getSimplifiedGeometry with a squaredTolerance less
+      // than or equal to the current squaredTolerance will also not have any
+      // effect.  This allows us to short circuit simplification (saving CPU
+      // cycles) and prevents the cache of simplified geometries from filling
+      // up with useless identical copies of this geometry (saving memory).
+      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
+      return this;
+    }
+  }
 };
 
 
 /**
- * Returns the query data.
- * @return {!goog.Uri.QueryData} QueryData object.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.SimpleGeometry} Simplified geometry.
+ * @protected
  */
-goog.Uri.prototype.getQueryData = function() {
-  return this.queryData_;
+ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal =
+    function(squaredTolerance) {
+  return this;
 };
 
 
 /**
- * @return {string} The encoded URI query, not including the ?.
- *
- * Warning: This method, unlike other getter methods, returns encoded
- * value, instead of decoded one.
+ * @return {number} Stride.
  */
-goog.Uri.prototype.getQuery = function() {
-  return this.getEncodedQuery();
+ol.geom.SimpleGeometry.prototype.getStride = function() {
+  return this.stride;
 };
 
 
 /**
- * Sets the value of the named query parameters, clearing previous values for
- * that key.
- *
- * @param {string} key The parameter to set.
- * @param {*} value The new value.
- * @return {!goog.Uri} Reference to this URI object.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @protected
  */
-goog.Uri.prototype.setParameterValue = function(key, value) {
-  this.enforceReadOnly();
-  this.queryData_.set(key, value);
-  return this;
+ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal =
+    function(layout, flatCoordinates) {
+  this.stride = ol.geom.SimpleGeometry.getStrideForLayout_(layout);
+  this.layout = layout;
+  this.flatCoordinates = flatCoordinates;
 };
 
 
 /**
- * Sets the values of the named query parameters, clearing previous values for
- * that key.  Not new values will currently be moved to the end of the query
- * string.
- *
- * So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new'])
- * </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p>
- *
- * @param {string} key The parameter to set.
- * @param {*} values The new values. If values is a single
- *     string then it will be treated as the sole value.
- * @return {!goog.Uri} Reference to this URI object.
+ * @param {ol.geom.GeometryLayout|undefined} layout Layout.
+ * @param {Array} coordinates Coordinates.
+ * @param {number} nesting Nesting.
+ * @protected
  */
-goog.Uri.prototype.setParameterValues = function(key, values) {
-  this.enforceReadOnly();
-
-  if (!goog.isArray(values)) {
-    values = [String(values)];
+ol.geom.SimpleGeometry.prototype.setLayout =
+    function(layout, coordinates, nesting) {
+  /** @type {number} */
+  var stride;
+  if (goog.isDef(layout)) {
+    stride = ol.geom.SimpleGeometry.getStrideForLayout_(layout);
+  } else {
+    var i;
+    for (i = 0; i < nesting; ++i) {
+      if (coordinates.length === 0) {
+        this.layout = ol.geom.GeometryLayout.XY;
+        this.stride = 2;
+        return;
+      } else {
+        coordinates = /** @type {Array} */ (coordinates[0]);
+      }
+    }
+    stride = (/** @type {Array} */ (coordinates)).length;
+    layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride);
   }
-
-  // TODO(nicksantos): This cast shouldn't be necessary.
-  this.queryData_.setValues(key, /** @type {Array} */ (values));
-
-  return this;
+  this.layout = layout;
+  this.stride = stride;
 };
 
 
 /**
- * Returns the value<b>s</b> for a given cgi parameter as a list of decoded
- * query parameter values.
- * @param {string} name The parameter to get values for.
- * @return {!Array} The values for a given cgi parameter as a list of
- *     decoded query parameter values.
+ * @inheritDoc
+ * @api stable
  */
-goog.Uri.prototype.getParameterValues = function(name) {
-  return this.queryData_.getValues(name);
+ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
+  if (!goog.isNull(this.flatCoordinates)) {
+    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
+    this.changed();
+  }
 };
 
 
 /**
- * Returns the first value for a given cgi parameter or undefined if the given
- * parameter name does not appear in the query string.
- * @param {string} paramName Unescaped parameter name.
- * @return {string|undefined} The first value for a given cgi parameter or
- *     undefined if the given parameter name does not appear in the query
- *     string.
+ * Translate the geometry.
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ * @api
  */
-goog.Uri.prototype.getParameterValue = function(paramName) {
-  // NOTE(nicksantos): This type-cast is a lie when
-  // preserveParameterTypesCompatibilityFlag is set to true.
-  // But this should only be set to true in tests.
-  return /** @type {string|undefined} */ (this.queryData_.get(paramName));
+ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) {
+  var flatCoordinates = this.getFlatCoordinates();
+  if (!goog.isNull(flatCoordinates)) {
+    var stride = this.getStride();
+    ol.geom.flat.transform.translate(
+        flatCoordinates, 0, flatCoordinates.length, stride,
+        deltaX, deltaY, flatCoordinates);
+    this.changed();
+  }
 };
 
 
 /**
- * @return {string} The URI fragment, not including the #.
+ * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed flat coordinates.
  */
-goog.Uri.prototype.getFragment = function() {
-  return this.fragment_;
+ol.geom.transformSimpleGeometry2D =
+    function(simpleGeometry, transform, opt_dest) {
+  var flatCoordinates = simpleGeometry.getFlatCoordinates();
+  if (goog.isNull(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');
+
 
 /**
- * Sets the URI fragment.
- * @param {string} newFragment New fragment value.
- * @param {boolean=} opt_decode Optional param for whether to decode new value.
- * @return {!goog.Uri} Reference to this URI object.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-goog.Uri.prototype.setFragment = function(newFragment, opt_decode) {
-  this.enforceReadOnly();
-  this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) :
-                   newFragment;
-  return this;
+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;
 };
 
 
 /**
- * @return {boolean} Whether the URI has a fragment set.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-goog.Uri.prototype.hasFragment = function() {
-  return !!this.fragment_;
+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;
 };
 
 
 /**
- * Returns true if this has the same domain as that of uri2.
- * @param {goog.Uri} uri2 The URI object to compare to.
- * @return {boolean} true if same domain; false otherwise.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-goog.Uri.prototype.hasSameDomainAs = function(uri2) {
-  return ((!this.hasDomain() && !uri2.hasDomain()) ||
-          this.getDomain() == uri2.getDomain()) &&
-      ((!this.hasPort() && !uri2.hasPort()) ||
-          this.getPort() == uri2.getPort());
+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.math');
 
-/**
- * Adds a random parameter to the Uri.
- * @return {!goog.Uri} Reference to this Uri object.
- */
-goog.Uri.prototype.makeUnique = function() {
-  this.enforceReadOnly();
-  this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString());
-
-  return this;
-};
+goog.require('goog.asserts');
 
 
 /**
- * Removes the named query parameter.
- *
- * @param {string} key The parameter to remove.
- * @return {!goog.Uri} Reference to this URI object.
+ * @param {number} x X.
+ * @return {number} Hyperbolic cosine of x.
  */
-goog.Uri.prototype.removeParameter = function(key) {
-  this.enforceReadOnly();
-  this.queryData_.remove(key);
-  return this;
+ol.math.cosh = function(x) {
+  return (Math.exp(x) + Math.exp(-x)) / 2;
 };
 
 
 /**
- * Sets whether Uri is read only. If this goog.Uri is read-only,
- * enforceReadOnly_ will be called at the start of any function that may modify
- * this Uri.
- * @param {boolean} isReadOnly whether this goog.Uri should be read only.
- * @return {!goog.Uri} Reference to this Uri object.
+ * @param {number} x X.
+ * @return {number} Hyperbolic cotangent of x.
  */
-goog.Uri.prototype.setReadOnly = function(isReadOnly) {
-  this.isReadOnly_ = isReadOnly;
-  return this;
+ol.math.coth = function(x) {
+  var expMinusTwoX = Math.exp(-2 * x);
+  return (1 + expMinusTwoX) / (1 - expMinusTwoX);
 };
 
 
 /**
- * @return {boolean} Whether the URI is read only.
+ * @param {number} x X.
+ * @return {number} Hyperbolic cosecant of x.
  */
-goog.Uri.prototype.isReadOnly = function() {
-  return this.isReadOnly_;
+ol.math.csch = function(x) {
+  return 2 / (Math.exp(x) - Math.exp(-x));
 };
 
 
 /**
- * Checks if this Uri has been marked as read only, and if so, throws an error.
- * This should be called whenever any modifying function is called.
+ * @param {number} x X.
+ * @return {number} The smallest power of two greater than or equal to x.
  */
-goog.Uri.prototype.enforceReadOnly = function() {
-  if (this.isReadOnly_) {
-    throw Error('Tried to modify a read-only Uri');
-  }
+ol.math.roundUpToPowerOfTwo = function(x) {
+  goog.asserts.assert(0 < x);
+  return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
 };
 
 
 /**
- * Sets whether to ignore case.
- * NOTE: If there are already key/value pairs in the QueryData, and
- * ignoreCase_ is set to false, the keys will all be lower-cased.
- * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
- * @return {!goog.Uri} Reference to this Uri object.
+ * @param {number} x X.
+ * @return {number} Hyperbolic secant of x.
  */
-goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {
-  this.ignoreCase_ = ignoreCase;
-  if (this.queryData_) {
-    this.queryData_.setIgnoreCase(ignoreCase);
-  }
-  return this;
+ol.math.sech = function(x) {
+  return 2 / (Math.exp(x) + Math.exp(-x));
 };
 
 
 /**
- * @return {boolean} Whether to ignore case.
+ * @param {number} x X.
+ * @return {number} Hyperbolic sine of x.
  */
-goog.Uri.prototype.getIgnoreCase = function() {
-  return this.ignoreCase_;
+ol.math.sinh = function(x) {
+  return (Math.exp(x) - Math.exp(-x)) / 2;
 };
 
 
-//==============================================================================
-// Static members
-//==============================================================================
-
-
 /**
- * Creates a uri from the string form.  Basically an alias of new goog.Uri().
- * If a Uri object is passed to parse then it will return a clone of the object.
- *
- * @param {*} uri Raw URI string or instance of Uri
- *     object.
- * @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter
- * names in #getParameterValue.
- * @return {!goog.Uri} The new URI object.
+ * Returns the square of the closest distance between the point (x, y) and the
+ * line segment (x1, y1) to (x2, y2).
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {number} x1 X1.
+ * @param {number} y1 Y1.
+ * @param {number} x2 X2.
+ * @param {number} y2 Y2.
+ * @return {number} Squared distance.
  */
-goog.Uri.parse = function(uri, opt_ignoreCase) {
-  return uri instanceof goog.Uri ?
-         uri.clone() : new goog.Uri(uri, opt_ignoreCase);
+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);
 };
 
 
 /**
- * Creates a new goog.Uri object from unencoded parts.
- *
- * @param {?string=} opt_scheme Scheme/protocol or full URI to parse.
- * @param {?string=} opt_userInfo username:password.
- * @param {?string=} opt_domain www.google.com.
- * @param {?number=} opt_port 9830.
- * @param {?string=} opt_path /some/path/to/a/file.html.
- * @param {string|goog.Uri.QueryData=} opt_query a=1&b=2.
- * @param {?string=} opt_fragment The fragment without the #.
- * @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in
- *     #getParameterValue.
- *
- * @return {!goog.Uri} The new URI object.
+ * Returns the square of the distance between the points (x1, y1) and (x2, y2).
+ * @param {number} x1 X1.
+ * @param {number} y1 Y1.
+ * @param {number} x2 X2.
+ * @param {number} y2 Y2.
+ * @return {number} Squared distance.
  */
-goog.Uri.create = function(opt_scheme, opt_userInfo, opt_domain, opt_port,
-                           opt_path, opt_query, opt_fragment, opt_ignoreCase) {
-
-  var uri = new goog.Uri(null, opt_ignoreCase);
-
-  // Only set the parts if they are defined and not empty strings.
-  opt_scheme && uri.setScheme(opt_scheme);
-  opt_userInfo && uri.setUserInfo(opt_userInfo);
-  opt_domain && uri.setDomain(opt_domain);
-  opt_port && uri.setPort(opt_port);
-  opt_path && uri.setPath(opt_path);
-  opt_query && uri.setQueryData(opt_query);
-  opt_fragment && uri.setFragment(opt_fragment);
-
-  return uri;
+ol.math.squaredDistance = function(x1, y1, x2, y2) {
+  var dx = x2 - x1;
+  var dy = y2 - y1;
+  return dx * dx + dy * dy;
 };
 
 
 /**
- * Resolves a relative Uri against a base Uri, accepting both strings and
- * Uri objects.
- *
- * @param {*} base Base Uri.
- * @param {*} rel Relative Uri.
- * @return {!goog.Uri} Resolved uri.
+ * @param {number} x X.
+ * @return {number} Hyperbolic tangent of x.
  */
-goog.Uri.resolve = function(base, rel) {
-  if (!(base instanceof goog.Uri)) {
-    base = goog.Uri.parse(base);
-  }
+ol.math.tanh = function(x) {
+  var expMinusTwoX = Math.exp(-2 * x);
+  return (1 - expMinusTwoX) / (1 + expMinusTwoX);
+};
 
-  if (!(rel instanceof goog.Uri)) {
-    rel = goog.Uri.parse(rel);
-  }
+goog.provide('ol.geom.flat.closest');
 
-  return base.resolve(rel);
-};
+goog.require('goog.asserts');
+goog.require('goog.math');
+goog.require('ol.math');
 
 
 /**
- * Removes dot segments in given path component, as described in
- * RFC 3986, section 5.2.4.
- *
- * @param {string} path A non-empty path component.
- * @return {string} Path component with removed dot segments.
+ * 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.
  */
-goog.Uri.removeDotSegments = function(path) {
-  if (path == '..' || path == '.') {
-    return '';
-
-  } else if (!goog.string.contains(path, './') &&
-             !goog.string.contains(path, '/.')) {
-    // This optimization detects uris which do not contain dot-segments,
-    // and as a consequence do not require any processing.
-    return path;
-
+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 leadingSlash = goog.string.startsWith(path, '/');
-    var segments = path.split('/');
-    var out = [];
-
-    for (var pos = 0; pos < segments.length; ) {
-      var segment = segments[pos++];
-
-      if (segment == '.') {
-        if (leadingSlash && pos == segments.length) {
-          out.push('');
-        }
-      } else if (segment == '..') {
-        if (out.length > 1 || out.length == 1 && out[0] != '') {
-          out.pop();
-        }
-        if (leadingSlash && pos == segments.length) {
-          out.push('');
-        }
-      } else {
-        out.push(segment);
-        leadingSlash = true;
+    var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
+    if (t > 1) {
+      offset = offset2;
+    } else if (t > 0) {
+      for (i = 0; i < stride; ++i) {
+        closestPoint[i] = goog.math.lerp(flatCoordinates[offset1 + i],
+            flatCoordinates[offset2 + i], t);
       }
+      closestPoint.length = stride;
+      return;
+    } else {
+      offset = offset1;
     }
-
-    return out.join('/');
   }
+  for (i = 0; i < stride; ++i) {
+    closestPoint[i] = flatCoordinates[offset + i];
+  }
+  closestPoint.length = stride;
 };
 
 
 /**
- * Decodes a value or returns the empty string if it isn't defined or empty.
- * @param {string|undefined} val Value to decode.
- * @return {string} Decoded value.
- * @private
+ * Return the squared of the largest distance between any pair of consecutive
+ * coordinates.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} maxSquaredDelta Max squared delta.
+ * @return {number} Max squared delta.
  */
-goog.Uri.decodeOrEmpty_ = function(val) {
-  // Don't use UrlDecode() here because val is not a query parameter.
-  return val ? decodeURIComponent(val) : '';
+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;
 };
 
 
 /**
- * If unescapedPart is non null, then escapes any characters in it that aren't
- * valid characters in a url and also escapes any special characters that
- * appear in extra.
- *
- * @param {*} unescapedPart The string to encode.
- * @param {RegExp} extra A character set of characters in [\01-\177].
- * @return {?string} null iff unescapedPart == null.
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @param {number} maxSquaredDelta Max squared delta.
+ * @return {number} Max squared delta.
  */
-goog.Uri.encodeSpecialChars_ = function(unescapedPart, extra) {
-  if (goog.isString(unescapedPart)) {
-    return encodeURI(unescapedPart).replace(extra, goog.Uri.encodeChar_);
+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 null;
+  return maxSquaredDelta;
 };
 
 
 /**
- * Converts a character in [\01-\177] to its unicode character equivalent.
- * @param {string} ch One character string.
- * @return {string} Encoded string.
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @param {number} maxSquaredDelta Max squared delta.
+ * @return {number} Max squared delta.
  */
-goog.Uri.encodeChar_ = function(ch) {
-  var n = ch.charCodeAt(0);
-  return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);
+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;
 };
 
 
 /**
- * Regular expression for characters that are disallowed in the scheme or
- * userInfo part of the URI.
- * @type {RegExp}
- * @private
- */
-goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g;
-
-
-/**
- * Regular expression for characters that are disallowed in a relative path.
- * @type {RegExp}
- * @private
- */
-goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g;
-
-
-/**
- * Regular expression for characters that are disallowed in an absolute path.
- * @type {RegExp}
- * @private
- */
-goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;
-
-
-/**
- * Regular expression for characters that are disallowed in the query.
- * @type {RegExp}
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} maxDelta Max delta.
+ * @param {boolean} isRing Is ring.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {Array.<number>} closestPoint Closest point.
+ * @param {number} minSquaredDistance Minimum squared distance.
+ * @param {Array.<number>=} opt_tmpPoint Temporary point object.
+ * @return {number} Minimum squared distance.
  */
-goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;
+ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end,
+    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
+    opt_tmpPoint) {
+  if (offset == end) {
+    return minSquaredDistance;
+  }
+  var i, squaredDistance;
+  if (maxDelta === 0) {
+    // All points are identical, so just test the first point.
+    squaredDistance = ol.math.squaredDistance(
+        x, y, flatCoordinates[offset], flatCoordinates[offset + 1]);
+    if (squaredDistance < minSquaredDistance) {
+      for (i = 0; i < stride; ++i) {
+        closestPoint[i] = flatCoordinates[offset + i];
+      }
+      closestPoint.length = stride;
+      return squaredDistance;
+    } else {
+      return minSquaredDistance;
+    }
+  }
+  goog.asserts.assert(maxDelta > 0);
+  var tmpPoint = goog.isDef(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;
+};
 
 
 /**
- * Regular expression for characters that are disallowed in the fragment.
- * @type {RegExp}
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @param {number} maxDelta Max delta.
+ * @param {boolean} isRing Is ring.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {Array.<number>} closestPoint Closest point.
+ * @param {number} minSquaredDistance Minimum squared distance.
+ * @param {Array.<number>=} opt_tmpPoint Temporary point object.
+ * @return {number} Minimum squared distance.
  */
-goog.Uri.reDisallowedInFragment_ = /#/g;
+ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends,
+    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
+    opt_tmpPoint) {
+  var tmpPoint = goog.isDef(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;
+};
 
 
 /**
- * Checks whether two URIs have the same domain.
- * @param {string} uri1String First URI string.
- * @param {string} uri2String Second URI string.
- * @return {boolean} true if the two URIs have the same domain; false otherwise.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @param {number} maxDelta Max delta.
+ * @param {boolean} isRing Is ring.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {Array.<number>} closestPoint Closest point.
+ * @param {number} minSquaredDistance Minimum squared distance.
+ * @param {Array.<number>=} opt_tmpPoint Temporary point object.
+ * @return {number} Minimum squared distance.
  */
-goog.Uri.haveSameDomain = function(uri1String, uri2String) {
-  // Differs from goog.uri.utils.haveSameDomain, since this ignores scheme.
-  // TODO(gboyer): Have this just call goog.uri.util.haveSameDomain.
-  var pieces1 = goog.uri.utils.split(uri1String);
-  var pieces2 = goog.uri.utils.split(uri2String);
-  return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
-             pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
-         pieces1[goog.uri.utils.ComponentIndex.PORT] ==
-             pieces2[goog.uri.utils.ComponentIndex.PORT];
+ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset,
+    endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
+    opt_tmpPoint) {
+  var tmpPoint = goog.isDef(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');
 
-
-/**
- * Class used to represent URI query parameters.  It is essentially a hash of
- * name-value pairs, though a name can be present more than once.
- *
- * Has the same interface as the collections in goog.structs.
- *
- * @param {?string=} opt_query Optional encoded query string to parse into
- *     the object.
- * @param {goog.Uri=} opt_uri Optional uri object that should have its
- *     cache invalidated when this object updates. Deprecated -- this
- *     is no longer required.
- * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
- *     name in #get.
- * @constructor
- * @final
- */
-goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) {
-  /**
-   * Encoded query string, or null if it requires computing from the key map.
-   * @type {?string}
-   * @private
-   */
-  this.encodedQuery_ = opt_query || null;
-
-  /**
-   * If true, ignore the case of the parameter name in #get.
-   * @type {boolean}
-   * @private
-   */
-  this.ignoreCase_ = !!opt_ignoreCase;
-};
+goog.require('goog.asserts');
 
 
 /**
- * If the underlying key map is not yet initialized, it parses the
- * query string and fills the map with parsed data.
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} stride Stride.
+ * @return {number} offset Offset.
  */
-goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {
-  if (!this.keyMap_) {
-    this.keyMap_ = new goog.structs.Map();
-    this.count_ = 0;
-
-    if (this.encodedQuery_) {
-      var pairs = this.encodedQuery_.split('&');
-      for (var i = 0; i < pairs.length; i++) {
-        var indexOfEquals = pairs[i].indexOf('=');
-        var name = null;
-        var value = null;
-        if (indexOfEquals >= 0) {
-          name = pairs[i].substring(0, indexOfEquals);
-          value = pairs[i].substring(indexOfEquals + 1);
-        } else {
-          name = pairs[i];
-        }
-        name = goog.string.urlDecode(name);
-        name = this.getKeyName_(name);
-        this.add(name, value ? goog.string.urlDecode(value) : '');
-      }
-    }
+ol.geom.flat.deflate.coordinate =
+    function(flatCoordinates, offset, coordinate, stride) {
+  goog.asserts.assert(coordinate.length == stride);
+  var i, ii;
+  for (i = 0, ii = coordinate.length; i < ii; ++i) {
+    flatCoordinates[offset++] = coordinate[i];
   }
+  return offset;
 };
 
 
 /**
- * Creates a new query data instance from a map of names and values.
- *
- * @param {!goog.structs.Map|!Object} map Map of string parameter
- *     names to parameter value. If parameter value is an array, it is
- *     treated as if the key maps to each individual value in the
- *     array.
- * @param {goog.Uri=} opt_uri URI object that should have its cache
- *     invalidated when this object updates.
- * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
- *     name in #get.
- * @return {!goog.Uri.QueryData} The populated query data instance.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {number} stride Stride.
+ * @return {number} offset Offset.
  */
-goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) {
-  var keys = goog.structs.getKeys(map);
-  if (typeof keys == 'undefined') {
-    throw Error('Keys are undefined');
-  }
-
-  var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
-  var values = goog.structs.getValues(map);
-  for (var i = 0; i < keys.length; i++) {
-    var key = keys[i];
-    var value = values[i];
-    if (!goog.isArray(value)) {
-      queryData.add(key, value);
-    } else {
-      queryData.setValues(key, value);
+ol.geom.flat.deflate.coordinates =
+    function(flatCoordinates, offset, coordinates, stride) {
+  var i, ii;
+  for (i = 0, ii = coordinates.length; i < ii; ++i) {
+    var coordinate = coordinates[i];
+    goog.asserts.assert(coordinate.length == stride);
+    var j;
+    for (j = 0; j < stride; ++j) {
+      flatCoordinates[offset++] = coordinate[j];
     }
   }
-  return queryData;
+  return offset;
 };
 
 
 /**
- * Creates a new query data instance from parallel arrays of parameter names
- * and values. Allows for duplicate parameter names. Throws an error if the
- * lengths of the arrays differ.
- *
- * @param {Array.<string>} keys Parameter names.
- * @param {Array} values Parameter values.
- * @param {goog.Uri=} opt_uri URI object that should have its cache
- *     invalidated when this object updates.
- * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
- *     name in #get.
- * @return {!goog.Uri.QueryData} The populated query data instance.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess.
+ * @param {number} stride Stride.
+ * @param {Array.<number>=} opt_ends Ends.
+ * @return {Array.<number>} Ends.
  */
-goog.Uri.QueryData.createFromKeysValues = function(
-    keys, values, opt_uri, opt_ignoreCase) {
-  if (keys.length != values.length) {
-    throw Error('Mismatched lengths for keys/values');
-  }
-  var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
-  for (var i = 0; i < keys.length; i++) {
-    queryData.add(keys[i], values[i]);
+ol.geom.flat.deflate.coordinatess =
+    function(flatCoordinates, offset, coordinatess, stride, opt_ends) {
+  var ends = goog.isDef(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;
   }
-  return queryData;
+  ends.length = i;
+  return ends;
 };
 
 
 /**
- * The map containing name/value or name/array-of-values pairs.
- * May be null if it requires parsing from the query string.
- *
- * We need to use a Map because we cannot guarantee that the key names will
- * not be problematic for IE.
- *
- * @type {goog.structs.Map.<string, Array>}
- * @private
+ * @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.
  */
-goog.Uri.QueryData.prototype.keyMap_ = null;
-
+ol.geom.flat.deflate.coordinatesss =
+    function(flatCoordinates, offset, coordinatesss, stride, opt_endss) {
+  var endss = goog.isDef(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;
+};
 
-/**
- * The number of params, or null if it requires computing.
- * @type {?number}
- * @private
- */
-goog.Uri.QueryData.prototype.count_ = null;
+goog.provide('ol.geom.flat.inflate');
 
 
 /**
- * @return {?number} The number of parameters.
+ * @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.
  */
-goog.Uri.QueryData.prototype.getCount = function() {
-  this.ensureKeyMapInitialized_();
-  return this.count_;
+ol.geom.flat.inflate.coordinates =
+    function(flatCoordinates, offset, end, stride, opt_coordinates) {
+  var coordinates = goog.isDef(opt_coordinates) ? 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;
 };
 
 
 /**
- * Adds a key value pair.
- * @param {string} key Name.
- * @param {*} value Value.
- * @return {!goog.Uri.QueryData} Instance of this object.
+ * @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.
  */
-goog.Uri.QueryData.prototype.add = function(key, value) {
-  this.ensureKeyMapInitialized_();
-  this.invalidateCache_();
-
-  key = this.getKeyName_(key);
-  var values = this.keyMap_.get(key);
-  if (!values) {
-    this.keyMap_.set(key, (values = []));
+ol.geom.flat.inflate.coordinatess =
+    function(flatCoordinates, offset, ends, stride, opt_coordinatess) {
+  var coordinatess = goog.isDef(opt_coordinatess) ? 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;
   }
-  values.push(value);
-  this.count_++;
-  return this;
+  coordinatess.length = i;
+  return coordinatess;
 };
 
 
 /**
- * Removes all the params with the given key.
- * @param {string} key Name.
- * @return {boolean} Whether any parameter was removed.
+ * @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.
  */
-goog.Uri.QueryData.prototype.remove = function(key) {
-  this.ensureKeyMapInitialized_();
-
-  key = this.getKeyName_(key);
-  if (this.keyMap_.containsKey(key)) {
-    this.invalidateCache_();
-
-    // Decrement parameter count.
-    this.count_ -= this.keyMap_.get(key).length;
-    return this.keyMap_.remove(key);
+ol.geom.flat.inflate.coordinatesss =
+    function(flatCoordinates, offset, endss, stride, opt_coordinatesss) {
+  var coordinatesss = goog.isDef(opt_coordinatesss) ? opt_coordinatesss : [];
+  var i = 0;
+  var j, jj;
+  for (j = 0, jj = endss.length; j < jj; ++j) {
+    var ends = endss[j];
+    coordinatesss[i++] = ol.geom.flat.inflate.coordinatess(
+        flatCoordinates, offset, ends, stride, coordinatesss[i]);
+    offset = ends[ends.length - 1];
   }
-  return false;
+  coordinatesss.length = i;
+  return coordinatesss;
 };
 
+// Based on simplify-js https://github.com/mourner/simplify-js
+// Copyright (c) 2012, Vladimir Agafonkin
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//    1. Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//
+//    2. Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
 
-/**
- * Clears the parameters.
- */
-goog.Uri.QueryData.prototype.clear = function() {
-  this.invalidateCache_();
-  this.keyMap_ = null;
-  this.count_ = 0;
-};
-
+goog.provide('ol.geom.flat.simplify');
 
-/**
- * @return {boolean} Whether we have any parameters.
- */
-goog.Uri.QueryData.prototype.isEmpty = function() {
-  this.ensureKeyMapInitialized_();
-  return this.count_ == 0;
-};
+goog.require('ol.math');
 
 
 /**
- * Whether there is a parameter with the given name
- * @param {string} key The parameter name to check for.
- * @return {boolean} Whether there is a parameter with the given name.
+ * @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.
  */
-goog.Uri.QueryData.prototype.containsKey = function(key) {
-  this.ensureKeyMapInitialized_();
-  key = this.getKeyName_(key);
-  return this.keyMap_.containsKey(key);
+ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end,
+    stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) {
+  var simplifiedFlatCoordinates = goog.isDef(opt_simplifiedFlatCoordinates) ?
+      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;
 };
 
 
 /**
- * Whether there is a parameter with the given value.
- * @param {*} value The value to check for.
- * @return {boolean} Whether there is a parameter with the given value.
+ * @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.
  */
-goog.Uri.QueryData.prototype.containsValue = function(value) {
-  // NOTE(arv): This solution goes through all the params even if it was the
-  // first param. We can get around this by not reusing code or by switching to
-  // iterators.
-  var vals = this.getValues();
-  return goog.array.contains(vals, value);
+ol.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;
 };
 
 
 /**
- * Returns all the keys of the parameters. If a key is used multiple times
- * it will be included multiple times in the returned array
- * @return {!Array.<string>} All the keys of the parameters.
+ * @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.
  */
-goog.Uri.QueryData.prototype.getKeys = function() {
-  this.ensureKeyMapInitialized_();
-  // We need to get the values to know how many keys to add.
-  var vals = /** @type {Array.<Array|*>} */ (this.keyMap_.getValues());
-  var keys = this.keyMap_.getKeys();
-  var rv = [];
-  for (var i = 0; i < keys.length; i++) {
-    var val = vals[i];
-    for (var j = 0; j < val.length; j++) {
-      rv.push(keys[i]);
-    }
+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 rv;
+  return simplifiedOffset;
 };
 
 
 /**
- * Returns all the values of the parameters with the given name. If the query
- * data has no such key this will return an empty array. If no key is given
- * all values wil be returned.
- * @param {string=} opt_key The name of the parameter to get the values for.
- * @return {!Array} All the values of the parameters with the given name.
+ * @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.
  */
-goog.Uri.QueryData.prototype.getValues = function(opt_key) {
-  this.ensureKeyMapInitialized_();
-  var rv = [];
-  if (goog.isString(opt_key)) {
-    if (this.containsKey(opt_key)) {
-      rv = goog.array.concat(rv, this.keyMap_.get(this.getKeyName_(opt_key)));
+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];
     }
-  } else {
-    // Return all values.
-    var values = /** @type {Array.<Array|*>} */ (this.keyMap_.getValues());
-    for (var i = 0; i < values.length; i++) {
-      rv = goog.array.concat(rv, values[i]);
+    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;
     }
   }
-  return rv;
+  if (x2 != x1 || y2 != y1) {
+    // copy last point
+    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
+    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
+  }
+  return simplifiedOffset;
 };
 
 
 /**
- * Sets a key value pair and removes all other keys with the same value.
- *
- * @param {string} key Name.
- * @param {*} value Value.
- * @return {!goog.Uri.QueryData} Instance of this object.
+ * @param {number} value Value.
+ * @param {number} tolerance Tolerance.
+ * @return {number} Rounded value.
  */
-goog.Uri.QueryData.prototype.set = function(key, value) {
-  this.ensureKeyMapInitialized_();
-  this.invalidateCache_();
+ol.geom.flat.simplify.snap = function(value, tolerance) {
+  return tolerance * Math.round(value / tolerance);
+};
 
-  // TODO(user): This could be better written as
-  // this.remove(key), this.add(key, value), but that would reorder
-  // the key (since the key is first removed and then added at the
-  // end) and we would have to fix unit tests that depend on key
-  // ordering.
-  key = this.getKeyName_(key);
-  if (this.containsKey(key)) {
-    this.count_ -= this.keyMap_.get(key).length;
+
+/**
+ * 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;
   }
-  this.keyMap_.set(key, [value]);
-  this.count_++;
-  return this;
+  // 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;
 };
 
 
 /**
- * Returns the first value associated with the key. If the query data has no
- * such key this will return undefined or the optional default.
- * @param {string} key The name of the parameter to get the value for.
- * @param {*=} opt_default The default value to return if the query data
- *     has no such key.
- * @return {*} The first string value associated with the key, or opt_default
- *     if there's no value.
+ * @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.
  */
-goog.Uri.QueryData.prototype.get = function(key, opt_default) {
-  var values = key ? this.getValues(key) : [];
-  if (goog.Uri.preserveParameterTypesCompatibilityFlag) {
-    return values.length > 0 ? values[0] : opt_default;
-  } else {
-    return values.length > 0 ? String(values[0]) : opt_default;
+ol.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;
 };
 
 
 /**
- * Sets the values for a key. If the key already exists, this will
- * override all of the existing values that correspond to the key.
- * @param {string} key The key to set values for.
- * @param {Array} values The values to set.
+ * @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.
  */
-goog.Uri.QueryData.prototype.setValues = function(key, values) {
-  this.remove(key);
-
-  if (values.length > 0) {
-    this.invalidateCache_();
-    this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values));
-    this.count_ += values.length;
+ol.geom.flat.simplify.quantizess = function(
+    flatCoordinates, offset, endss, stride,
+    tolerance,
+    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
+  var i, ii;
+  for (i = 0, ii = endss.length; i < ii; ++i) {
+    var ends = endss[i];
+    var simplifiedEnds = [];
+    simplifiedOffset = ol.geom.flat.simplify.quantizes(
+        flatCoordinates, offset, ends, stride,
+        tolerance,
+        simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
+    simplifiedEndss.push(simplifiedEnds);
+    offset = ends[ends.length - 1];
   }
+  return simplifiedOffset;
 };
 
+goog.provide('ol.geom.LinearRing');
+
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.geom.flat.area');
+goog.require('ol.geom.flat.closest');
+goog.require('ol.geom.flat.deflate');
+goog.require('ol.geom.flat.inflate');
+goog.require('ol.geom.flat.simplify');
+
+
 
 /**
- * @return {string} Encoded query string.
- * @override
+ * @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
  */
-goog.Uri.QueryData.prototype.toString = function() {
-  if (this.encodedQuery_) {
-    return this.encodedQuery_;
-  }
+ol.geom.LinearRing = function(coordinates, opt_layout) {
 
-  if (!this.keyMap_) {
-    return '';
-  }
+  goog.base(this);
 
-  var sb = [];
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
 
-  // In the past, we use this.getKeys() and this.getVals(), but that
-  // generates a lot of allocations as compared to simply iterating
-  // over the keys.
-  var keys = this.keyMap_.getKeys();
-  for (var i = 0; i < keys.length; i++) {
-    var key = keys[i];
-    var encodedKey = goog.string.urlEncode(key);
-    var val = this.getValues(key);
-    for (var j = 0; j < val.length; j++) {
-      var param = encodedKey;
-      // Ensure that null and undefined are encoded into the url as
-      // literal strings.
-      if (val[j] !== '') {
-        param += '=' + goog.string.urlEncode(val[j]);
-      }
-      sb.push(param);
-    }
-  }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
+
+  this.setCoordinates(coordinates,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
 
-  return this.encodedQuery_ = sb.join('&');
 };
+goog.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry);
 
 
 /**
- * @return {string} Decoded query string.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.LinearRing} Clone.
+ * @api stable
  */
-goog.Uri.QueryData.prototype.toDecodedString = function() {
-  return goog.Uri.decodeOrEmpty_(this.toString());
+ol.geom.LinearRing.prototype.clone = function() {
+  var linearRing = new ol.geom.LinearRing(null);
+  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return linearRing;
 };
 
 
 /**
- * Invalidate the cache.
- * @private
+ * @inheritDoc
  */
-goog.Uri.QueryData.prototype.invalidateCache_ = function() {
-  this.encodedQuery_ = null;
+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);
 };
 
 
 /**
- * Removes all keys that are not in the provided list. (Modifies this object.)
- * @param {Array.<string>} keys The desired keys.
- * @return {!goog.Uri.QueryData} a reference to this object.
+ * @return {number} Area (on projected plane).
+ * @api stable
  */
-goog.Uri.QueryData.prototype.filterKeys = function(keys) {
-  this.ensureKeyMapInitialized_();
-  this.keyMap_.forEach(
-      function(value, key) {
-        if (!goog.array.contains(keys, key)) {
-          this.remove(key);
-        }
-      }, this);
-  return this;
+ol.geom.LinearRing.prototype.getArea = function() {
+  return ol.geom.flat.area.linearRing(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * Clone the query data instance.
- * @return {!goog.Uri.QueryData} New instance of the QueryData object.
+ * @return {Array.<ol.Coordinate>} Coordinates.
+ * @api stable
  */
-goog.Uri.QueryData.prototype.clone = function() {
-  var rv = new goog.Uri.QueryData();
-  rv.encodedQuery_ = this.encodedQuery_;
-  if (this.keyMap_) {
-    rv.keyMap_ = this.keyMap_.clone();
-    rv.count_ = this.count_;
-  }
-  return rv;
+ol.geom.LinearRing.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * Helper function to get the key name from a JavaScript object. Converts
- * the object to a string, and to lower case if necessary.
- * @private
- * @param {*} arg The object to get a key name from.
- * @return {string} valid key name which can be looked up in #keyMap_.
+ * @inheritDoc
  */
-goog.Uri.QueryData.prototype.getKeyName_ = function(arg) {
-  var keyName = String(arg);
-  if (this.ignoreCase_) {
-    keyName = keyName.toLowerCase();
-  }
-  return keyName;
+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;
 };
 
 
 /**
- * Ignore case in parameter names.
- * NOTE: If there are already key/value pairs in the QueryData, and
- * ignoreCase_ is set to false, the keys will all be lower-cased.
- * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
+ * @inheritDoc
+ * @api stable
  */
-goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) {
-  var resetKeys = ignoreCase && !this.ignoreCase_;
-  if (resetKeys) {
-    this.ensureKeyMapInitialized_();
-    this.invalidateCache_();
-    this.keyMap_.forEach(
-        function(value, key) {
-          var lowerCase = key.toLowerCase();
-          if (key != lowerCase) {
-            this.remove(key);
-            this.setValues(lowerCase, value);
-          }
-        }, this);
-  }
-  this.ignoreCase_ = ignoreCase;
+ol.geom.LinearRing.prototype.getType = function() {
+  return ol.geom.GeometryType.LINEAR_RING;
 };
 
 
 /**
- * Extends a query data object with another query data or map like object. This
- * operates 'in-place', it does not create a new QueryData object.
- *
- * @param {...(goog.Uri.QueryData|goog.structs.Map|Object)} var_args The object
- *     from which key value pairs will be copied.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-goog.Uri.QueryData.prototype.extend = function(var_args) {
-  for (var i = 0; i < arguments.length; i++) {
-    var data = arguments[i];
-    goog.structs.forEach(data,
-        /** @this {goog.Uri.QueryData} */
-        function(value, key) {
-          this.add(key, value);
-        }, this);
+ol.geom.LinearRing.prototype.setCoordinates =
+    function(coordinates, opt_layout) {
+  if (goog.isNull(coordinates)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+  } else {
+    this.setLayout(opt_layout, coordinates, 1);
+    if (goog.isNull(this.flatCoordinates)) {
+      this.flatCoordinates = [];
+    }
+    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
+        this.flatCoordinates, 0, coordinates, this.stride);
+    this.changed();
   }
 };
 
-goog.provide('ol.style.Icon');
-goog.provide('ol.style.IconAnchorUnits');
-goog.provide('ol.style.IconImageCache');
-goog.provide('ol.style.IconOrigin');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
-goog.require('ol.dom');
-goog.require('ol.style.Image');
-goog.require('ol.style.ImageState');
-
 
 /**
- * Icon anchor units. One of 'fraction', 'pixels'.
- * @enum {string}
- * @api
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-ol.style.IconAnchorUnits = {
-  FRACTION: 'fraction',
-  PIXELS: 'pixels'
+ol.geom.LinearRing.prototype.setFlatCoordinates =
+    function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
+goog.provide('ol.geom.Point');
 
-/**
- * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'.
- * @enum {string}
- * @api
- */
-ol.style.IconOrigin = {
-  BOTTOM_LEFT: 'bottom-left',
-  BOTTOM_RIGHT: 'bottom-right',
-  TOP_LEFT: 'top-left',
-  TOP_RIGHT: 'top-right'
-};
+goog.require('goog.asserts');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.geom.flat.deflate');
+goog.require('ol.math');
 
 
 
 /**
  * @classdesc
- * Set icon style for vector features.
+ * Point geometry.
  *
  * @constructor
- * @param {olx.style.IconOptions=} opt_options Options.
- * @extends {ol.style.Image}
- * @api
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-ol.style.Icon = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.anchor_ = goog.isDef(options.anchor) ? options.anchor : [0.5, 0.5];
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.normalizedAnchor_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.IconOrigin}
-   */
-  this.anchorOrigin_ = goog.isDef(options.anchorOrigin) ?
-      options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;
-
-  /**
-   * @private
-   * @type {ol.style.IconAnchorUnits}
-   */
-  this.anchorXUnits_ = goog.isDef(options.anchorXUnits) ?
-      options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;
-
-  /**
-   * @private
-   * @type {ol.style.IconAnchorUnits}
-   */
-  this.anchorYUnits_ = goog.isDef(options.anchorYUnits) ?
-      options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;
-
-  /**
-   * @type {?string}
-   */
-  var crossOrigin =
-      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
-
-  /**
-   * @type {Image}
-   */
-  var image = goog.isDef(options.img) ? options.img : null;
-
-  /**
-   * @type {string|undefined}
-   */
-  var src = options.src;
-
-  if ((!goog.isDef(src) || src.length === 0) && !goog.isNull(image)) {
-    src = image.src;
-  }
-  goog.asserts.assert(goog.isDef(src) && src.length > 0);
-
-  /**
-   * @type {ol.style.ImageState}
-   */
-  var imageState = goog.isDef(options.src) ?
-      ol.style.ImageState.IDLE : ol.style.ImageState.LOADED;
-
-  /**
-   * @private
-   * @type {ol.style.IconImage_}
-   */
-  this.iconImage_ = ol.style.IconImage_.get(
-      image, src, crossOrigin, imageState);
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.offset_ = goog.isDef(options.offset) ? options.offset : [0, 0];
-
-  /**
-   * @private
-   * @type {ol.style.IconOrigin}
-   */
-  this.offsetOrigin_ = goog.isDef(options.offsetOrigin) ?
-      options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.origin_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.size_ = goog.isDef(options.size) ? options.size : null;
-
-  /**
-   * @type {number}
-   */
-  var opacity = goog.isDef(options.opacity) ? options.opacity : 1;
-
-  /**
-   * @type {boolean}
-   */
-  var rotateWithView = goog.isDef(options.rotateWithView) ?
-      options.rotateWithView : false;
-
-  /**
-   * @type {number}
-   */
-  var rotation = goog.isDef(options.rotation) ? options.rotation : 0;
-
-  /**
-   * @type {number}
-   */
-  var scale = goog.isDef(options.scale) ? options.scale : 1;
-
-  /**
-   * @type {boolean}
-   */
-  var snapToPixel = goog.isDef(options.snapToPixel) ?
-      options.snapToPixel : true;
+ol.geom.Point = function(coordinates, opt_layout) {
+  goog.base(this);
+  this.setCoordinates(coordinates,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
+};
+goog.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
 
-  goog.base(this, {
-    opacity: opacity,
-    rotation: rotation,
-    scale: scale,
-    snapToPixel: snapToPixel,
-    rotateWithView: rotateWithView
-  });
 
+/**
+ * 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;
 };
-goog.inherits(ol.style.Icon, ol.style.Image);
 
 
 /**
  * @inheritDoc
- * @api
  */
-ol.style.Icon.prototype.getAnchor = function() {
-  if (!goog.isNull(this.normalizedAnchor_)) {
-    return this.normalizedAnchor_;
-  }
-  var anchor = this.anchor_;
-  var size = this.getSize();
-  if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION ||
-      this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
-    if (goog.isNull(size)) {
-      return null;
-    }
-    anchor = this.anchor_.slice();
-    if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION) {
-      anchor[0] *= size[0];
-    }
-    if (this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
-      anchor[1] *= size[1];
-    }
-  }
-
-  if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
-    if (goog.isNull(size)) {
-      return null;
-    }
-    if (anchor === this.anchor_) {
-      anchor = this.anchor_.slice();
-    }
-    if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
-        this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
-      anchor[0] = -anchor[0] + size[0];
-    }
-    if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
-        this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
-      anchor[1] = -anchor[1] + size[1];
+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;
   }
-  this.normalizedAnchor_ = anchor;
-  return this.normalizedAnchor_;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * @return {ol.Coordinate} Coordinates.
+ * @api stable
  */
-ol.style.Icon.prototype.getImage = function(pixelRatio) {
-  return this.iconImage_.getImage(pixelRatio);
+ol.geom.Point.prototype.getCoordinates = function() {
+  return goog.isNull(this.flatCoordinates) ? [] : this.flatCoordinates.slice();
 };
 
 
 /**
- * Real Image size used.
- * @return {ol.Size} Size.
+ * @inheritDoc
  */
-ol.style.Icon.prototype.getImageSize = function() {
-  return this.iconImage_.getSize();
+ol.geom.Point.prototype.getExtent = function(opt_extent) {
+  if (this.extentRevision != this.getRevision()) {
+    this.extent = ol.extent.createOrUpdateFromCoordinate(
+        this.flatCoordinates, this.extent);
+    this.extentRevision = this.getRevision();
+  }
+  goog.asserts.assert(goog.isDef(this.extent));
+  return ol.extent.returnOrUpdate(this.extent, opt_extent);
 };
 
 
 /**
  * @inheritDoc
+ * @api stable
  */
-ol.style.Icon.prototype.getImageState = function() {
-  return this.iconImage_.getImageState();
+ol.geom.Point.prototype.getType = function() {
+  return ol.geom.GeometryType.POINT;
 };
 
 
 /**
  * @inheritDoc
+ * @api
  */
-ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) {
-  return this.iconImage_.getHitDetectionImage(pixelRatio);
+ol.geom.Point.prototype.intersectsExtent = function(extent) {
+  return ol.extent.containsXY(extent,
+      this.flatCoordinates[0], this.flatCoordinates[1]);
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-ol.style.Icon.prototype.getOrigin = function() {
-  if (!goog.isNull(this.origin_)) {
-    return this.origin_;
-  }
-  var offset = this.offset_;
-
-  if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
-    var size = this.getSize();
-    var iconImageSize = this.iconImage_.getSize();
-    if (goog.isNull(size) || goog.isNull(iconImageSize)) {
-      return null;
-    }
-    offset = offset.slice();
-    if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
-        this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
-      offset[0] = iconImageSize[0] - size[0] - offset[0];
-    }
-    if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
-        this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
-      offset[1] = iconImageSize[1] - size[1] - offset[1];
+ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) {
+  if (goog.isNull(coordinates)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+  } else {
+    this.setLayout(opt_layout, coordinates, 0);
+    if (goog.isNull(this.flatCoordinates)) {
+      this.flatCoordinates = [];
     }
+    this.flatCoordinates.length = ol.geom.flat.deflate.coordinate(
+        this.flatCoordinates, 0, coordinates, this.stride);
+    this.changed();
   }
-  this.origin_ = offset;
-  return this.origin_;
 };
 
 
 /**
- * @return {string|undefined} Image src.
- * @api
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-ol.style.Icon.prototype.getSrc = function() {
-  return this.iconImage_.getSrc();
+ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
+goog.provide('ol.geom.flat.contains');
 
-/**
- * @inheritDoc
- * @api
- */
-ol.style.Icon.prototype.getSize = function() {
-  return goog.isNull(this.size_) ? this.iconImage_.getSize() : this.size_;
-};
+goog.require('goog.asserts');
+goog.require('ol.extent');
 
 
 /**
- * @inheritDoc
+ * @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.style.Icon.prototype.listenImageChange = function(listener, thisArg) {
-  return goog.events.listen(this.iconImage_, goog.events.EventType.CHANGE,
-      listener, false, thisArg);
+ol.geom.flat.contains.linearRingContainsExtent =
+    function(flatCoordinates, offset, end, stride, extent) {
+  var outside = ol.extent.forEachCorner(extent,
+      /**
+       * @param {ol.Coordinate} coordinate Coordinate.
+       */
+      function(coordinate) {
+        return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates,
+            offset, end, stride, coordinate[0], coordinate[1]);
+      });
+  return !outside;
 };
 
 
 /**
- * Load not yet loaded URI.
+ * @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.style.Icon.prototype.load = function() {
-  this.iconImage_.load();
+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;
 };
 
 
 /**
- * @inheritDoc
+ * @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.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
-  goog.events.unlisten(this.iconImage_, goog.events.EventType.CHANGE,
-      listener, false, thisArg);
+ol.geom.flat.contains.linearRingsContainsXY =
+    function(flatCoordinates, offset, ends, stride, x, y) {
+  goog.asserts.assert(ends.length > 0);
+  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;
 };
 
 
-
 /**
- * @constructor
- * @param {Image} image Image.
- * @param {string|undefined} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.style.ImageState} imageState Image state.
- * @extends {goog.events.EventTarget}
- * @private
+ * @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.style.IconImage_ = function(image, src, crossOrigin, imageState) {
+ol.geom.flat.contains.linearRingssContainsXY =
+    function(flatCoordinates, offset, endss, stride, x, y) {
+  goog.asserts.assert(endss.length > 0);
+  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.base(this);
+goog.provide('ol.geom.flat.interiorpoint');
 
-  /**
-   * @private
-   * @type {Image|HTMLCanvasElement}
-   */
-  this.hitDetectionImage_ = null;
-
-  /**
-   * @private
-   * @type {Image}
-   */
-  this.image_ = goog.isNull(image) ? new Image() : image;
-
-  if (!goog.isNull(crossOrigin)) {
-    this.image_.crossOrigin = crossOrigin;
-  }
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.imageListenerKeys_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.ImageState}
-   */
-  this.imageState_ = imageState;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.size_ = null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.src_ = src;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.tainting_ = false;
-
-};
-goog.inherits(ol.style.IconImage_, goog.events.EventTarget);
+goog.require('goog.asserts');
+goog.require('ol.geom.flat.contains');
 
 
 /**
- * @param {Image} image Image.
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.style.ImageState} imageState Image state.
- * @return {ol.style.IconImage_} Icon image.
+ * 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.style.IconImage_.get = function(image, src, crossOrigin, imageState) {
-  var iconImageCache = ol.style.IconImageCache.getInstance();
-  var iconImage = iconImageCache.get(src, crossOrigin);
-  if (goog.isNull(iconImage)) {
-    iconImage = new ol.style.IconImage_(image, src, crossOrigin, imageState);
-    iconImageCache.set(src, crossOrigin, iconImage);
+ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
+    ends, stride, flatCenters, flatCentersOffset, opt_dest) {
+  var i, ii, x, x1, x2, y1, y2;
+  var y = flatCenters[flatCentersOffset + 1];
+  /** @type {Array.<number>} */
+  var intersections = [];
+  // Calculate intersections with the horizontal line
+  var end = ends[0];
+  x1 = flatCoordinates[end - stride];
+  y1 = flatCoordinates[end - stride + 1];
+  for (i = offset; i < end; i += stride) {
+    x2 = flatCoordinates[i];
+    y2 = flatCoordinates[i + 1];
+    if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) {
+      x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
+      intersections.push(x);
+    }
+    x1 = x2;
+    y1 = y2;
+  }
+  // Find the longest segment of the horizontal line that has its center point
+  // inside the linear ring.
+  var pointX = NaN;
+  var maxSegmentLength = -Infinity;
+  intersections.sort();
+  x1 = intersections[0];
+  for (i = 1, ii = intersections.length; i < ii; ++i) {
+    x2 = intersections[i];
+    var segmentLength = Math.abs(x2 - x1);
+    if (segmentLength > maxSegmentLength) {
+      x = (x1 + x2) / 2;
+      if (ol.geom.flat.contains.linearRingsContainsXY(
+          flatCoordinates, offset, ends, stride, x, y)) {
+        pointX = x;
+        maxSegmentLength = segmentLength;
+      }
+    }
+    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 (goog.isDef(opt_dest)) {
+    opt_dest.push(pointX, y);
+    return opt_dest;
+  } else {
+    return [pointX, y];
   }
-  return iconImage;
 };
 
 
 /**
- * @private
+ * @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.style.IconImage_.prototype.determineTainting_ = function() {
-  var context = ol.dom.createCanvasContext2D(1, 1);
-  context.drawImage(this.image_, 0, 0);
-  try {
-    context.getImageData(0, 0, 1, 1);
-  } catch (e) {
-    this.tainting_ = true;
+ol.geom.flat.interiorpoint.linearRingss =
+    function(flatCoordinates, offset, endss, stride, flatCenters) {
+  goog.asserts.assert(2 * endss.length == 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');
+
 
 /**
- * @private
+ * 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(ol.Coordinate, ol.Coordinate): T} callback Function
+ *     called for each segment.
+ * @return {T|boolean} Value.
+ * @template T
  */
-ol.style.IconImage_.prototype.dispatchChangeEvent_ = function() {
-  this.dispatchEvent(goog.events.EventType.CHANGE);
+ol.geom.flat.segments.forEach =
+    function(flatCoordinates, offset, end, stride, callback) {
+  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(point1, point2);
+    if (ret) {
+      return ret;
+    }
+    point1[0] = point2[0];
+    point1[1] = point2[1];
+  }
+  return false;
 };
 
+goog.provide('ol.geom.flat.intersectsextent');
 
-/**
- * @private
- */
-ol.style.IconImage_.prototype.handleImageError_ = function() {
-  this.imageState_ = ol.style.ImageState.ERROR;
-  this.unlistenImage_();
-  this.dispatchChangeEvent_();
-};
+goog.require('goog.asserts');
+goog.require('ol.extent');
+goog.require('ol.geom.flat.contains');
+goog.require('ol.geom.flat.segments');
 
 
 /**
- * @private
+ * @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.style.IconImage_.prototype.handleImageLoad_ = function() {
-  this.imageState_ = ol.style.ImageState.LOADED;
-  this.size_ = [this.image_.width, this.image_.height];
-  this.unlistenImage_();
-  this.determineTainting_();
-  this.dispatchChangeEvent_();
+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 {number} pixelRatio Pixel ratio.
- * @return {Image} Image element.
+ * @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.style.IconImage_.prototype.getImage = function(pixelRatio) {
-  return this.image_;
+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;
 };
 
 
 /**
- * @return {ol.style.ImageState} Image state.
+ * @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.style.IconImage_.prototype.getImageState = function() {
-  return this.imageState_;
+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 {number} pixelRatio Pixel ratio.
- * @return {Image|HTMLCanvasElement} Image element.
+ * @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.style.IconImage_.prototype.getHitDetectionImage = function(pixelRatio) {
-  if (goog.isNull(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_;
+ol.geom.flat.intersectsextent.linearRings =
+    function(flatCoordinates, offset, ends, stride, extent) {
+  goog.asserts.assert(ends.length > 0);
+  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 this.hitDetectionImage_;
+  return true;
 };
 
 
 /**
- * @return {ol.Size} Image size.
+ * @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.style.IconImage_.prototype.getSize = function() {
-  return this.size_;
+ol.geom.flat.intersectsextent.linearRingss =
+    function(flatCoordinates, offset, endss, stride, extent) {
+  goog.asserts.assert(endss.length > 0);
+  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;
 };
 
-
-/**
- * @return {string|undefined} Image src.
- */
-ol.style.IconImage_.prototype.getSrc = function() {
-  return this.src_;
-};
+goog.provide('ol.geom.flat.reverse');
 
 
 /**
- * Load not yet loaded URI.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  */
-ol.style.IconImage_.prototype.load = function() {
-  if (this.imageState_ == ol.style.ImageState.IDLE) {
-    goog.asserts.assert(goog.isDef(this.src_));
-    goog.asserts.assert(goog.isNull(this.imageListenerKeys_));
-    this.imageState_ = ol.style.ImageState.LOADING;
-    this.imageListenerKeys_ = [
-      goog.events.listenOnce(this.image_, goog.events.EventType.ERROR,
-          this.handleImageError_, false, this),
-      goog.events.listenOnce(this.image_, goog.events.EventType.LOAD,
-          this.handleImageLoad_, false, this)
-    ];
-    try {
-      this.image_.src = this.src_;
-    } catch (e) {
-      this.handleImageError_();
+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');
 
-/**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
- */
-ol.style.IconImage_.prototype.unlistenImage_ = function() {
-  goog.asserts.assert(!goog.isNull(this.imageListenerKeys_));
-  goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
-};
-
+goog.require('ol.geom.flat.reverse');
 
 
 /**
- * @constructor
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {boolean} Is clockwise.
  */
-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;
+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;
 };
-goog.addSingletonGetter(ol.style.IconImageCache);
 
 
 /**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @return {string} Cache key.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @return {boolean} `true` if all rings are correctly oriented, `false`
+ *     otherwise.
  */
-ol.style.IconImageCache.getKey = function(src, crossOrigin) {
-  goog.asserts.assert(goog.isDef(crossOrigin));
-  return crossOrigin + ':' + src;
+ol.geom.flat.orient.linearRingsAreOriented =
+    function(flatCoordinates, offset, ends, stride) {
+  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 ? !isClockwise : isClockwise) {
+      return false;
+    }
+    offset = end;
+  }
+  return true;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @return {boolean} `true` if all rings are correctly oriented, `false`
+ *     otherwise.
  */
-ol.style.IconImageCache.prototype.clear = function() {
-  this.cache_ = {};
-  this.cacheSize_ = 0;
+ol.geom.flat.linearRingssAreOriented =
+    function(flatCoordinates, offset, endss, stride) {
+  var i, ii;
+  for (i = 0, ii = endss.length; i < ii; ++i) {
+    if (!ol.geom.flat.orient.linearRingsAreOriented(
+        flatCoordinates, offset, endss[i], stride)) {
+      return false;
+    }
+  }
+  return true;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @return {number} End.
  */
-ol.style.IconImageCache.prototype.expire = function() {
-  if (this.cacheSize_ > this.maxCacheSize_) {
-    var i = 0;
-    var key, iconImage;
-    for (key in this.cache_) {
-      iconImage = this.cache_[key];
-      if ((i++ & 3) === 0 && !goog.events.hasListener(iconImage)) {
-        delete this.cache_[key];
-        --this.cacheSize_;
-      }
+ol.geom.flat.orient.orientLinearRings =
+    function(flatCoordinates, offset, ends, stride) {
+  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 ? !isClockwise : isClockwise;
+    if (reverse) {
+      ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride);
     }
+    offset = end;
   }
+  return offset;
 };
 
 
 /**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @return {ol.style.IconImage_} Icon image.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @return {number} End.
  */
-ol.style.IconImageCache.prototype.get = function(src, crossOrigin) {
-  var key = ol.style.IconImageCache.getKey(src, crossOrigin);
-  return key in this.cache_ ? this.cache_[key] : null;
+ol.geom.flat.orient.orientLinearRingss =
+    function(flatCoordinates, offset, endss, stride) {
+  var i, ii;
+  for (i = 0, ii = endss.length; i < ii; ++i) {
+    offset = ol.geom.flat.orient.orientLinearRings(
+        flatCoordinates, offset, endss[i], stride);
+  }
+  return offset;
 };
 
+goog.provide('ol.geom.Polygon');
 
-/**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.style.IconImage_} iconImage Icon image.
- */
-ol.style.IconImageCache.prototype.set = function(src, crossOrigin, iconImage) {
-  var key = ol.style.IconImageCache.getKey(src, crossOrigin);
-  this.cache_[key] = iconImage;
-  ++this.cacheSize_;
-};
-
-goog.provide('ol.style.Text');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.LinearRing');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.geom.flat.area');
+goog.require('ol.geom.flat.closest');
+goog.require('ol.geom.flat.contains');
+goog.require('ol.geom.flat.deflate');
+goog.require('ol.geom.flat.inflate');
+goog.require('ol.geom.flat.interiorpoint');
+goog.require('ol.geom.flat.intersectsextent');
+goog.require('ol.geom.flat.orient');
+goog.require('ol.geom.flat.simplify');
 
 
 
 /**
  * @classdesc
- * Set text style for vector features.
+ * Polygon geometry.
  *
  * @constructor
- * @param {olx.style.TextOptions=} opt_options Options.
- * @api
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-ol.style.Text = function(opt_options) {
+ol.geom.Polygon = function(coordinates, opt_layout) {
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  goog.base(this);
 
   /**
+   * @type {Array.<number>}
    * @private
-   * @type {string|undefined}
    */
-  this.font_ = options.font;
+  this.ends_ = [];
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {number}
    */
-  this.rotation_ = options.rotation;
+  this.flatInteriorPointRevision_ = -1;
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {ol.Coordinate}
    */
-  this.scale_ = options.scale;
+  this.flatInteriorPoint_ = null;
 
   /**
    * @private
-   * @type {string|undefined}
+   * @type {number}
    */
-  this.text_ = options.text;
+  this.maxDelta_ = -1;
 
   /**
    * @private
-   * @type {string|undefined}
+   * @type {number}
    */
-  this.textAlign_ = options.textAlign;
+  this.maxDeltaRevision_ = -1;
 
   /**
    * @private
-   * @type {string|undefined}
+   * @type {number}
    */
-  this.textBaseline_ = options.textBaseline;
+  this.orientedRevision_ = -1;
 
   /**
    * @private
-   * @type {ol.style.Fill}
+   * @type {Array.<number>}
    */
-  this.fill_ = goog.isDef(options.fill) ? options.fill : null;
+  this.orientedFlatCoordinates_ = null;
 
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.offsetX_ = goog.isDef(options.offsetX) ? options.offsetX : 0;
+  this.setCoordinates(coordinates,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.offsetY_ = goog.isDef(options.offsetY) ? options.offsetY : 0;
 };
+goog.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
 
 
 /**
- * @return {string|undefined} Font.
- * @api
+ * @param {ol.geom.LinearRing} linearRing Linear ring.
+ * @api stable
  */
-ol.style.Text.prototype.getFont = function() {
-  return this.font_;
+ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
+  goog.asserts.assert(linearRing.getLayout() == this.layout);
+  if (goog.isNull(this.flatCoordinates)) {
+    this.flatCoordinates = linearRing.getFlatCoordinates().slice();
+  } else {
+    goog.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
+  }
+  this.ends_.push(this.flatCoordinates.length);
+  this.changed();
 };
 
 
 /**
- * @return {number} Horizontal text offset.
- * @api
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Polygon} Clone.
+ * @api stable
  */
-ol.style.Text.prototype.getOffsetX = function() {
-  return this.offsetX_;
+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;
 };
 
 
 /**
- * @return {number} Vertical text offset.
- * @api
+ * @inheritDoc
  */
-ol.style.Text.prototype.getOffsetY = function() {
-  return this.offsetY_;
+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);
 };
 
 
 /**
- * @return {ol.style.Fill} Fill style.
- * @api
+ * @inheritDoc
  */
-ol.style.Text.prototype.getFill = function() {
-  return this.fill_;
+ol.geom.Polygon.prototype.containsXY = function(x, y) {
+  return ol.geom.flat.contains.linearRingsContainsXY(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
 };
 
 
 /**
- * @return {number|undefined} Rotation.
- * @api
+ * @return {number} Area (on projected plane).
+ * @api stable
  */
-ol.style.Text.prototype.getRotation = function() {
-  return this.rotation_;
+ol.geom.Polygon.prototype.getArea = function() {
+  return ol.geom.flat.area.linearRings(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
 };
 
 
 /**
- * @return {number|undefined} Scale.
- * @api
+ * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
+ * @api stable
  */
-ol.style.Text.prototype.getScale = function() {
-  return this.scale_;
+ol.geom.Polygon.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinatess(
+      this.flatCoordinates, 0, this.ends_, this.stride);
 };
 
 
 /**
- * @return {ol.style.Stroke} Stroke style.
- * @api
+ * @return {Array.<number>} Ends.
  */
-ol.style.Text.prototype.getStroke = function() {
-  return this.stroke_;
+ol.geom.Polygon.prototype.getEnds = function() {
+  return this.ends_;
 };
 
 
 /**
- * @return {string|undefined} Text.
- * @api
+ * @return {Array.<number>} Interior point.
  */
-ol.style.Text.prototype.getText = function() {
-  return this.text_;
+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 {string|undefined} Text align.
- * @api
+ * @return {ol.geom.Point} Interior point.
+ * @api stable
  */
-ol.style.Text.prototype.getTextAlign = function() {
-  return this.textAlign_;
+ol.geom.Polygon.prototype.getInteriorPoint = function() {
+  return new ol.geom.Point(this.getFlatInteriorPoint());
 };
 
 
 /**
- * @return {string|undefined} Text baseline.
+ * Return the number of rings of the polygon,  this includes the exterior
+ * ring and any interior rings.
+ *
+ * @return {number} Number of rings.
  * @api
  */
-ol.style.Text.prototype.getTextBaseline = function() {
-  return this.textBaseline_;
+ol.geom.Polygon.prototype.getLinearRingCount = function() {
+  return this.ends_.length;
 };
 
-// FIXME http://earth.google.com/kml/1.0 namespace?
-// FIXME why does node.getAttribute return an unknown type?
-// FIXME text
-// FIXME serialize arbitrary feature properties
-// FIXME don't parse style if extractStyles is false
-
-goog.provide('ol.format.KML');
-
-goog.require('goog.Uri');
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.math');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('ol.Feature');
-goog.require('ol.array');
-goog.require('ol.color');
-goog.require('ol.feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.format.XSD');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.proj');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Icon');
-goog.require('ol.style.IconAnchorUnits');
-goog.require('ol.style.IconOrigin');
-goog.require('ol.style.Image');
-goog.require('ol.style.Stroke');
-goog.require('ol.style.Style');
-goog.require('ol.style.Text');
-goog.require('ol.xml');
-
 
 /**
- * @typedef {{x: number, xunits: (ol.style.IconAnchorUnits|undefined),
- *            y: number, yunits: (ol.style.IconAnchorUnits|undefined)}}
+ * 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.format.KMLVec2_;
+ol.geom.Polygon.prototype.getLinearRing = function(index) {
+  goog.asserts.assert(0 <= index && index < this.ends_.length);
+  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;
+};
 
 
 /**
- * @typedef {{flatCoordinates: Array.<number>,
- *            whens: Array.<number>}}
+ * @return {Array.<ol.geom.LinearRing>} Linear rings.
+ * @api stable
  */
-ol.format.KMLGxTrackObject_;
-
+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;
+};
 
 
 /**
- * @classdesc
- * Feature format for reading and writing data in the KML format.
- *
- * @constructor
- * @extends {ol.format.XMLFeature}
- * @param {olx.format.KMLOptions=} opt_options Options.
- * @api stable
+ * @return {Array.<number>} Oriented flat coordinates.
  */
-ol.format.KML = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() {
+  if (this.orientedRevision_ != this.getRevision()) {
+    var flatCoordinates = this.flatCoordinates;
+    if (ol.geom.flat.orient.linearRingsAreOriented(
+        flatCoordinates, 0, this.ends_, this.stride)) {
+      this.orientedFlatCoordinates_ = flatCoordinates;
+    } else {
+      this.orientedFlatCoordinates_ = flatCoordinates.slice();
+      this.orientedFlatCoordinates_.length =
+          ol.geom.flat.orient.orientLinearRings(
+              this.orientedFlatCoordinates_, 0, this.ends_, this.stride);
+    }
+    this.orientedRevision_ = this.getRevision();
+  }
+  return this.orientedFlatCoordinates_;
+};
 
-  goog.base(this);
 
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+/**
+ * @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;
+};
 
-  var defaultStyle = goog.isDef(options.defaultStyle) ?
-      options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_;
 
-  /** @type {Object.<string, (Array.<ol.style.Style>|string)>} */
-  var sharedStyles = {};
+/**
+ * @inheritDoc
+ * @api stable
+ */
+ol.geom.Polygon.prototype.getType = function() {
+  return ol.geom.GeometryType.POLYGON;
+};
 
-  var findStyle =
-      /**
-       * @param {Array.<ol.style.Style>|string|undefined} styleValue Style
-       *     value.
-       * @return {Array.<ol.style.Style>} Style.
-       */
-      function(styleValue) {
-    if (goog.isArray(styleValue)) {
-      return styleValue;
-    } else if (goog.isString(styleValue)) {
-      // KML files in the wild occasionally forget the leading `#` on styleUrls
-      // defined in the same document.  Add a leading `#` if it enables to find
-      // a style.
-      if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) {
-        styleValue = '#' + styleValue;
-      }
-      return findStyle(sharedStyles[styleValue]);
-    } else {
-      return defaultStyle;
-    }
-  };
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.extractStyles_ = goog.isDef(options.extractStyles) ?
-      options.extractStyles : true;
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.linearRings(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
+};
 
-  /**
-   * @private
-   * @type {Object.<string, (Array.<ol.style.Style>|string)>}
-   */
-  this.sharedStyles_ = sharedStyles;
 
-  /**
-   * @private
-   * @type {ol.feature.FeatureStyleFunction}
-   */
-  this.featureStyleFunction_ =
-      /**
-       * @param {number} resolution Resolution.
-       * @return {Array.<ol.style.Style>} Style.
-       * @this {ol.Feature}
-       */
-      function(resolution) {
-    var style = /** @type {Array.<ol.style.Style>|undefined} */
-        (this.get('Style'));
-    if (goog.isDef(style)) {
-      return style;
-    }
-    var styleUrl = /** @type {string|undefined} */ (this.get('styleUrl'));
-    if (goog.isDef(styleUrl)) {
-      return findStyle(styleUrl);
+/**
+ * @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 (goog.isNull(coordinates)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
+  } else {
+    this.setLayout(opt_layout, coordinates, 2);
+    if (goog.isNull(this.flatCoordinates)) {
+      this.flatCoordinates = [];
     }
-    return defaultStyle;
-  };
-
+    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();
+  }
 };
-goog.inherits(ol.format.KML, ol.format.XMLFeature);
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<number>} ends Ends.
  */
-ol.format.KML.EXTENSIONS_ = ['.kml'];
+ol.geom.Polygon.prototype.setFlatCoordinates =
+    function(layout, flatCoordinates, ends) {
+  if (goog.isNull(flatCoordinates)) {
+    goog.asserts.assert(!goog.isNull(ends) && ends.length === 0);
+  } else if (ends.length === 0) {
+    goog.asserts.assert(flatCoordinates.length === 0);
+  } else {
+    goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]);
+  }
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.ends_ = ends;
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * 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.format.KML.GX_NAMESPACE_URIS_ = [
-  'http://www.google.com/kml/ext/2.2'
-];
+ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) {
+  var n = goog.isDef(opt_n) ? opt_n : 32;
+  /** @type {Array.<number>} */
+  var flatCoordinates = [];
+  var i;
+  for (i = 0; i < n; ++i) {
+    goog.array.extend(
+        flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n));
+  }
+  flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
+  var polygon = new ol.geom.Polygon(null);
+  polygon.setFlatCoordinates(
+      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
+  return polygon;
+};
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * 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.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'
-];
+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;
+};
+
+// FIXME remove trailing "Geometry" in method names
+
+goog.provide('ol.render.IVectorContext');
+
 
 
 /**
- * @const
- * @type {string}
- * @private
+ * VectorContext interface. Implemented by
+ * {@link ol.render.canvas.Immediate} and {@link ol.render.webgl.Immediate}.
+ * @interface
  */
-ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' +
-    'https://developers.google.com/kml/schema/kml22gx.xsd';
+ol.render.IVectorContext = function() {
+};
 
 
 /**
- * @const
- * @type {ol.Color}
- * @private
+ * @param {number} zIndex Z index.
+ * @param {function(ol.render.IVectorContext)} callback Callback.
  */
-ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1];
+ol.render.IVectorContext.prototype.drawAsync = function(zIndex, callback) {
+};
 
 
 /**
- * @const
- * @type {ol.style.Fill}
- * @private
+ * @param {ol.geom.Circle} circleGeometry Circle geometry.
+ * @param {ol.Feature} feature Feature,
  */
-ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({
-  color: ol.format.KML.DEFAULT_COLOR_
-});
+ol.render.IVectorContext.prototype.drawCircleGeometry =
+    function(circleGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {ol.Size}
- * @private
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [2, 20]; // FIXME maybe [8, 32] ?
+ol.render.IVectorContext.prototype.drawFeature = function(feature, style) {
+};
 
 
 /**
- * @const
- * @type {ol.style.IconAnchorUnits}
- * @private
+ * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
+ *     collection.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ =
-    ol.style.IconAnchorUnits.PIXELS;
+ol.render.IVectorContext.prototype.drawGeometryCollectionGeometry =
+    function(geometryCollectionGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {ol.style.IconAnchorUnits}
- * @private
+ * @param {ol.geom.Point} pointGeometry Point geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ =
-    ol.style.IconAnchorUnits.PIXELS;
+ol.render.IVectorContext.prototype.drawPointGeometry =
+    function(pointGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {ol.Size}
- * @private
+ * @param {ol.geom.LineString} lineStringGeometry Line string geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [32, 32];
+ol.render.IVectorContext.prototype.drawLineStringGeometry =
+    function(lineStringGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {string}
- * @private
+ * @param {ol.geom.MultiLineString} multiLineStringGeometry
+ *     MultiLineString geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
-    'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
+ol.render.IVectorContext.prototype.drawMultiLineStringGeometry =
+    function(multiLineStringGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {ol.style.Image}
- * @private
+ * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({
-  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_,
-  crossOrigin: 'anonymous',
-  rotation: 0,
-  scale: 1,
-  size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_,
-  src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_
-});
+ol.render.IVectorContext.prototype.drawMultiPointGeometry =
+    function(multiPointGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {ol.style.Stroke}
- * @private
+ * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
-  color: ol.format.KML.DEFAULT_COLOR_,
-  width: 1
-});
+ol.render.IVectorContext.prototype.drawMultiPolygonGeometry =
+    function(multiPolygonGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {ol.style.Style}
- * @private
+ * @param {ol.geom.Polygon} polygonGeometry Polygon geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({
-  fill: ol.format.KML.DEFAULT_FILL_STYLE_,
-  image: ol.format.KML.DEFAULT_IMAGE_STYLE_,
-  text: null, // FIXME
-  stroke: ol.format.KML.DEFAULT_STROKE_STYLE_,
-  zIndex: 0
-});
+ol.render.IVectorContext.prototype.drawPolygonGeometry =
+    function(polygonGeometry, feature) {
+};
 
 
 /**
- * @const
- * @type {Array.<ol.style.Style>}
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_];
+ol.render.IVectorContext.prototype.drawText =
+    function(flatCoordinates, offset, end, stride, geometry, feature) {
+};
 
 
 /**
- * @const
- * @type {Object.<string, ol.style.IconAnchorUnits>}
- * @private
+ * @param {ol.style.Fill} fillStyle Fill style.
+ * @param {ol.style.Stroke} strokeStyle Stroke style.
  */
-ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = {
-  'fraction': ol.style.IconAnchorUnits.FRACTION,
-  'pixels': ol.style.IconAnchorUnits.PIXELS
+ol.render.IVectorContext.prototype.setFillStrokeStyle =
+    function(fillStyle, strokeStyle) {
 };
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {ol.Color|undefined} Color.
+ * @param {ol.style.Image} imageStyle Image style.
  */
-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;
-  }
+ol.render.IVectorContext.prototype.setImageStyle = function(imageStyle) {
 };
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
+ * @param {ol.style.Text} textStyle Text style.
  */
-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;
+ol.render.IVectorContext.prototype.setTextStyle = function(textStyle) {
 };
 
+goog.provide('ol.render.Event');
+goog.provide('ol.render.EventType');
 
-/**
- * @param {Node} node Node.
- * @private
- * @return {string|undefined} Style URL.
- */
-ol.format.KML.readStyleUrl_ = function(node) {
-  var s = goog.string.trim(ol.xml.getAllTextContent(node, false));
-  if (goog.isDefAndNotNull(node.baseURI)) {
-    return goog.Uri.resolve(node.baseURI, s).toString();
-  } else {
-    return s;
-  }
-
-};
+goog.require('goog.events.Event');
+goog.require('ol.render.IVectorContext');
 
 
 /**
- * @param {Node} node Node.
- * @private
- * @return {string} URI.
+ * @enum {string}
  */
-ol.format.KML.readURI_ = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  if (goog.isDefAndNotNull(node.baseURI)) {
-    return goog.Uri.resolve(node.baseURI, goog.string.trim(s)).toString();
-  } else {
-    return goog.string.trim(s);
-  }
+ol.render.EventType = {
+  /**
+   * @event ol.render.Event#postcompose
+   * @api
+   */
+  POSTCOMPOSE: 'postcompose',
+  /**
+   * @event ol.render.Event#precompose
+   * @api
+   */
+  PRECOMPOSE: 'precompose',
+  /**
+   * @event ol.render.Event#render
+   * @api
+   */
+  RENDER: 'render'
 };
 
 
+
 /**
- * @param {Node} node Node.
- * @private
- * @return {ol.format.KMLVec2_} Vec2.
+ * @constructor
+ * @extends {goog.events.Event}
+ * @implements {oli.render.Event}
+ * @param {ol.render.EventType} type Type.
+ * @param {Object=} opt_target Target.
+ * @param {ol.render.IVectorContext=} opt_vectorContext Vector context.
+ * @param {ol.render.IReplayGroup=} opt_replayGroup Replay group.
+ * @param {olx.FrameState=} opt_frameState Frame state.
+ * @param {?CanvasRenderingContext2D=} opt_context Context.
+ * @param {?ol.webgl.Context=} opt_glContext WebGL Context.
  */
-ol.format.KML.readVec2_ = function(node) {
-  var xunits = node.getAttribute('xunits');
-  var yunits = node.getAttribute('yunits');
-  return {
-    x: parseFloat(node.getAttribute('x')),
-    xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits],
-    y: parseFloat(node.getAttribute('y')),
-    yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits]
-  };
-};
+ol.render.Event = function(
+    type, opt_target, opt_vectorContext, opt_replayGroup, opt_frameState,
+    opt_context, opt_glContext) {
 
+  goog.base(this, type, opt_target);
 
-/**
- * @param {Node} node Node.
- * @private
- * @return {number|undefined} Scale.
- */
-ol.format.KML.readScale_ = function(node) {
-  var number = ol.format.XSD.readDecimal(node);
-  if (goog.isDef(number)) {
-    return Math.sqrt(number);
-  } else {
-    return undefined;
-  }
-};
+  /**
+   * For canvas, this is an instance of {@link ol.render.canvas.Immediate}.
+   * @type {ol.render.IVectorContext|undefined}
+   * @api
+   */
+  this.vectorContext = opt_vectorContext;
 
+  /**
+   * @type {ol.render.IReplayGroup|undefined}
+   */
+  this.replayGroup = opt_replayGroup;
+
+  /**
+   * @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;
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<ol.style.Style>|string|undefined} StyleMap.
- */
-ol.format.KML.readStyleMapValue_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.style.Style>|string|undefined} */ (undefined),
-      ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack);
 };
+goog.inherits(ol.render.Event, goog.events.Event);
+
+// FIXME add rotation
+
+goog.provide('ol.render.Box');
+
+goog.require('goog.Disposable');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('ol.geom.Polygon');
+goog.require('ol.render.EventType');
+
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @constructor
+ * @extends {goog.Disposable}
+ * @param {ol.style.Style} style Style.
  */
-ol.format.KML.IconStyleParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == '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 (!goog.isDef(object)) {
-    return;
-  }
-  var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  goog.asserts.assert(goog.isObject(styleObject));
-  var IconObject = /** @type {Object} */ (goog.object.get(object, 'Icon', {}));
-  var src;
-  var href = /** @type {string|undefined} */
-      (goog.object.get(IconObject, 'href'));
-  if (goog.isDef(href)) {
-    src = href;
-  } else {
-    src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_;
-  }
-  var anchor, anchorXUnits, anchorYUnits;
-  var hotSpot = /** @type {ol.format.KMLVec2_|undefined} */
-      (goog.object.get(object, 'hotSpot'));
-  if (goog.isDef(hotSpot)) {
-    anchor = [hotSpot.x, hotSpot.y];
-    anchorXUnits = hotSpot.xunits;
-    anchorYUnits = hotSpot.yunits;
-  } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
-    anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_;
-    anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_;
-    anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_;
-  } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) {
-    anchor = [0.5, 0];
-    anchorXUnits = ol.style.IconAnchorUnits.FRACTION;
-    anchorYUnits = ol.style.IconAnchorUnits.FRACTION;
-  }
+ol.render.Box = function(style) {
 
-  var offset;
-  var x = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'x'));
-  var y = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'y'));
-  if (goog.isDef(x) && goog.isDef(y)) {
-    offset = [x, y];
-  }
+  /**
+   * @private
+   * @type {ol.Map}
+   */
+  this.map_ = null;
 
-  var size;
-  var w = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'w'));
-  var h = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'h'));
-  if (goog.isDef(w) && goog.isDef(h)) {
-    size = [w, h];
-  }
+  /**
+   * @private
+   * @type {goog.events.Key}
+   */
+  this.postComposeListenerKey_ = null;
 
-  var rotation;
-  var heading = /** @type {number|undefined} */
-      (goog.object.get(object, 'heading'));
-  if (goog.isDef(heading)) {
-    rotation = goog.math.toRadians(heading);
-  }
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.startPixel_ = null;
 
-  var scale = /** @type {number|undefined} */
-      (goog.object.get(object, 'scale'));
-  if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
-    size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
-  }
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.endPixel_ = null;
+
+  /**
+   * @private
+   * @type {ol.geom.Polygon}
+   */
+  this.geometry_ = null;
+
+  /**
+   * @private
+   * @type {ol.style.Style}
+   */
+  this.style_ = style;
 
-  var imageStyle = new ol.style.Icon({
-    anchor: anchor,
-    anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
-    anchorXUnits: anchorXUnits,
-    anchorYUnits: anchorYUnits,
-    crossOrigin: 'anonymous', // FIXME should this be configurable?
-    offset: offset,
-    offsetOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
-    rotation: rotation,
-    scale: scale,
-    size: size,
-    src: src
-  });
-  goog.object.set(styleObject, 'imageStyle', imageStyle);
 };
+goog.inherits(ol.render.Box, goog.Disposable);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {ol.geom.Polygon} Geometry.
  */
-ol.format.KML.LineStyleParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == '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 (!goog.isDef(object)) {
-    return;
-  }
-  var styleObject = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(styleObject));
-  var strokeStyle = new ol.style.Stroke({
-    color: /** @type {ol.Color} */
-        (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)),
-    width: /** @type {number} */ (goog.object.get(object, 'width', 1))
-  });
-  goog.object.set(styleObject, 'strokeStyle', strokeStyle);
+ol.render.Box.prototype.createGeometry_ = function() {
+  goog.asserts.assert(!goog.isNull(this.startPixel_));
+  goog.asserts.assert(!goog.isNull(this.endPixel_));
+  goog.asserts.assert(!goog.isNull(this.map_));
+  var startPixel = this.startPixel_;
+  var endPixel = this.endPixel_;
+  var pixels = [
+    startPixel,
+    [startPixel[0], endPixel[1]],
+    endPixel,
+    [endPixel[0], startPixel[1]]
+  ];
+  var coordinates = goog.array.map(pixels,
+      this.map_.getCoordinateFromPixel, this.map_);
+  // close the polygon
+  coordinates[4] = coordinates[0].slice();
+  return new ol.geom.Polygon([coordinates]);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @inheritDoc
+ */
+ol.render.Box.prototype.disposeInternal = function() {
+  this.setMap(null);
+};
+
+
+/**
+ * @param {ol.render.Event} event Event.
  * @private
  */
-ol.format.KML.PolyStyleParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'PolyStyle');
-  // FIXME colorMode
-  var object = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack);
-  if (!goog.isDef(object)) {
-    return;
-  }
-  var styleObject = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(styleObject));
-  var fillStyle = new ol.style.Fill({
-    color: /** @type {ol.Color} */
-        (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_))
+ol.render.Box.prototype.handleMapPostCompose_ = function(event) {
+  var geometry = this.geometry_;
+  goog.asserts.assert(goog.isDefAndNotNull(geometry));
+  var style = this.style_;
+  goog.asserts.assert(!goog.isNull(style));
+  // use drawAsync(Infinity) to draw above everything
+  event.vectorContext.drawAsync(Infinity, function(render) {
+    render.setFillStrokeStyle(style.getFill(), style.getStroke());
+    render.setTextStyle(style.getText());
+    render.drawPolygonGeometry(geometry, null);
   });
-  goog.object.set(styleObject, 'fillStyle', fillStyle);
-  var fill = /** @type {boolean|undefined} */ (goog.object.get(object, 'fill'));
-  if (goog.isDef(fill)) {
-    goog.object.set(styleObject, 'fill', fill);
-  }
-  var outline =
-      /** @type {boolean|undefined} */ (goog.object.get(object, 'outline'));
-  if (goog.isDef(outline)) {
-    goog.object.set(styleObject, 'outline', outline);
-  }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>} LinearRing flat coordinates.
+ * @return {ol.geom.Polygon} Geometry.
  */
-ol.format.KML.readFlatLinearRing_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'LinearRing');
-  return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(
-      null, ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack));
+ol.render.Box.prototype.getGeometry = function() {
+  return this.geometry_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
  * @private
  */
-ol.format.KML.gxCoordParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(goog.array.contains(
-      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI));
-  goog.asserts.assert(node.localName == 'coord');
-  var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */
-      (objectStack[objectStack.length - 1]);
-  goog.asserts.assert(goog.isObject(gxTrackObject));
-  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);
+ol.render.Box.prototype.requestMapRenderFrame_ = function() {
+  if (!goog.isNull(this.map_) &&
+      !goog.isNull(this.startPixel_) &&
+      !goog.isNull(this.endPixel_)) {
+    this.map_.render();
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ * @param {ol.Map} map Map.
  */
-ol.format.KML.readGxMultiTrack_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(goog.array.contains(
-      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI));
-  goog.asserts.assert(node.localName == 'MultiTrack');
-  var lineStrings = ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.geom.LineString>} */ ([]),
-      ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack);
-  if (!goog.isDef(lineStrings)) {
-    return undefined;
+ol.render.Box.prototype.setMap = function(map) {
+  if (!goog.isNull(this.postComposeListenerKey_)) {
+    goog.events.unlistenByKey(this.postComposeListenerKey_);
+    this.postComposeListenerKey_ = null;
+    this.map_.render();
+    this.map_ = null;
+  }
+  this.map_ = map;
+  if (!goog.isNull(this.map_)) {
+    this.postComposeListenerKey_ = goog.events.listen(
+        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
+        this);
+    this.requestMapRenderFrame_();
   }
-  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.
+ * @param {ol.Pixel} startPixel Start pixel.
+ * @param {ol.Pixel} endPixel End pixel.
  */
-ol.format.KML.readGxTrack_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(goog.array.contains(
-      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI));
-  goog.asserts.assert(node.localName == 'Track');
-  var gxTrackObject = ol.xml.pushParseAndPop(
-      /** @type {ol.format.KMLGxTrackObject_} */ ({
-        flatCoordinates: [],
-        whens: []
-      }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack);
-  if (!goog.isDef(gxTrackObject)) {
-    return undefined;
-  }
-  var flatCoordinates = gxTrackObject.flatCoordinates;
-  var whens = gxTrackObject.whens;
-  goog.asserts.assert(flatCoordinates.length / 4 == whens.length);
-  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;
+ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
+  this.startPixel_ = startPixel;
+  this.endPixel_ = endPixel;
+  this.geometry_ = this.createGeometry_();
+  this.requestMapRenderFrame_();
 };
 
+// FIXME draw drag box
+goog.provide('ol.DragBoxEvent');
+goog.provide('ol.interaction.DragBox');
+
+goog.require('goog.events.Event');
+goog.require('ol');
+goog.require('ol.events.ConditionType');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.render.Box');
+
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object} Icon object.
+ * @const
+ * @type {number}
  */
-ol.format.KML.readIcon_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Icon');
-  var iconObject = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.ICON_PARSERS_, node, objectStack);
-  if (goog.isDef(iconObject)) {
-    return iconObject;
-  } else {
-    return null;
-  }
-};
+ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED =
+    ol.DRAG_BOX_HYSTERESIS_PIXELS *
+    ol.DRAG_BOX_HYSTERESIS_PIXELS;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>} Flat coordinates.
+ * @enum {string}
  */
-ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(null,
-      ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack));
+ol.DragBoxEventType = {
+  /**
+   * Triggered upon drag box start.
+   * @event ol.DragBoxEvent#boxstart
+   * @api stable
+   */
+  BOXSTART: 'boxstart',
+  /**
+   * Triggered upon drag box end.
+   * @event ol.DragBoxEvent#boxend
+   * @api stable
+   */
+  BOXEND: 'boxend'
 };
 
 
+
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.LineString|undefined} LineString.
+ * @classdesc
+ * Events emitted by {@link ol.interaction.DragBox} instances are instances of
+ * this type.
+ *
+ * @param {string} type The event type.
+ * @param {ol.Coordinate} coordinate The event coordinate.
+ * @extends {goog.events.Event}
+ * @constructor
+ * @implements {oli.DragBoxEvent}
  */
-ol.format.KML.readLineString_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'LineString');
-  var flatCoordinates =
-      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (goog.isDef(flatCoordinates)) {
-    var lineString = new ol.geom.LineString(null);
-    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return lineString;
-  } else {
-    return undefined;
-  }
+ol.DragBoxEvent = function(type, coordinate) {
+  goog.base(this, type);
+
+  /**
+   * The coordinate of the drag event.
+   * @const
+   * @type {ol.Coordinate}
+   * @api stable
+   */
+  this.coordinate = coordinate;
+
 };
+goog.inherits(ol.DragBoxEvent, goog.events.Event);
+
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Polygon|undefined} Polygon.
+ * @classdesc
+ * Allows the user to draw a vector box by clicking and dragging on the map,
+ * normally combined with an {@link ol.events.condition} that limits
+ * it to when the shift or other key is held down. This is used, for example,
+ * for zooming to a specific area of the map
+ * (see {@link ol.interaction.DragZoom} and
+ * {@link ol.interaction.DragRotateAndZoom}).
+ *
+ * This interaction is only supported for mouse devices.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.DragBoxEvent
+ * @param {olx.interaction.DragBoxOptions=} opt_options Options.
+ * @api stable
  */
-ol.format.KML.readLinearRing_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'LinearRing');
-  var flatCoordinates =
-      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (goog.isDef(flatCoordinates)) {
-    var polygon = new ol.geom.Polygon(null);
-    polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates,
-        [flatCoordinates.length]);
-    return polygon;
-  } else {
-    return undefined;
-  }
+ol.interaction.DragBox = function(opt_options) {
+
+  goog.base(this, {
+    handleDownEvent: ol.interaction.DragBox.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragBox.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragBox.handleUpEvent_
+  });
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {ol.style.Style}
+   */
+  var style = goog.isDef(options.style) ? options.style : null;
+
+  /**
+   * @type {ol.render.Box}
+   * @private
+   */
+  this.box_ = new ol.render.Box(style);
+
+  /**
+   * @type {ol.Pixel}
+   * @private
+   */
+  this.startPixel_ = null;
+
+  /**
+   * @private
+   * @type {ol.events.ConditionType}
+   */
+  this.condition_ = goog.isDef(options.condition) ?
+      options.condition : ol.events.condition.always;
+
 };
+goog.inherits(ol.interaction.DragBox, ol.interaction.Pointer);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragBox}
  * @private
- * @return {ol.geom.Geometry} Geometry.
  */
-ol.format.KML.readMultiGeometry_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'MultiGeometry');
-  var geometries = ol.xml.pushParseAndPop(
-      /** @type {Array.<ol.geom.Geometry>} */ ([]),
-      ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack);
-  if (!goog.isDef(geometries)) {
-    return null;
-  }
-  if (geometries.length === 0) {
-    return new ol.geom.GeometryCollection(geometries);
-  }
-  var homogeneous = true;
-  var type = geometries[0].getType();
-  var geometry, i, ii;
-  for (i = 1, ii = geometries.length; i < ii; ++i) {
-    geometry = geometries[i];
-    if (geometry.getType() != type) {
-      homogeneous = false;
-      break;
-    }
-  }
-  if (homogeneous) {
-    /** @type {ol.geom.GeometryLayout} */
-    var layout;
-    /** @type {Array.<number>} */
-    var flatCoordinates;
-    if (type == ol.geom.GeometryType.POINT) {
-      var point = geometries[0];
-      goog.asserts.assertInstanceof(point, ol.geom.Point);
-      layout = point.getLayout();
-      flatCoordinates = point.getFlatCoordinates();
-      for (i = 1, ii = geometries.length; i < ii; ++i) {
-        geometry = geometries[i];
-        goog.asserts.assertInstanceof(geometry, ol.geom.Point);
-        goog.asserts.assert(geometry.getLayout() == layout);
-        ol.array.safeExtend(flatCoordinates, geometry.getFlatCoordinates());
-      }
-      var multiPoint = new ol.geom.MultiPoint(null);
-      multiPoint.setFlatCoordinates(layout, flatCoordinates);
-      return multiPoint;
-    } else if (type == ol.geom.GeometryType.LINE_STRING) {
-      var multiLineString = new ol.geom.MultiLineString(null);
-      multiLineString.setLineStrings(geometries);
-      return multiLineString;
-    } else if (type == ol.geom.GeometryType.POLYGON) {
-      var multiPolygon = new ol.geom.MultiPolygon(null);
-      multiPolygon.setPolygons(geometries);
-      return multiPolygon;
-    } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
-      return new ol.geom.GeometryCollection(geometries);
-    } else {
-      goog.asserts.fail();
-      return null;
-    }
-  } else {
-    return new ol.geom.GeometryCollection(geometries);
+ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return;
   }
+
+  this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Point|undefined} Point.
+ * Returns geometry of last drawn box.
+ * @return {ol.geom.Polygon} Geometry.
+ * @api stable
  */
-ol.format.KML.readPoint_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Point');
-  var flatCoordinates =
-      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (goog.isDefAndNotNull(flatCoordinates)) {
-    var point = new ol.geom.Point(null);
-    goog.asserts.assert(flatCoordinates.length == 3);
-    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return point;
-  } else {
-    return undefined;
-  }
+ol.interaction.DragBox.prototype.getGeometry = function() {
+  return this.box_.getGeometry();
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Polygon|undefined} Polygon.
+ * To be overriden by child classes.
+ * FIXME: use constructor option instead of relying on overridding.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @protected
  */
-ol.format.KML.readPolygon_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Polygon');
-  var flatLinearRings = ol.xml.pushParseAndPop(
-      /** @type {Array.<Array.<number>>} */ ([null]),
-      ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack);
-  if (goog.isDefAndNotNull(flatLinearRings) &&
-      !goog.isNull(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.safeExtend(flatCoordinates, flatLinearRings[i]);
-      ends.push(flatCoordinates.length);
-    }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    return polygon;
-  } else {
-    return undefined;
-  }
-};
+ol.interaction.DragBox.prototype.onBoxEnd = goog.nullFunction;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragBox}
  * @private
- * @return {Array.<ol.style.Style>} Style.
  */
-ol.format.KML.readStyle_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Style');
-  var styleObject = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.STYLE_PARSERS_, node, objectStack);
-  if (!goog.isDef(styleObject)) {
-    return null;
-  }
-  var fillStyle = /** @type {ol.style.Fill} */ (goog.object.get(
-      styleObject, 'fillStyle', ol.format.KML.DEFAULT_FILL_STYLE_));
-  var fill = /** @type {boolean|undefined} */
-      (goog.object.get(styleObject, 'fill'));
-  if (goog.isDef(fill) && !fill) {
-    fillStyle = null;
+ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return true;
   }
-  var imageStyle = /** @type {ol.style.Image} */ (goog.object.get(
-      styleObject, 'imageStyle', ol.format.KML.DEFAULT_IMAGE_STYLE_));
-  var strokeStyle = /** @type {ol.style.Stroke} */ (goog.object.get(
-      styleObject, 'strokeStyle', ol.format.KML.DEFAULT_STROKE_STYLE_));
-  var outline = /** @type {boolean|undefined} */
-      (goog.object.get(styleObject, 'outline'));
-  if (goog.isDef(outline) && !outline) {
-    strokeStyle = null;
+
+  this.box_.setMap(null);
+
+  var deltaX = mapBrowserEvent.pixel[0] - this.startPixel_[0];
+  var deltaY = mapBrowserEvent.pixel[1] - this.startPixel_[1];
+
+  if (deltaX * deltaX + deltaY * deltaY >=
+      ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED) {
+    this.onBoxEnd(mapBrowserEvent);
+    this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXEND,
+        mapBrowserEvent.coordinate));
   }
-  return [new ol.style.Style({
-    fill: fillStyle,
-    image: imageStyle,
-    stroke: strokeStyle,
-    text: null, // FIXME
-    zIndex: undefined // FIXME
-  })];
+  return false;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragBox}
  * @private
  */
-ol.format.KML.DataParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Data');
-  var name = node.getAttribute('name');
-  if (!goog.isNull(name)) {
-    var data = ol.xml.pushParseAndPop(
-        undefined, ol.format.KML.DATA_PARSERS_, node, objectStack);
-    if (goog.isDef(data)) {
-      var featureObject =
-          /** @type {Object} */ (objectStack[objectStack.length - 1]);
-      goog.asserts.assert(goog.isObject(featureObject));
-      goog.object.set(featureObject, name, data);
-    }
+ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return false;
+  }
+
+  var browserEvent = mapBrowserEvent.browserEvent;
+  if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) {
+    this.startPixel_ = mapBrowserEvent.pixel;
+    this.box_.setMap(mapBrowserEvent.map);
+    this.box_.setPixels(this.startPixel_, this.startPixel_);
+    this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXSTART,
+        mapBrowserEvent.coordinate));
+    return true;
+  } else {
+    return false;
   }
 };
 
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @fileoverview Namespace with crypto related helper functions.
  */
-ol.format.KML.ExtendedDataParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ExtendedData');
-  ol.xml.parse(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack);
-};
+
+goog.provide('goog.crypt');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Turns a string into an array of bytes; a "byte" being a JS number in the
+ * range 0-255.
+ * @param {string} str String value to arrify.
+ * @return {!Array<number>} Array of numbers corresponding to the
+ *     UCS character codes of each character in str.
  */
-ol.format.KML.PairDataParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Pair');
-  var pairObject = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.PAIR_PARSERS_, node, objectStack);
-  if (!goog.isDef(pairObject)) {
-    return;
-  }
-  var key = /** @type {string|undefined} */
-      (goog.object.get(pairObject, 'key'));
-  if (goog.isDef(key) && key == 'normal') {
-    var styleUrl = /** @type {string|undefined} */
-        (goog.object.get(pairObject, 'styleUrl'));
-    if (goog.isDef(styleUrl)) {
-      objectStack[objectStack.length - 1] = styleUrl;
-    }
-    var Style = /** @type {ol.style.Style} */
-        (goog.object.get(pairObject, 'Style'));
-    if (goog.isDef(Style)) {
-      objectStack[objectStack.length - 1] = Style;
+goog.crypt.stringToByteArray = function(str) {
+  var output = [], p = 0;
+  for (var i = 0; i < str.length; i++) {
+    var c = str.charCodeAt(i);
+    while (c > 0xff) {
+      output[p++] = c & 0xff;
+      c >>= 8;
     }
+    output[p++] = c;
   }
+  return output;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Turns an array of numbers into the string given by the concatenation of the
+ * characters to which the numbers correspond.
+ * @param {Array<number>} bytes Array of numbers representing characters.
+ * @return {string} Stringification of the array.
  */
-ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'StyleMap');
-  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
-  if (!goog.isDef(styleMapValue)) {
-    return;
+goog.crypt.byteArrayToString = function(bytes) {
+  var CHUNK_SIZE = 8192;
+
+  // Special-case the simple case for speed's sake.
+  if (bytes.length < CHUNK_SIZE) {
+    return String.fromCharCode.apply(null, bytes);
   }
-  var placemarkObject = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(placemarkObject));
-  if (goog.isArray(styleMapValue)) {
-    goog.object.set(placemarkObject, 'Style', styleMapValue);
-  } else if (goog.isString(styleMapValue)) {
-    goog.object.set(placemarkObject, 'styleUrl', styleMapValue);
-  } else {
-    goog.asserts.fail();
+
+  // The remaining logic splits conversion by chunks since
+  // Function#apply() has a maximum parameter count.
+  // See discussion: http://goo.gl/LrWmZ9
+
+  var str = '';
+  for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
+    var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE);
+    str += String.fromCharCode.apply(null, chunk);
   }
+  return str;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Turns an array of numbers into the hex string given by the concatenation of
+ * the hex values to which the numbers correspond.
+ * @param {Uint8Array|Array<number>} array Array of numbers representing
+ *     characters.
+ * @return {string} Hex string.
  */
-ol.format.KML.SchemaDataParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'SchemaData');
-  ol.xml.parse(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack);
+goog.crypt.byteArrayToHex = function(array) {
+  return goog.array.map(array, function(numByte) {
+    var hexByte = numByte.toString(16);
+    return hexByte.length > 1 ? hexByte : '0' + hexByte;
+  }).join('');
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Converts a hex string into an integer array.
+ * @param {string} hexString Hex string of 16-bit integers (two characters
+ *     per integer).
+ * @return {!Array<number>} Array of {0,255} integers for the given string.
  */
-ol.format.KML.SimpleDataParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'SimpleData');
-  var name = node.getAttribute('name');
-  if (!goog.isNull(name)) {
-    var data = ol.format.XSD.readString(node);
-    var featureObject =
-        /** @type {Object} */ (objectStack[objectStack.length - 1]);
-    goog.object.set(featureObject, name, data);
+goog.crypt.hexToByteArray = function(hexString) {
+  goog.asserts.assert(hexString.length % 2 == 0,
+                      'Key string length must be multiple of 2');
+  var arr = [];
+  for (var i = 0; i < hexString.length; i += 2) {
+    arr.push(parseInt(hexString.substring(i, i + 2), 16));
   }
+  return arr;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Converts a JS string to a UTF-8 "byte" array.
+ * @param {string} str 16-bit unicode string.
+ * @return {!Array<number>} UTF-8 byte array.
  */
-ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'innerBoundaryIs');
-  var flatLinearRing = ol.xml.pushParseAndPop(
-      /** @type {Array.<number>|undefined} */ (undefined),
-      ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack);
-  if (goog.isDef(flatLinearRing)) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    goog.asserts.assert(goog.isArray(flatLinearRings));
-    goog.asserts.assert(flatLinearRings.length > 0);
-    flatLinearRings.push(flatLinearRing);
+goog.crypt.stringToUtf8ByteArray = function(str) {
+  // TODO(user): Use native implementations if/when available
+  str = str.replace(/\r\n/g, '\n');
+  var out = [], p = 0;
+  for (var i = 0; i < str.length; i++) {
+    var c = str.charCodeAt(i);
+    if (c < 128) {
+      out[p++] = c;
+    } else if (c < 2048) {
+      out[p++] = (c >> 6) | 192;
+      out[p++] = (c & 63) | 128;
+    } else {
+      out[p++] = (c >> 12) | 224;
+      out[p++] = ((c >> 6) & 63) | 128;
+      out[p++] = (c & 63) | 128;
+    }
   }
+  return out;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
+ * @param {Uint8Array|Array<number>} bytes UTF-8 byte array.
+ * @return {string} 16-bit Unicode string.
  */
-ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'outerBoundaryIs');
-  var flatLinearRing = ol.xml.pushParseAndPop(
-      /** @type {Array.<number>|undefined} */ (undefined),
-      ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack);
-  if (goog.isDef(flatLinearRing)) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    goog.asserts.assert(goog.isArray(flatLinearRings));
-    goog.asserts.assert(flatLinearRings.length > 0);
-    flatLinearRings[0] = flatLinearRing;
+goog.crypt.utf8ByteArrayToString = function(bytes) {
+  // TODO(user): Use native implementations if/when available
+  var out = [], pos = 0, c = 0;
+  while (pos < bytes.length) {
+    var c1 = bytes[pos++];
+    if (c1 < 128) {
+      out[c++] = String.fromCharCode(c1);
+    } else if (c1 > 191 && c1 < 224) {
+      var c2 = bytes[pos++];
+      out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
+    } else {
+      var c2 = bytes[pos++];
+      var c3 = bytes[pos++];
+      out[c++] = String.fromCharCode(
+          (c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
+    }
   }
+  return out.join('');
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * XOR two byte arrays.
+ * @param {!ArrayBufferView|!Array<number>} bytes1 Byte array 1.
+ * @param {!ArrayBufferView|!Array<number>} bytes2 Byte array 2.
+ * @return {!Array<number>} Resulting XOR of the two byte arrays.
  */
-ol.format.KML.whenParser_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'when');
-  var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */
-      (objectStack[objectStack.length - 1]);
-  goog.asserts.assert(goog.isObject(gxTrackObject));
-  var whens = gxTrackObject.whens;
-  var s = ol.xml.getAllTextContent(node, false);
-  var re =
-      /^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/;
-  var m = re.exec(s);
-  if (m) {
-    var year = parseInt(m[1], 10);
-    var month = goog.isDef(m[3]) ? parseInt(m[3], 10) - 1 : 0;
-    var day = goog.isDef(m[5]) ? parseInt(m[5], 10) : 1;
-    var hour = goog.isDef(m[7]) ? parseInt(m[7], 10) : 0;
-    var minute = goog.isDef(m[8]) ? parseInt(m[8], 10) : 0;
-    var second = goog.isDef(m[9]) ? parseInt(m[9], 10) : 0;
-    var when = Date.UTC(year, month, day, hour, minute, second);
-    if (goog.isDef(m[10]) && m[10] != 'Z') {
-      var sign = m[11] == '-' ? -1 : 1;
-      when += sign * 60 * parseInt(m[12], 10);
-      if (goog.isDef(m[13])) {
-        when += sign * 60 * 60 * parseInt(m[13], 10);
-      }
-    }
-    whens.push(when);
-  } else {
-    whens.push(0);
+goog.crypt.xorByteArray = function(bytes1, bytes2) {
+  goog.asserts.assert(
+      bytes1.length == bytes2.length,
+      'XOR array lengths must match');
+
+  var result = [];
+  for (var i = 0; i < bytes1.length; i++) {
+    result.push(bytes1[i] ^ bytes2[i]);
   }
+  return result;
 };
 
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @fileoverview Abstract cryptographic hash interface.
+ *
+ * See goog.crypt.Sha1 and goog.crypt.Md5 for sample implementations.
+ *
  */
-ol.format.KML.DATA_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'value': ol.xml.makeReplacer(ol.format.XSD.readString)
-    });
+
+goog.provide('goog.crypt.Hash');
+
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Create a cryptographic hash instance.
+ *
+ * @constructor
+ * @struct
  */
-ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Data': ol.format.KML.DataParser_,
-      'SchemaData': ol.format.KML.SchemaDataParser_
-    });
+goog.crypt.Hash = function() {
+  /**
+   * The block size for the hasher.
+   * @type {number}
+   */
+  this.blockSize = -1;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Resets the internal accumulator.
  */
-ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
-    });
+goog.crypt.Hash.prototype.reset = goog.abstractMethod;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Adds a byte array (array with values in [0-255] range) or a string (might
+ * only contain 8-bit, i.e., Latin1 characters) to the internal accumulator.
+ *
+ * Many hash functions operate on blocks of data and implement optimizations
+ * when a full chunk of data is readily available. Hence it is often preferable
+ * to provide large chunks of data (a kilobyte or more) than to repeatedly
+ * call the update method with few tens of bytes. If this is not possible, or
+ * not feasible, it might be good to provide data in multiplies of hash block
+ * size (often 64 bytes). Please see the implementation and performance tests
+ * of your favourite hash.
+ *
+ * @param {Array<number>|Uint8Array|string} bytes Data used for the update.
+ * @param {number=} opt_length Number of bytes to use.
  */
-ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_,
-      'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_
-    });
+goog.crypt.Hash.prototype.update = goog.abstractMethod;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {!Array<number>} The finalized hash computed
+ *     from the internal accumulator.
  */
-ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'when': ol.format.KML.whenParser_
-    }, ol.xml.makeParsersNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'coord': ol.format.KML.gxCoordParser_
-        }));
+goog.crypt.Hash.prototype.digest = goog.abstractMethod;
 
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @fileoverview MD5 cryptographic hash.
+ * Implementation of http://tools.ietf.org/html/rfc1321 with common
+ * optimizations and tweaks (see http://en.wikipedia.org/wiki/MD5).
+ *
+ * Usage:
+ *   var md5 = new goog.crypt.Md5();
+ *   md5.update(bytes);
+ *   var hash = md5.digest();
+ *
+ * Performance:
+ *   Chrome 23              ~680 Mbit/s
+ *   Chrome 13 (in a VM)    ~250 Mbit/s
+ *   Firefox 6.0 (in a VM)  ~100 Mbit/s
+ *   IE9 (in a VM)           ~27 Mbit/s
+ *   Firefox 3.6             ~15 Mbit/s
+ *   IE8 (in a VM)           ~13 Mbit/s
+ *
  */
-ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
-    });
+
+goog.provide('goog.crypt.Md5');
+
+goog.require('goog.crypt.Hash');
+
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * MD5 cryptographic hash constructor.
+ * @constructor
+ * @extends {goog.crypt.Hash}
+ * @final
+ * @struct
  */
-ol.format.KML.ICON_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
-    }, ol.xml.makeParsersNS(
-        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)
-        }));
+goog.crypt.Md5 = function() {
+  goog.crypt.Md5.base(this, 'constructor');
+
+  this.blockSize = 512 / 8;
+
+  /**
+   * Holds the current values of accumulated A-D variables (MD buffer).
+   * @type {!Array<number>}
+   * @private
+   */
+  this.chain_ = new Array(4);
+
+  /**
+   * A buffer holding the data until the whole block can be processed.
+   * @type {!Array<number>}
+   * @private
+   */
+  this.block_ = new Array(this.blockSize);
+
+  /**
+   * The length of yet-unprocessed data as collected in the block.
+   * @type {number}
+   * @private
+   */
+  this.blockLength_ = 0;
+
+  /**
+   * The total length of the message so far.
+   * @type {number}
+   * @private
+   */
+  this.totalLength_ = 0;
+
+  this.reset();
+};
+goog.inherits(goog.crypt.Md5, goog.crypt.Hash);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * Integer rotation constants used by the abbreviated implementation.
+ * They are hardcoded in the unrolled implementation, so it is left
+ * here commented out.
+ * @type {Array<number>}
  * @private
+ *
+goog.crypt.Md5.S_ = [
+  7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
+  5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
+  4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
+  6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
+];
  */
-ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_),
-      'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_),
-      'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
-    });
-
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Sine function constants used by the abbreviated implementation.
+ * They are hardcoded in the unrolled implementation, so it is left
+ * here commented out.
+ * @type {Array<number>}
+ * @private
+ *
+goog.crypt.Md5.T_ = [
+  0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+  0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+  0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+  0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+  0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+  0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+  0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+  0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+  0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+  0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+  0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
+  0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+  0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+  0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+  0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+  0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+];
  */
-ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
-    });
+
+
+/** @override */
+goog.crypt.Md5.prototype.reset = function() {
+  this.chain_[0] = 0x67452301;
+  this.chain_[1] = 0xefcdab89;
+  this.chain_[2] = 0x98badcfe;
+  this.chain_[3] = 0x10325476;
+
+  this.blockLength_ = 0;
+  this.totalLength_ = 0;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * Internal compress helper function. It takes a block of data (64 bytes)
+ * and updates the accumulator.
+ * @param {Array<number>|Uint8Array|string} buf The block to compress.
+ * @param {number=} opt_offset Offset of the block in the buffer.
  * @private
  */
-ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
-      'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
-    });
+goog.crypt.Md5.prototype.compress_ = function(buf, opt_offset) {
+  if (!opt_offset) {
+    opt_offset = 0;
+  }
+
+  // We allocate the array every time, but it's cheap in practice.
+  var X = new Array(16);
+
+  // Get 16 little endian words. It is not worth unrolling this for Chrome 11.
+  if (goog.isString(buf)) {
+    for (var i = 0; i < 16; ++i) {
+      X[i] = (buf.charCodeAt(opt_offset++)) |
+             (buf.charCodeAt(opt_offset++) << 8) |
+             (buf.charCodeAt(opt_offset++) << 16) |
+             (buf.charCodeAt(opt_offset++) << 24);
+    }
+  } else {
+    for (var i = 0; i < 16; ++i) {
+      X[i] = (buf[opt_offset++]) |
+             (buf[opt_offset++] << 8) |
+             (buf[opt_offset++] << 16) |
+             (buf[opt_offset++] << 24);
+    }
+  }
+
+  var A = this.chain_[0];
+  var B = this.chain_[1];
+  var C = this.chain_[2];
+  var D = this.chain_[3];
+  var sum = 0;
+
+  /*
+   * This is an abbreviated implementation, it is left here commented out for
+   * reference purposes. See below for an unrolled version in use.
+   *
+  var f, n, tmp;
+  for (var i = 0; i < 64; ++i) {
+
+    if (i < 16) {
+      f = (D ^ (B & (C ^ D)));
+      n = i;
+    } else if (i < 32) {
+      f = (C ^ (D & (B ^ C)));
+      n = (5 * i + 1) % 16;
+    } else if (i < 48) {
+      f = (B ^ C ^ D);
+      n = (3 * i + 5) % 16;
+    } else {
+      f = (C ^ (B | (~D)));
+      n = (7 * i) % 16;
+    }
+
+    tmp = D;
+    D = C;
+    C = B;
+    sum = (A + f + goog.crypt.Md5.T_[i] + X[n]) & 0xffffffff;
+    B += ((sum << goog.crypt.Md5.S_[i]) & 0xffffffff) |
+         (sum >>> (32 - goog.crypt.Md5.S_[i]));
+    A = tmp;
+  }
+   */
+
+  /*
+   * This is an unrolled MD5 implementation, which gives ~30% speedup compared
+   * to the abbreviated implementation above, as measured on Chrome 11. It is
+   * important to keep 32-bit croppings to minimum and inline the integer
+   * rotation.
+   */
+  sum = (A + (D ^ (B & (C ^ D))) + X[0] + 0xd76aa478) & 0xffffffff;
+  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
+  sum = (D + (C ^ (A & (B ^ C))) + X[1] + 0xe8c7b756) & 0xffffffff;
+  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
+  sum = (C + (B ^ (D & (A ^ B))) + X[2] + 0x242070db) & 0xffffffff;
+  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
+  sum = (B + (A ^ (C & (D ^ A))) + X[3] + 0xc1bdceee) & 0xffffffff;
+  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
+  sum = (A + (D ^ (B & (C ^ D))) + X[4] + 0xf57c0faf) & 0xffffffff;
+  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
+  sum = (D + (C ^ (A & (B ^ C))) + X[5] + 0x4787c62a) & 0xffffffff;
+  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
+  sum = (C + (B ^ (D & (A ^ B))) + X[6] + 0xa8304613) & 0xffffffff;
+  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
+  sum = (B + (A ^ (C & (D ^ A))) + X[7] + 0xfd469501) & 0xffffffff;
+  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
+  sum = (A + (D ^ (B & (C ^ D))) + X[8] + 0x698098d8) & 0xffffffff;
+  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
+  sum = (D + (C ^ (A & (B ^ C))) + X[9] + 0x8b44f7af) & 0xffffffff;
+  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
+  sum = (C + (B ^ (D & (A ^ B))) + X[10] + 0xffff5bb1) & 0xffffffff;
+  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
+  sum = (B + (A ^ (C & (D ^ A))) + X[11] + 0x895cd7be) & 0xffffffff;
+  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
+  sum = (A + (D ^ (B & (C ^ D))) + X[12] + 0x6b901122) & 0xffffffff;
+  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
+  sum = (D + (C ^ (A & (B ^ C))) + X[13] + 0xfd987193) & 0xffffffff;
+  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
+  sum = (C + (B ^ (D & (A ^ B))) + X[14] + 0xa679438e) & 0xffffffff;
+  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
+  sum = (B + (A ^ (C & (D ^ A))) + X[15] + 0x49b40821) & 0xffffffff;
+  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
+  sum = (A + (C ^ (D & (B ^ C))) + X[1] + 0xf61e2562) & 0xffffffff;
+  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
+  sum = (D + (B ^ (C & (A ^ B))) + X[6] + 0xc040b340) & 0xffffffff;
+  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
+  sum = (C + (A ^ (B & (D ^ A))) + X[11] + 0x265e5a51) & 0xffffffff;
+  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
+  sum = (B + (D ^ (A & (C ^ D))) + X[0] + 0xe9b6c7aa) & 0xffffffff;
+  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
+  sum = (A + (C ^ (D & (B ^ C))) + X[5] + 0xd62f105d) & 0xffffffff;
+  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
+  sum = (D + (B ^ (C & (A ^ B))) + X[10] + 0x02441453) & 0xffffffff;
+  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
+  sum = (C + (A ^ (B & (D ^ A))) + X[15] + 0xd8a1e681) & 0xffffffff;
+  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
+  sum = (B + (D ^ (A & (C ^ D))) + X[4] + 0xe7d3fbc8) & 0xffffffff;
+  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
+  sum = (A + (C ^ (D & (B ^ C))) + X[9] + 0x21e1cde6) & 0xffffffff;
+  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
+  sum = (D + (B ^ (C & (A ^ B))) + X[14] + 0xc33707d6) & 0xffffffff;
+  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
+  sum = (C + (A ^ (B & (D ^ A))) + X[3] + 0xf4d50d87) & 0xffffffff;
+  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
+  sum = (B + (D ^ (A & (C ^ D))) + X[8] + 0x455a14ed) & 0xffffffff;
+  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
+  sum = (A + (C ^ (D & (B ^ C))) + X[13] + 0xa9e3e905) & 0xffffffff;
+  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
+  sum = (D + (B ^ (C & (A ^ B))) + X[2] + 0xfcefa3f8) & 0xffffffff;
+  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
+  sum = (C + (A ^ (B & (D ^ A))) + X[7] + 0x676f02d9) & 0xffffffff;
+  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
+  sum = (B + (D ^ (A & (C ^ D))) + X[12] + 0x8d2a4c8a) & 0xffffffff;
+  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
+  sum = (A + (B ^ C ^ D) + X[5] + 0xfffa3942) & 0xffffffff;
+  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
+  sum = (D + (A ^ B ^ C) + X[8] + 0x8771f681) & 0xffffffff;
+  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
+  sum = (C + (D ^ A ^ B) + X[11] + 0x6d9d6122) & 0xffffffff;
+  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
+  sum = (B + (C ^ D ^ A) + X[14] + 0xfde5380c) & 0xffffffff;
+  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
+  sum = (A + (B ^ C ^ D) + X[1] + 0xa4beea44) & 0xffffffff;
+  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
+  sum = (D + (A ^ B ^ C) + X[4] + 0x4bdecfa9) & 0xffffffff;
+  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
+  sum = (C + (D ^ A ^ B) + X[7] + 0xf6bb4b60) & 0xffffffff;
+  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
+  sum = (B + (C ^ D ^ A) + X[10] + 0xbebfbc70) & 0xffffffff;
+  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
+  sum = (A + (B ^ C ^ D) + X[13] + 0x289b7ec6) & 0xffffffff;
+  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
+  sum = (D + (A ^ B ^ C) + X[0] + 0xeaa127fa) & 0xffffffff;
+  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
+  sum = (C + (D ^ A ^ B) + X[3] + 0xd4ef3085) & 0xffffffff;
+  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
+  sum = (B + (C ^ D ^ A) + X[6] + 0x04881d05) & 0xffffffff;
+  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
+  sum = (A + (B ^ C ^ D) + X[9] + 0xd9d4d039) & 0xffffffff;
+  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
+  sum = (D + (A ^ B ^ C) + X[12] + 0xe6db99e5) & 0xffffffff;
+  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
+  sum = (C + (D ^ A ^ B) + X[15] + 0x1fa27cf8) & 0xffffffff;
+  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
+  sum = (B + (C ^ D ^ A) + X[2] + 0xc4ac5665) & 0xffffffff;
+  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
+  sum = (A + (C ^ (B | (~D))) + X[0] + 0xf4292244) & 0xffffffff;
+  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
+  sum = (D + (B ^ (A | (~C))) + X[7] + 0x432aff97) & 0xffffffff;
+  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
+  sum = (C + (A ^ (D | (~B))) + X[14] + 0xab9423a7) & 0xffffffff;
+  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
+  sum = (B + (D ^ (C | (~A))) + X[5] + 0xfc93a039) & 0xffffffff;
+  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
+  sum = (A + (C ^ (B | (~D))) + X[12] + 0x655b59c3) & 0xffffffff;
+  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
+  sum = (D + (B ^ (A | (~C))) + X[3] + 0x8f0ccc92) & 0xffffffff;
+  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
+  sum = (C + (A ^ (D | (~B))) + X[10] + 0xffeff47d) & 0xffffffff;
+  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
+  sum = (B + (D ^ (C | (~A))) + X[1] + 0x85845dd1) & 0xffffffff;
+  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
+  sum = (A + (C ^ (B | (~D))) + X[8] + 0x6fa87e4f) & 0xffffffff;
+  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
+  sum = (D + (B ^ (A | (~C))) + X[15] + 0xfe2ce6e0) & 0xffffffff;
+  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
+  sum = (C + (A ^ (D | (~B))) + X[6] + 0xa3014314) & 0xffffffff;
+  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
+  sum = (B + (D ^ (C | (~A))) + X[13] + 0x4e0811a1) & 0xffffffff;
+  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
+  sum = (A + (C ^ (B | (~D))) + X[4] + 0xf7537e82) & 0xffffffff;
+  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
+  sum = (D + (B ^ (A | (~C))) + X[11] + 0xbd3af235) & 0xffffffff;
+  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
+  sum = (C + (A ^ (D | (~B))) + X[2] + 0x2ad7d2bb) & 0xffffffff;
+  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
+  sum = (B + (D ^ (C | (~A))) + X[9] + 0xeb86d391) & 0xffffffff;
+  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
+
+  this.chain_[0] = (this.chain_[0] + A) & 0xffffffff;
+  this.chain_[1] = (this.chain_[1] + B) & 0xffffffff;
+  this.chain_[2] = (this.chain_[2] + C) & 0xffffffff;
+  this.chain_[3] = (this.chain_[3] + D) & 0xffffffff;
+};
+
+
+/** @override */
+goog.crypt.Md5.prototype.update = function(bytes, opt_length) {
+  if (!goog.isDef(opt_length)) {
+    opt_length = bytes.length;
+  }
+  var lengthMinusBlock = opt_length - this.blockSize;
+
+  // Copy some object properties to local variables in order to save on access
+  // time from inside the loop (~10% speedup was observed on Chrome 11).
+  var block = this.block_;
+  var blockLength = this.blockLength_;
+  var i = 0;
+
+  // The outer while loop should execute at most twice.
+  while (i < opt_length) {
+    // When we have no data in the block to top up, we can directly process the
+    // input buffer (assuming it contains sufficient data). This gives ~30%
+    // speedup on Chrome 14 and ~70% speedup on Firefox 6.0, but requires that
+    // the data is provided in large chunks (or in multiples of 64 bytes).
+    if (blockLength == 0) {
+      while (i <= lengthMinusBlock) {
+        this.compress_(bytes, i);
+        i += this.blockSize;
+      }
+    }
+
+    if (goog.isString(bytes)) {
+      while (i < opt_length) {
+        block[blockLength++] = bytes.charCodeAt(i++);
+        if (blockLength == this.blockSize) {
+          this.compress_(block);
+          blockLength = 0;
+          // Jump to the outer loop so we use the full-block optimization.
+          break;
+        }
+      }
+    } else {
+      while (i < opt_length) {
+        block[blockLength++] = bytes[i++];
+        if (blockLength == this.blockSize) {
+          this.compress_(block);
+          blockLength = 0;
+          // Jump to the outer loop so we use the full-block optimization.
+          break;
+        }
+      }
+    }
+  }
+
+  this.blockLength_ = blockLength;
+  this.totalLength_ += opt_length;
+};
+
+
+/** @override */
+goog.crypt.Md5.prototype.digest = function() {
+  // This must accommodate at least 1 padding byte (0x80), 8 bytes of
+  // total bitlength, and must end at a 64-byte boundary.
+  var pad = new Array((this.blockLength_ < 56 ?
+                       this.blockSize :
+                       this.blockSize * 2) - this.blockLength_);
+
+  // Add padding: 0x80 0x00*
+  pad[0] = 0x80;
+  for (var i = 1; i < pad.length - 8; ++i) {
+    pad[i] = 0;
+  }
+  // Add the total number of bits, little endian 64-bit integer.
+  var totalBits = this.totalLength_ * 8;
+  for (var i = pad.length - 8; i < pad.length; ++i) {
+    pad[i] = totalBits & 0xff;
+    totalBits /= 0x100; // Don't use bit-shifting here!
+  }
+  this.update(pad);
+
+  var digest = new Array(16);
+  var n = 0;
+  for (var i = 0; i < 4; ++i) {
+    for (var j = 0; j < 32; j += 8) {
+      digest[n++] = (this.chain_[i] >>> j) & 0xff;
+    }
+  }
+  return digest;
+};
+
+goog.provide('ol.structs.IHasChecksum');
+
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @interface
  */
-ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS(
-    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_)
-    });
+ol.structs.IHasChecksum = function() {
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {string} The checksum.
  */
-ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.GX_NAMESPACE_URIS_, {
-      'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_)
-    });
+ol.structs.IHasChecksum.prototype.getChecksum = function() {
+};
+
+goog.provide('ol.style.Stroke');
+
+goog.require('goog.crypt');
+goog.require('goog.crypt.Md5');
+goog.require('ol.color');
+goog.require('ol.structs.IHasChecksum');
+
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @classdesc
+ * Set stroke style for vector features.
+ * Note that the defaults given are the Canvas defaults, which will be used if
+ * option is not defined. The `get` functions return whatever was entered in
+ * the options; they will not return the default.
+ *
+ * @constructor
+ * @param {olx.style.StrokeOptions=} opt_options Options.
+ * @implements {ol.structs.IHasChecksum}
+ * @api
  */
-ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
-    });
+ol.style.Stroke = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {ol.Color|string}
+   */
+  this.color_ = goog.isDef(options.color) ? options.color : null;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.lineCap_ = options.lineCap;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.lineDash_ = goog.isDef(options.lineDash) ? 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;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {ol.Color|string} Color.
+ * @api
  */
-ol.format.KML.PAIR_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_),
-      'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyleUrl_)
-    });
+ol.style.Stroke.prototype.getColor = function() {
+  return this.color_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {string|undefined} Line cap.
+ * @api
  */
-ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeParsersNS(
-    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.makeParsersNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'MultiTrack': ol.xml.makeObjectPropertySetter(
-              ol.format.KML.readGxMultiTrack_, 'geometry'),
-          'Track': ol.xml.makeObjectPropertySetter(
-              ol.format.KML.readGxTrack_, 'geometry')
-        }
-    ));
+ol.style.Stroke.prototype.getLineCap = function() {
+  return this.lineCap_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {Array.<number>} Line dash.
+ * @api
  */
-ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
-      'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
-      'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
-    });
+ol.style.Stroke.prototype.getLineDash = function() {
+  return this.lineDash_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {string|undefined} Line join.
+ * @api
  */
-ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'SimpleData': ol.format.KML.SimpleDataParser_
-    });
+ol.style.Stroke.prototype.getLineJoin = function() {
+  return this.lineJoin_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {number|undefined} Miter limit.
+ * @api
  */
-ol.format.KML.STYLE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'IconStyle': ol.format.KML.IconStyleParser_,
-      'LineStyle': ol.format.KML.LineStyleParser_,
-      'PolyStyle': ol.format.KML.PolyStyleParser_
-    });
+ol.style.Stroke.prototype.getMiterLimit = function() {
+  return this.miterLimit_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {number|undefined} Width.
+ * @api
  */
-ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Pair': ol.format.KML.PairDataParser_
-    });
+ol.style.Stroke.prototype.getWidth = function() {
+  return this.width_;
+};
 
 
 /**
- * @inheritDoc
+ * Set the color.
+ *
+ * @param {ol.Color|string} color Color.
+ * @api
  */
-ol.format.KML.prototype.getExtensions = function() {
-  return ol.format.KML.EXTENSIONS_;
+ol.style.Stroke.prototype.setColor = function(color) {
+  this.color_ = color;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<ol.Feature>|undefined} Features.
+ * Set the line cap.
+ *
+ * @param {string|undefined} lineCap Line cap.
+ * @api
  */
-ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  var localName = ol.xml.getLocalName(node);
-  goog.asserts.assert(localName == 'Document' || localName == 'Folder');
-  // FIXME use scope somehow
-  var parsersNS = ol.xml.makeParsersNS(
-      ol.format.KML.NAMESPACE_URIS_, {
-        'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this),
-        'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this),
-        'Style': goog.bind(this.readSharedStyle_, this),
-        'StyleMap': goog.bind(this.readSharedStyleMap_, this)
-      });
-  var features = ol.xml.pushParseAndPop(/** @type {Array.<ol.Feature>} */ ([]),
-      parsersNS, node, objectStack, this);
-  if (goog.isDef(features)) {
-    return features;
-  } else {
-    return undefined;
-  }
+ol.style.Stroke.prototype.setLineCap = function(lineCap) {
+  this.lineCap_ = lineCap;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Feature.
+ * Set the line dash.
+ *
+ * @param {Array.<number>} lineDash Line dash.
+ * @api
  */
-ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Placemark');
-  var object = ol.xml.pushParseAndPop({'geometry': null},
-      ol.format.KML.PLACEMARK_PARSERS_, node, objectStack);
-  if (!goog.isDef(object)) {
-    return undefined;
-  }
-  var feature = new ol.Feature();
-  var id = node.getAttribute('id');
-  if (!goog.isNull(id)) {
-    feature.setId(id);
-  }
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  if (goog.isDefAndNotNull(object.geometry)) {
-    ol.format.Feature.transformWithOptions(object.geometry, false, options);
-  }
-  feature.setProperties(object);
-  if (this.extractStyles_) {
-    feature.setStyle(this.featureStyleFunction_);
-  }
-  return feature;
+ol.style.Stroke.prototype.setLineDash = function(lineDash) {
+  this.lineDash_ = lineDash;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Set the line join.
+ *
+ * @param {string|undefined} lineJoin Line join.
+ * @api
  */
-ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Style');
-  var id = node.getAttribute('id');
-  if (!goog.isNull(id)) {
-    var style = ol.format.KML.readStyle_(node, objectStack);
-    if (goog.isDef(style)) {
-      var styleUri;
-      if (goog.isDefAndNotNull(node.baseURI)) {
-        styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString();
-      } else {
-        styleUri = '#' + id;
-      }
-      this.sharedStyles_[styleUri] = style;
-    }
-  }
+ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
+  this.lineJoin_ = lineJoin;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'StyleMap');
-  var id = node.getAttribute('id');
-  if (goog.isNull(id)) {
-    return;
-  }
-  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
-  if (!goog.isDef(styleMapValue)) {
-    return;
-  }
-  var styleUri;
-  if (goog.isDefAndNotNull(node.baseURI)) {
-    styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString();
-  } else {
-    styleUri = '#' + id;
-  }
-  this.sharedStyles_[styleUri] = styleMapValue;
+ * Set the miter limit.
+ *
+ * @param {number|undefined} miterLimit Miter limit.
+ * @api
+ */
+ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
+  this.miterLimit_ = miterLimit;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * Read the first feature from a KML source.
+ * Set the width.
  *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
+ * @param {number|undefined} width Width.
+ * @api
  */
-ol.format.KML.prototype.readFeature;
+ol.style.Stroke.prototype.setWidth = function(width) {
+  this.width_ = width;
+  this.checksum_ = undefined;
+};
 
 
 /**
  * @inheritDoc
  */
-ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
-    return null;
-  }
-  goog.asserts.assert(node.localName == 'Placemark');
-  var feature = this.readPlacemark_(
-      node, [this.getReadOptions(node, opt_options)]);
-  if (goog.isDef(feature)) {
-    return feature;
-  } else {
-    return null;
+ol.style.Stroke.prototype.getChecksum = function() {
+  if (!goog.isDef(this.checksum_)) {
+    var raw = 's' +
+        (!goog.isNull(this.color_) ?
+            ol.color.asString(this.color_) : '-') + ',' +
+        (goog.isDef(this.lineCap_) ?
+            this.lineCap_.toString() : '-') + ',' +
+        (!goog.isNull(this.lineDash_) ?
+            this.lineDash_.toString() : '-') + ',' +
+        (goog.isDef(this.lineJoin_) ?
+            this.lineJoin_ : '-') + ',' +
+        (goog.isDef(this.miterLimit_) ?
+            this.miterLimit_.toString() : '-') + ',' +
+        (goog.isDef(this.width_) ?
+            this.width_.toString() : '-');
+
+    var md5 = new goog.crypt.Md5();
+    md5.update(raw);
+    this.checksum_ = goog.crypt.byteArrayToString(md5.digest());
   }
+
+  return this.checksum_;
 };
 
+goog.provide('ol.render.canvas');
+
 
 /**
- * Read all features from a KML source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * @typedef {{fillStyle: string}}
  */
-ol.format.KML.prototype.readFeatures;
+ol.render.canvas.FillState;
 
 
 /**
- * @inheritDoc
+ * @typedef {{lineCap: string,
+ *            lineDash: Array.<number>,
+ *            lineJoin: string,
+ *            lineWidth: number,
+ *            miterLimit: number,
+ *            strokeStyle: string}}
  */
-ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
-    return [];
-  }
-  var features;
-  var localName = ol.xml.getLocalName(node);
-  if (localName == 'Document' || localName == 'Folder') {
-    features = this.readDocumentOrFolder_(
-        node, [this.getReadOptions(node, opt_options)]);
-    if (goog.isDef(features)) {
-      return features;
-    } else {
-      return [];
-    }
-  } else if (localName == 'Placemark') {
-    var feature = this.readPlacemark_(
-        node, [this.getReadOptions(node, opt_options)]);
-    if (goog.isDef(feature)) {
-      return [feature];
-    } else {
-      return [];
-    }
-  } else if (localName == 'kml') {
-    features = [];
-    var n;
-    for (n = node.firstElementChild; !goog.isNull(n);
-         n = n.nextElementSibling) {
-      var fs = this.readFeaturesFromNode(n, opt_options);
-      if (goog.isDef(fs)) {
-        goog.array.extend(features, fs);
-      }
-    }
-    return features;
-  } else {
-    return [];
-  }
-};
+ol.render.canvas.StrokeState;
 
 
 /**
- * @param {Document|Node|string} source Souce.
- * @return {string|undefined} Name.
- * @api stable
+ * @typedef {{font: string,
+ *            textAlign: string,
+ *            textBaseline: string}}
  */
-ol.format.KML.prototype.readName = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readNameFromDocument(/** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readNameFromNode(/** @type {Node} */ (source));
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readNameFromDocument(doc);
-  } else {
-    goog.asserts.fail();
-    return undefined;
-  }
-};
+ol.render.canvas.TextState;
 
 
 /**
- * @param {Document} doc Document.
- * @return {string|undefined} Name.
+ * @const
+ * @type {string}
  */
-ol.format.KML.prototype.readNameFromDocument = function(doc) {
-  var n;
-  for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
-    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
-      var name = this.readNameFromNode(n);
-      if (goog.isDef(name)) {
-        return name;
-      }
-    }
-  }
-  return undefined;
-};
+ol.render.canvas.defaultFont = '10px sans-serif';
 
 
 /**
- * @param {Node} node Node.
- * @return {string|undefined} Name.
+ * @const
+ * @type {ol.Color}
  */
-ol.format.KML.prototype.readNameFromNode = function(node) {
-  var n;
-  for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) {
-    if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
-        n.localName == 'name') {
-      return ol.format.XSD.readString(n);
-    }
-  }
-  for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) {
-    var localName = ol.xml.getLocalName(n);
-    if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
-        (localName == 'Document' ||
-         localName == 'Folder' ||
-         localName == 'Placemark' ||
-         localName == 'kml')) {
-      var name = this.readNameFromNode(n);
-      if (goog.isDef(name)) {
-        return name;
-      }
-    }
-  }
-  return undefined;
-};
+ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
 
 
 /**
- * Read the projection from a KML source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * @const
+ * @type {string}
  */
-ol.format.KML.prototype.readProjection;
+ol.render.canvas.defaultLineCap = 'round';
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Array.<number>}
  */
-ol.format.KML.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
-};
+ol.render.canvas.defaultLineDash = [];
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {string}
  */
-ol.format.KML.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
-};
+ol.render.canvas.defaultLineJoin = 'round';
 
 
 /**
- * @param {Node} node Node to append a TextNode with the color to.
- * @param {ol.Color|string} color Color.
- * @private
+ * @const
+ * @type {number}
  */
-ol.format.KML.writeColorTextNode_ = function(node, color) {
-  var rgba = ol.color.asArray(color);
-  var opacity = (rgba.length == 4) ? rgba[3] : 1;
-  var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]];
-  var i;
-  for (i = 0; i < 4; ++i) {
-    var hex = parseInt(abgr[i], 10).toString(16);
-    abgr[i] = (hex.length == 1) ? '0' + hex : hex;
-  }
-  ol.format.XSD.writeStringTextNode(node, abgr.join(''));
-};
+ol.render.canvas.defaultMiterLimit = 10;
 
 
 /**
- * @param {Node} node Node to append a TextNode with the coordinates to.
- * @param {Array.<number>} coordinates Coordinates.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @const
+ * @type {ol.Color}
  */
-ol.format.KML.writeCoordinatesTextNode_ =
-    function(node, coordinates, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-
-  var layout = goog.object.get(context, 'layout');
-  var stride = goog.object.get(context, 'stride');
-
-  var dimension;
-  if (layout == ol.geom.GeometryLayout.XY ||
-      layout == ol.geom.GeometryLayout.XYM) {
-    dimension = 2;
-  } else if (layout == ol.geom.GeometryLayout.XYZ ||
-      layout == ol.geom.GeometryLayout.XYZM) {
-    dimension = 3;
-  } else {
-    goog.asserts.fail();
-  }
-
-  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);
-};
+ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<ol.Feature>} features Features.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @const
+ * @type {string}
  */
-ol.format.KML.writeDocument_ = function(node, features, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_,
-      ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack);
-};
+ol.render.canvas.defaultTextAlign = 'center';
 
 
 /**
- * @param {Node} node Node.
- * @param {Object} icon Icon object.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @const
+ * @type {string}
  */
-ol.format.KML.writeIcon_ = function(node, icon, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(icon, orderedKeys);
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
-  orderedKeys =
-      ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]];
-  values = ol.xml.makeSequence(icon, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_,
-      ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys);
-};
+ol.render.canvas.defaultTextBaseline = 'middle';
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Icon} style Icon style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @const
+ * @type {number}
  */
-ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  var properties = {};
-  var src = style.getSrc();
-  var size = style.getSize();
-  var iconImageSize = style.getImageSize();
-  var iconProperties = {
-    'href': src
-  };
+ol.render.canvas.defaultLineWidth = 1;
 
-  if (!goog.isNull(size)) {
-    goog.object.set(iconProperties, 'w', size[0]);
-    goog.object.set(iconProperties, 'h', size[1]);
-    var anchor = style.getAnchor(); // top-left
-    var origin = style.getOrigin(); // top-left
+goog.provide('ol.style.Fill');
 
-    if (!goog.isNull(origin) && !goog.isNull(iconImageSize) &&
-        origin[0] !== 0 && origin[1] !== size[1]) {
-      goog.object.set(iconProperties, 'x', origin[0]);
-      goog.object.set(iconProperties, 'y',
-          iconImageSize[1] - (origin[1] + size[1]));
-    }
+goog.require('ol.color');
+goog.require('ol.structs.IHasChecksum');
 
-    if (!goog.isNull(anchor) &&
-        anchor[0] !== 0 && anchor[1] !== size[1]) {
-      var /** @type {ol.format.KMLVec2_} */ hotSpot = {
-        x: anchor[0],
-        xunits: ol.style.IconAnchorUnits.PIXELS,
-        y: size[1] - anchor[1],
-        yunits: ol.style.IconAnchorUnits.PIXELS
-      };
-      goog.object.set(properties, 'hotSpot', hotSpot);
-    }
-  }
 
-  goog.object.set(properties, 'Icon', iconProperties);
 
-  var scale = style.getScale();
-  if (scale !== 1) {
-    goog.object.set(properties, 'scale', scale);
-  }
+/**
+ * @classdesc
+ * Set fill style for vector features.
+ *
+ * @constructor
+ * @param {olx.style.FillOptions=} opt_options Options.
+ * @implements {ol.structs.IHasChecksum}
+ * @api
+ */
+ol.style.Fill = function(opt_options) {
 
-  var rotation = style.getRotation();
-  if (rotation !== 0) {
-    goog.object.set(properties, 'heading', rotation); // 0-360
-  }
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  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);
+  /**
+   * @private
+   * @type {ol.Color|string}
+   */
+  this.color_ = goog.isDef(options.color) ? options.color : null;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.checksum_ = undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Text} style style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {ol.Color|string} Color.
+ * @api
  */
-ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  var properties = {};
-  var fill = style.getFill();
-  if (!goog.isNull(fill)) {
-    goog.object.set(properties, 'color', fill.getColor());
-  }
-  var scale = style.getScale();
-  if (goog.isDef(scale) && scale !== 1) {
-    goog.object.set(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);
+ol.style.Fill.prototype.getColor = function() {
+  return this.color_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Stroke} style style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Set the color.
+ *
+ * @param {ol.Color|string} color Color.
+ * @api
  */
-ol.format.KML.writeLineStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  var properties = {
-    'color': style.getColor(),
-    'width': style.getWidth()
-  };
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
+ol.style.Fill.prototype.setColor = function(color) {
+  this.color_ = color;
+  this.checksum_ = undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @inheritDoc
  */
-ol.format.KML.writeMultiGeometry_ =
-    function(node, geometry, objectStack) {
-  goog.asserts.assert(
-      (geometry instanceof ol.geom.MultiPoint) ||
-      (geometry instanceof ol.geom.MultiLineString) ||
-      (geometry instanceof ol.geom.MultiPolygon));
-  /** @type {ol.xml.NodeStackItem} */
-  var context = {node: node};
-  var type = geometry.getType();
-  /** @type {Array.<ol.geom.Geometry>} */
-  var geometries;
-  /** @type {function(*, Array.<*>, string=): (Node|undefined)} */
-  var factory;
-  if (type == ol.geom.GeometryType.MULTI_POINT) {
-    geometries =
-        (/** @type {ol.geom.MultiPoint} */ (geometry)).getPoints();
-    factory = ol.format.KML.POINT_NODE_FACTORY_;
-  } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) {
-    geometries =
-        (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings();
-    factory = ol.format.KML.LINE_STRING_NODE_FACTORY_;
-  } else if (type == ol.geom.GeometryType.MULTI_POLYGON) {
-    geometries =
-        (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons();
-    factory = ol.format.KML.POLYGON_NODE_FACTORY_;
-  } else {
-    goog.asserts.fail();
+ol.style.Fill.prototype.getChecksum = function() {
+  if (!goog.isDef(this.checksum_)) {
+    this.checksum_ = 'f' + (!goog.isNull(this.color_) ?
+        ol.color.asString(this.color_) : '-');
   }
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory,
-      geometries, objectStack);
+
+  return this.checksum_;
 };
 
+goog.provide('ol.style.Circle');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('ol.color');
+goog.require('ol.has');
+goog.require('ol.render.canvas');
+goog.require('ol.structs.IHasChecksum');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Image');
+goog.require('ol.style.ImageState');
+goog.require('ol.style.Stroke');
 
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} linearRing Linear ring.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.BOUNDARY_IS_SERIALIZERS_,
-      ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack);
-};
 
 
 /**
- * FIXME currently we do serialize arbitrary/custom feature properties
- * (ExtendedData).
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @classdesc
+ * Set circle style for vector features.
+ *
+ * @constructor
+ * @param {olx.style.CircleOptions=} opt_options Options.
+ * @extends {ol.style.Image}
+ * @implements {ol.structs.IHasChecksum}
+ * @api
  */
-ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+ol.style.Circle = function(opt_options) {
 
-  // set id
-  if (goog.isDefAndNotNull(feature.getId())) {
-    node.setAttribute('id', feature.getId());
-  }
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  // serialize properties (properties unknown to KML are not serialized)
-  var properties = feature.getProperties();
-  var styleFunction = feature.getStyleFunction();
-  if (goog.isDef(styleFunction)) {
-    // FIXME the styles returned by the style function are supposed to be
-    // resolution-independent here
-    var styles = styleFunction.call(feature, 0);
-    if (!goog.isNull(styles) && styles.length > 0) {
-      goog.object.set(properties, 'Style', styles[0]);
-      var textStyle = styles[0].getText();
-      if (!goog.isNull(textStyle)) {
-        goog.object.set(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);
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.checksums_ = null;
 
-  // serialize geometry
-  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
-  var geometry = feature.getGeometry();
-  if (goog.isDefAndNotNull(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);
-};
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = null;
 
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.hitDetectionCanvas_ = null;
+
+  /**
+   * @private
+   * @type {ol.style.Fill}
+   */
+  this.fill_ = goog.isDef(options.fill) ? options.fill : null;
+
+  /**
+   * @private
+   * @type {ol.style.Stroke}
+   */
+  this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.radius_ = options.radius;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.origin_ = [0, 0];
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.hitDetectionOrigin_ = [0, 0];
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.anchor_ = null;
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.size_ = null;
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.imageSize_ = null;
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.hitDetectionImageSize_ = null;
+
+  this.render_(options.atlasManager);
+
+  /**
+   * @type {boolean}
+   */
+  var snapToPixel = goog.isDef(options.snapToPixel) ?
+      options.snapToPixel : true;
+
+  goog.base(this, {
+    opacity: 1,
+    rotateWithView: false,
+    rotation: 0,
+    scale: 1,
+    snapToPixel: snapToPixel
+  });
 
-/**
- * @param {Node} node Node.
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) {
-  goog.asserts.assert(
-      (geometry instanceof ol.geom.Point) ||
-      (geometry instanceof ol.geom.LineString) ||
-      (geometry instanceof ol.geom.LinearRing));
-  var flatCoordinates = geometry.getFlatCoordinates();
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  goog.object.set(context, 'layout', geometry.getLayout());
-  goog.object.set(context, 'stride', geometry.getStride());
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_,
-      ol.format.KML.COORDINATES_NODE_FACTORY_,
-      [flatCoordinates], objectStack);
 };
+goog.inherits(ol.style.Circle, ol.style.Image);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @inheritDoc
+ * @api
  */
-ol.format.KML.writePolygon_ = function(node, polygon, objectStack) {
-  goog.asserts.assertInstanceof(polygon, ol.geom.Polygon);
-  var linearRings = polygon.getLinearRings();
-  goog.asserts.assert(linearRings.length > 0);
-  var outerRing = linearRings.shift();
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  // inner rings
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.POLYGON_SERIALIZERS_,
-      ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_,
-      linearRings, objectStack);
-  // outer ring
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.POLYGON_SERIALIZERS_,
-      ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_,
-      [outerRing], objectStack);
+ol.style.Circle.prototype.getAnchor = function() {
+  return this.anchor_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Fill} style Style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {ol.style.Fill} Fill style.
+ * @api
  */
-ol.format.KML.writePolyStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_,
-      ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack);
+ol.style.Circle.prototype.getFill = function() {
+  return this.fill_;
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the scale to.
- * @param {number|undefined} scale Scale.
- * @private
+ * @inheritDoc
  */
-ol.format.KML.writeScaleTextNode_ = function(node, scale) {
-  ol.format.XSD.writeDecimalTextNode(node, scale * scale);
+ol.style.Circle.prototype.getHitDetectionImage = function(pixelRatio) {
+  return this.hitDetectionCanvas_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.style.Style} style Style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {HTMLCanvasElement} Canvas element.
+ * @api
  */
-ol.format.KML.writeStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  var properties = {};
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  var imageStyle = style.getImage();
-  var textStyle = style.getText();
-  if (!goog.isNull(imageStyle)) {
-    goog.object.set(properties, 'IconStyle', imageStyle);
-  }
-  if (!goog.isNull(textStyle)) {
-    goog.object.set(properties, 'LabelStyle', textStyle);
-  }
-  if (!goog.isNull(strokeStyle)) {
-    goog.object.set(properties, 'LineStyle', strokeStyle);
-  }
-  if (!goog.isNull(fillStyle)) {
-    goog.object.set(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);
+ol.style.Circle.prototype.getImage = function(pixelRatio) {
+  return this.canvas_;
 };
 
 
 /**
- * @param {Node} node Node to append a TextNode with the Vec2 to.
- * @param {ol.format.KMLVec2_} vec2 Vec2.
- * @private
+ * @inheritDoc
  */
-ol.format.KML.writeVec2_ = function(node, vec2) {
-  node.setAttribute('x', vec2.x);
-  node.setAttribute('y', vec2.y);
-  node.setAttribute('xunits', vec2.xunits);
-  node.setAttribute('yunits', vec2.yunits);
+ol.style.Circle.prototype.getImageState = function() {
+  return ol.style.ImageState.LOADED;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'Document', 'Placemark'
-    ]);
+ol.style.Circle.prototype.getImageSize = function() {
+  return this.imageSize_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-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_)
-    });
+ol.style.Circle.prototype.getHitDetectionImageSize = function() {
+  return this.hitDetectionImageSize_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
+ * @api
  */
-ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
-    });
+ol.style.Circle.prototype.getOrigin = function() {
+  return this.origin_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, string>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = {
-  'Point': 'Point',
-  'LineString': 'LineString',
-  'LinearRing': 'LinearRing',
-  'Polygon': 'Polygon',
-  'MultiPoint': 'MultiGeometry',
-  'MultiLineString': 'MultiGeometry',
-  'MultiPolygon': 'MultiGeometry'
+ol.style.Circle.prototype.getHitDetectionOrigin = function() {
+  return this.hitDetectionOrigin_;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @return {number} Radius.
+ * @api
  */
-ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'href'
-    ],
-    ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, [
-          'x', 'y', 'w', 'h'
-    ]));
+ol.style.Circle.prototype.getRadius = function() {
+  return this.radius_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
+ * @api
  */
-ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-    }, ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
-        }));
+ol.style.Circle.prototype.getSize = function() {
+  return this.size_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
  */
-ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'scale', 'heading', 'Icon', 'hotSpot'
-    ]);
+ol.style.Circle.prototype.getStroke = function() {
+  return this.stroke_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_),
-      'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_),
-      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
-    });
+ol.style.Circle.prototype.listenImageChange = goog.nullFunction;
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'color', 'scale'
-    ]);
+ol.style.Circle.prototype.load = goog.nullFunction;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
+ * @inheritDoc
  */
-ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_),
-      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
-    });
+ol.style.Circle.prototype.unlistenImageChange = goog.nullFunction;
 
 
 /**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
+ * @typedef {{strokeStyle: (string|undefined), strokeWidth: number,
+ *   size: number, lineDash: Array.<number>}}
  */
-ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'color', 'width'
-    ]);
+ol.style.Circle.RenderOptions;
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
  * @private
+ * @param {ol.style.AtlasManager|undefined} atlasManager
  */
-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)
-    });
+ol.style.Circle.prototype.render_ = function(atlasManager) {
+  var imageSize;
+  var lineDash = null;
+  var strokeStyle;
+  var strokeWidth = 0;
 
+  if (!goog.isNull(this.stroke_)) {
+    strokeStyle = ol.color.asString(this.stroke_.getColor());
+    strokeWidth = this.stroke_.getWidth();
+    if (!goog.isDef(strokeWidth)) {
+      strokeWidth = ol.render.canvas.defaultLineWidth;
+    }
+    lineDash = this.stroke_.getLineDash();
+    if (!ol.has.CANVAS_LINE_DASH) {
+      lineDash = null;
+    }
+  }
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LinearRing': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_)
-    });
 
+  var size = 2 * (this.radius_ + strokeWidth) + 1;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LineString': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Point': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_)
-    });
+  /** @type {ol.style.Circle.RenderOptions} */
+  var renderOptions = {
+    strokeStyle: strokeStyle,
+    strokeWidth: strokeWidth,
+    size: size,
+    lineDash: lineDash
+  };
 
+  if (!goog.isDef(atlasManager)) {
+    // no atlas manager is used, create a new canvas
+    this.canvas_ = /** @type {HTMLCanvasElement} */
+        (goog.dom.createElement(goog.dom.TagName.CANVAS));
+    this.canvas_.height = size;
+    this.canvas_.width = size;
 
-/**
- * @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'
-    ]);
+    // canvas.width and height are rounded to the closest integer
+    size = this.canvas_.width;
+    imageSize = size;
 
+    // draw the circle on the canvas
+    var context = /** @type {CanvasRenderingContext2D} */
+        (this.canvas_.getContext('2d'));
+    this.draw_(renderOptions, context, 0, 0);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'MultiGeometry': ol.xml.makeChildAppender(
-          ol.format.KML.writeMultiGeometry_),
-      'LineString': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'LinearRing': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Point': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_),
-      'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_),
-      'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'description': ol.xml.makeChildAppender(
-          ol.format.XSD.writeStringTextNode),
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode),
-      'phoneNumber': ol.xml.makeChildAppender(
-          ol.format.XSD.writeStringTextNode),
-      'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'visibility': ol.xml.makeChildAppender(
-          ol.format.XSD.writeBooleanTextNode)
-    });
+    this.createHitDetectionCanvas_(renderOptions);
+  } else {
+    // an atlas manager is used, add the symbol to an atlas
+    size = Math.round(size);
+
+    var hasCustomHitDetectionImage = goog.isNull(this.fill_);
+    var renderHitDetectionCallback;
+    if (hasCustomHitDetectionImage) {
+      // render the hit-detection image into a separate atlas image
+      renderHitDetectionCallback =
+          goog.bind(this.drawHitDetectionCanvas_, this, renderOptions);
+    }
+
+    var id = this.getChecksum();
+    var info = atlasManager.add(
+        id, size, size, goog.bind(this.draw_, this, renderOptions),
+        renderHitDetectionCallback);
+    goog.asserts.assert(info !== null, '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.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
+      this.hitDetectionImageSize_ =
+          [info.hitImage.width, info.hitImage.height];
+    } else {
+      this.hitDetectionCanvas_ = this.canvas_;
+      this.hitDetectionOrigin_ = this.origin_;
+      this.hitDetectionImageSize_ = [imageSize, imageSize];
+    }
+  }
+
+  this.anchor_ = [size / 2, size / 2];
+  this.size_ = [size, size];
+  this.imageSize_ = [imageSize, imageSize];
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
  * @private
+ * @param {ol.style.Circle.RenderOptions} renderOptions
+ * @param {CanvasRenderingContext2D} context
+ * @param {number} x The origin for the symbol (x).
+ * @param {number} y The origin for the symbol (y).
  */
-ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'coordinates': ol.xml.makeChildAppender(
-          ol.format.KML.writeCoordinatesTextNode_)
-    });
+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);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'outerBoundaryIs': ol.xml.makeChildAppender(
-          ol.format.KML.writeBoundaryIs_),
-      'innerBoundaryIs': ol.xml.makeChildAppender(
-          ol.format.KML.writeBoundaryIs_)
-    });
+  context.beginPath();
+  context.arc(
+      renderOptions.size / 2, renderOptions.size / 2,
+      this.radius_, 0, 2 * Math.PI, true);
+
+  if (!goog.isNull(this.fill_)) {
+    context.fillStyle = ol.color.asString(this.fill_.getColor());
+    context.fill();
+  }
+  if (!goog.isNull(this.stroke_)) {
+    context.strokeStyle = renderOptions.strokeStyle;
+    context.lineWidth = renderOptions.strokeWidth;
+    if (!goog.isNull(renderOptions.lineDash)) {
+      context.setLineDash(renderOptions.lineDash);
+    }
+    context.stroke();
+  }
+  context.closePath();
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
  * @private
+ * @param {ol.style.Circle.RenderOptions} renderOptions
  */
-ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_)
-    });
+ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) {
+  this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
+  if (!goog.isNull(this.fill_)) {
+    this.hitDetectionCanvas_ = this.canvas_;
+    return;
+  }
 
+  // if no fill style is set, create an extra hit-detection image with a
+  // default fill style
+  this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
+      (goog.dom.createElement(goog.dom.TagName.CANVAS));
+  var canvas = this.hitDetectionCanvas_;
 
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle'
-    ]);
+  canvas.height = renderOptions.size;
+  canvas.width = renderOptions.size;
+
+  var context = /** @type {CanvasRenderingContext2D} */
+      (canvas.getContext('2d'));
+  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
  * @private
+ * @param {ol.style.Circle.RenderOptions} renderOptions
+ * @param {CanvasRenderingContext2D} context
+ * @param {number} x The origin for the symbol (x).
+ * @param {number} y The origin for the symbol (y).
  */
-ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_),
-      'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_),
-      'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_),
-      'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_)
-    });
+ol.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);
 
-/**
- * @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);
+  context.beginPath();
+  context.arc(
+      renderOptions.size / 2, renderOptions.size / 2,
+      this.radius_, 0, 2 * Math.PI, true);
+
+  context.fillStyle = ol.render.canvas.defaultFillStyle;
+  context.fill();
+  if (!goog.isNull(this.stroke_)) {
+    context.strokeStyle = renderOptions.strokeStyle;
+    context.lineWidth = renderOptions.strokeWidth;
+    if (!goog.isNull(renderOptions.lineDash)) {
+      context.setLineDash(renderOptions.lineDash);
+    }
+    context.stroke();
+  }
+  context.closePath();
 };
 
 
 /**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
+ * @inheritDoc
  */
-ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack,
-    opt_nodeName) {
-  goog.asserts.assertInstanceof(value, ol.Feature);
-  var parentNode = objectStack[objectStack.length - 1].node;
-  goog.asserts.assert(ol.xml.isNode(parentNode));
-  return ol.xml.createElementNS(parentNode.namespaceURI, 'Placemark');
+ol.style.Circle.prototype.getChecksum = function() {
+  var strokeChecksum = !goog.isNull(this.stroke_) ?
+      this.stroke_.getChecksum() : '-';
+  var fillChecksum = !goog.isNull(this.fill_) ?
+      this.fill_.getChecksum() : '-';
+
+  var recalculate = goog.isNull(this.checksums_) ||
+      (strokeChecksum != this.checksums_[1] ||
+      fillChecksum != this.checksums_[2] ||
+      this.radius_ != this.checksums_[3]);
+
+  if (recalculate) {
+    var checksum = 'c' + strokeChecksum + fillChecksum +
+        (goog.isDef(this.radius_) ? this.radius_.toString() : '-');
+    this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_];
+  }
+
+  return this.checksums_[0];
 };
 
+goog.provide('ol.style.Style');
+goog.provide('ol.style.defaultGeometryFunction');
 
-/**
- * @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 (goog.isDefAndNotNull(value)) {
-    goog.asserts.assertInstanceof(value, ol.geom.Geometry);
-    var parentNode = objectStack[objectStack.length - 1].node;
-    goog.asserts.assert(ol.xml.isNode(parentNode));
-    return ol.xml.createElementNS(parentNode.namespaceURI,
-        ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[value.getType()]);
-  }
-};
-
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.style.Circle');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Image');
+goog.require('ol.style.Stroke');
 
-/**
- * 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
+ * @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, layer or FeatureOverlay that uses the style is re-rendered.
+ *
+ * @constructor
+ * @param {olx.style.StyleOptions=} opt_options Style options.
+ * @api
  */
-ol.format.KML.COORDINATES_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('coordinates');
+ol.style.Style = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * A factory for creating innerBoundaryIs nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('innerBoundaryIs');
+  /**
+   * @private
+   * @type {string|ol.geom.Geometry|ol.style.GeometryFunction}
+   */
+  this.geometry_ = null;
 
+  /**
+   * @private
+   * @type {!ol.style.GeometryFunction}
+   */
+  this.geometryFunction_ = ol.style.defaultGeometryFunction;
 
-/**
- * A factory for creating Point nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.POINT_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('Point');
+  if (goog.isDef(options.geometry)) {
+    this.setGeometry(options.geometry);
+  }
 
+  /**
+   * @private
+   * @type {ol.style.Fill}
+   */
+  this.fill_ = goog.isDef(options.fill) ? options.fill : null;
 
-/**
- * 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');
+  /**
+   * @private
+   * @type {ol.style.Image}
+   */
+  this.image_ = goog.isDef(options.image) ? options.image : null;
 
+  /**
+   * @private
+   * @type {ol.style.Stroke}
+   */
+  this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
 
-/**
- * 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');
+  /**
+   * @private
+   * @type {ol.style.Text}
+   */
+  this.text_ = goog.isDef(options.text) ? options.text : null;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.zIndex_ = options.zIndex;
 
-/**
- * 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
+ * @return {string|ol.geom.Geometry|ol.style.GeometryFunction}
+ * Feature property or geometry or function that returns the geometry that will
+ * be rendered with this style.
+ * @api
  */
-ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('outerBoundaryIs');
+ol.style.Style.prototype.getGeometry = function() {
+  return this.geometry_;
+};
 
 
 /**
- * Encode an array of features in the KML format.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Result.
- * @api stable
+ * @return {!ol.style.GeometryFunction} Function that is called with a feature
+ * and returns the geometry to render instead of the feature's geometry.
+ * @api
  */
-ol.format.KML.prototype.writeFeatures;
+ol.style.Style.prototype.getGeometryFunction = function() {
+  return this.geometryFunction_;
+};
 
 
 /**
- * @inheritDoc
+ * @return {ol.style.Fill} Fill style.
+ * @api
  */
-ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) {
-  var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml');
-  var xmlnsUri = 'http://www.w3.org/2000/xmlns/';
-  var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance';
-  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx',
-      ol.format.KML.GX_NAMESPACE_URIS_[0]);
-  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri);
-  ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation',
-      ol.format.KML.SCHEMA_LOCATION_);
-
-  var /** @type {ol.xml.NodeStackItem} */ context = {node: kml};
-  var properties = {};
-  if (features.length > 1) {
-    goog.object.set(properties, 'Document', features);
-  } else if (features.length == 1) {
-    goog.object.set(properties, 'Placemark', features[0]);
-  }
-  var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys);
-  return kml;
+ol.style.Style.prototype.getFill = function() {
+  return this.fill_;
 };
 
-// FIXME add typedef for stack state objects
-goog.provide('ol.format.OSMXML');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.object');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
 
 /**
- * @classdesc
- * Feature format for reading data in the
- * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML).
- *
- * @constructor
- * @extends {ol.format.XMLFeature}
- * @api stable
+ * @return {ol.style.Image} Image style.
+ * @api
  */
-ol.format.OSMXML = function() {
-  goog.base(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+ol.style.Style.prototype.getImage = function() {
+  return this.image_;
 };
-goog.inherits(ol.format.OSMXML, ol.format.XMLFeature);
 
 
 /**
- * @const
- * @type {Array.<string>}
- * @private
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
  */
-ol.format.OSMXML.EXTENSIONS_ = ['.osm'];
+ol.style.Style.prototype.getStroke = function() {
+  return this.stroke_;
+};
 
 
 /**
- * @inheritDoc
+ * @return {ol.style.Text} Text style.
+ * @api
  */
-ol.format.OSMXML.prototype.getExtensions = function() {
-  return ol.format.OSMXML.EXTENSIONS_;
+ol.style.Style.prototype.getText = function() {
+  return this.text_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {number|undefined} ZIndex.
+ * @api
  */
-ol.format.OSMXML.readNode_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'node');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var id = node.getAttribute('id');
-  var coordinates = /** @type {Array.<number>} */ ([
-    parseFloat(node.getAttribute('lon')),
-    parseFloat(node.getAttribute('lat'))
-  ]);
-  goog.object.set(state.nodes, id, coordinates);
-
-  var values = ol.xml.pushParseAndPop({
-    tags: {}
-  }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack);
-  if (!goog.object.isEmpty(values.tags)) {
-    var geometry = new ol.geom.Point(coordinates);
-    ol.format.Feature.transformWithOptions(geometry, false, options);
-    var feature = new ol.Feature(geometry);
-    feature.setId(id);
-    feature.setProperties(values.tags);
-    state.features.push(feature);
-  }
+ol.style.Style.prototype.getZIndex = function() {
+  return this.zIndex_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * Set a geometry that is rendered instead of the feature's geometry.
+ *
+ * @param {string|ol.geom.Geometry|ol.style.GeometryFunction} geometry
+ *     Feature property or geometry or function returning a geometry to render
+ *     for this style.
+ * @api
  */
-ol.format.OSMXML.readWay_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'way');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var id = node.getAttribute('id');
-  var values = ol.xml.pushParseAndPop({
-    ndrefs: [],
-    tags: {}
-  }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack);
-  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var flatCoordinates = /** @type {Array.<number>} */ ([]);
-  for (var i = 0, ii = values.ndrefs.length; i < ii; i++) {
-    var point = goog.object.get(state.nodes, values.ndrefs[i]);
-    goog.array.extend(flatCoordinates, point);
-  }
-  var geometry;
-  if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) {
-    // closed way
-    geometry = new ol.geom.Polygon(null);
-    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
-        [flatCoordinates.length]);
-  } else {
-    geometry = new ol.geom.LineString(null);
-    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+ol.style.Style.prototype.setGeometry = function(geometry) {
+  if (goog.isFunction(geometry)) {
+    this.geometryFunction_ = geometry;
+  } else if (goog.isString(geometry)) {
+    this.geometryFunction_ = function(feature) {
+      var result = feature.get(geometry);
+      if (goog.isDefAndNotNull(result)) {
+        goog.asserts.assertInstanceof(result, ol.geom.Geometry);
+      }
+      return result;
+    };
+  } else if (goog.isNull(geometry)) {
+    this.geometryFunction_ = ol.style.defaultGeometryFunction;
+  } else if (goog.isDef(geometry)) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.Geometry);
+    this.geometryFunction_ = function() {
+      return geometry;
+    };
   }
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setId(id);
-  feature.setProperties(values.tags);
-  state.features.push(feature);
+  this.geometry_ = geometry;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
+ * Set the zIndex.
+ *
+ * @param {number|undefined} zIndex ZIndex.
+ * @api
  */
-ol.format.OSMXML.readNd_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'nd');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  values.ndrefs.push(node.getAttribute('ref'));
+ol.style.Style.prototype.setZIndex = function(zIndex) {
+  this.zIndex_ = zIndex;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
+ * A function that takes an {@link ol.Feature} and a `{number}` representing
+ * the view's resolution. The function should return an array of
+ * {@link ol.style.Style}. This way e.g. a vector layer can be styled.
+ *
+ * @typedef {function(ol.Feature, number): Array.<ol.style.Style>}
+ * @api
  */
-ol.format.OSMXML.readTag_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'tag');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  goog.object.set(values.tags, node.getAttribute('k'), node.getAttribute('v'));
-};
+ol.style.StyleFunction;
 
 
 /**
- * @const
- * @private
- * @type {Array.<string>}
+ * Convert the provided object into a style function.  Functions passed through
+ * unchanged.  Arrays of ol.style.Style or single style objects wrapped in a
+ * new style function.
+ * @param {ol.style.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj
+ *     A style function, a single style, or an array of styles.
+ * @return {ol.style.StyleFunction} A style function.
  */
-ol.format.OSMXML.NAMESPACE_URIS_ = [
-  null
-];
+ol.style.createStyleFunction = function(obj) {
+  /**
+   * @type {ol.style.StyleFunction}
+   */
+  var styleFunction;
+
+  if (goog.isFunction(obj)) {
+    styleFunction = /** @type {ol.style.StyleFunction} */ (obj);
+  } else {
+    /**
+     * @type {Array.<ol.style.Style>}
+     */
+    var styles;
+    if (goog.isArray(obj)) {
+      styles = obj;
+    } else {
+      goog.asserts.assertInstanceof(obj, ol.style.Style);
+      styles = [obj];
+    }
+    styleFunction = goog.functions.constant(styles);
+  }
+  return styleFunction;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @param {ol.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @return {Array.<ol.style.Style>} Style.
  */
-ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'nd': ol.format.OSMXML.readNd_,
-      'tag': ol.format.OSMXML.readTag_
-    });
+ol.style.defaultStyleFunction = function(feature, resolution) {
+  var fill = new ol.style.Fill({
+    color: 'rgba(255,255,255,0.4)'
+  });
+  var stroke = new ol.style.Stroke({
+    color: '#3399CC',
+    width: 1.25
+  });
+  var styles = [
+    new ol.style.Style({
+      image: new ol.style.Circle({
+        fill: fill,
+        stroke: stroke,
+        radius: 5
+      }),
+      fill: fill,
+      stroke: stroke
+    })
+  ];
 
+  // Now that we've run it the first time, replace the function with
+  // a constant version. 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.)
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OSMXML.PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'node': ol.format.OSMXML.readNode_,
-      'way': ol.format.OSMXML.readWay_
-    });
+  /**
+   * @param {ol.Feature} feature Feature.
+   * @param {number} resolution Resolution.
+   * @return {Array.<ol.style.Style>} Style.
+   */
+  ol.style.defaultStyleFunction = function(feature, resolution) {
+    return styles;
+  };
+
+  return styles;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Default styles for editing features.
+ * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles
  */
-ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'tag': ol.format.OSMXML.readTag_
-    });
+ol.style.createDefaultEditingStyles = function() {
+  /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */
+  var styles = {};
+  var white = [255, 255, 255, 1];
+  var blue = [0, 153, 255, 1];
+  var width = 3;
+  styles[ol.geom.GeometryType.POLYGON] = [
+    new ol.style.Style({
+      fill: new ol.style.Fill({
+        color: [255, 255, 255, 0.5]
+      })
+    })
+  ];
+  styles[ol.geom.GeometryType.MULTI_POLYGON] =
+      styles[ol.geom.GeometryType.POLYGON];
 
+  styles[ol.geom.GeometryType.LINE_STRING] = [
+    new ol.style.Style({
+      stroke: new ol.style.Stroke({
+        color: white,
+        width: width + 2
+      })
+    }),
+    new ol.style.Style({
+      stroke: new ol.style.Stroke({
+        color: blue,
+        width: width
+      })
+    })
+  ];
+  styles[ol.geom.GeometryType.MULTI_LINE_STRING] =
+      styles[ol.geom.GeometryType.LINE_STRING];
 
-/**
- * Read all features from an OSM source.
- *
- * @function
- * @param {ArrayBuffer|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;
+  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.POINT]
+      );
 
-/**
- * @inheritDoc
- */
-ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.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 (goog.isDef(state.features)) {
-      return state.features;
-    }
-  }
-  return [];
+  return styles;
 };
 
 
 /**
- * Read the projection from an OSM source.
+ * A function that takes an {@link ol.Feature} as argument and returns an
+ * {@link ol.geom.Geometry} that will be rendered and styled for the feature.
  *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * @typedef {function(ol.Feature): (ol.geom.Geometry|undefined)}
+ * @api
  */
-ol.format.OSMXML.prototype.readProjection;
+ol.style.GeometryFunction;
 
 
 /**
- * @inheritDoc
+ * Function that is called with a feature and returns its default geometry.
+ * @param {ol.Feature} feature Feature to get the geometry for.
+ * @return {ol.geom.Geometry|undefined} Geometry to render.
  */
-ol.format.OSMXML.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
+ol.style.defaultGeometryFunction = function(feature) {
+  goog.asserts.assert(!goog.isNull(feature));
+  return feature.getGeometry();
 };
 
+goog.provide('ol.interaction.DragZoom');
 
-/**
- * @inheritDoc
- */
-ol.format.OSMXML.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
-};
+goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.events.condition');
+goog.require('ol.extent');
+goog.require('ol.interaction.DragBox');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Style');
 
-goog.provide('ol.format.XLink');
 
 
 /**
- * @const
- * @type {string}
+ * @classdesc
+ * Allows the user to zoom the map by clicking and dragging on the map,
+ * normally combined with an {@link ol.events.condition} that limits
+ * it to when a key, shift by default, is held down.
+ *
+ * @constructor
+ * @extends {ol.interaction.DragBox}
+ * @param {olx.interaction.DragZoomOptions=} opt_options Options.
+ * @api stable
  */
-ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
+ol.interaction.DragZoom = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  var condition = goog.isDef(options.condition) ?
+      options.condition : ol.events.condition.shiftKeyOnly;
+
+  /**
+   * @private
+   * @type {ol.style.Style}
+   */
+  var style = goog.isDef(options.style) ?
+      options.style : new ol.style.Style({
+        stroke: new ol.style.Stroke({
+          color: [0, 0, 255, 1]
+        })
+      });
+
+  goog.base(this, {
+    condition: condition,
+    style: style
+  });
+
+};
+goog.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
 
 
 /**
- * @param {Node} node Node.
- * @return {boolean|undefined} Boolean.
+ * @inheritDoc
  */
-ol.format.XLink.readHref = function(node) {
-  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
+ol.interaction.DragZoom.prototype.onBoxEnd = function() {
+  var map = this.getMap();
+  var view = map.getView();
+  goog.asserts.assert(!goog.isNull(view));
+  var extent = this.getGeometry().getExtent();
+  var center = ol.extent.getCenter(extent);
+  var size = map.getSize();
+  goog.asserts.assert(goog.isDef(size));
+  ol.interaction.Interaction.zoom(map, view,
+      view.getResolutionForExtent(extent, size),
+      center, ol.DRAGZOOM_ANIMATION_DURATION);
 };
 
-goog.provide('ol.format.XML');
+goog.provide('ol.interaction.KeyboardPan');
 
 goog.require('goog.asserts');
-goog.require('ol.xml');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.events.KeyHandler.EventType');
+goog.require('goog.functions');
+goog.require('ol');
+goog.require('ol.coordinate');
+goog.require('ol.events.ConditionType');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Interaction');
 
 
 
 /**
  * @classdesc
- * Generic format for reading non-feature XML data
+ * 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.format.XML = function() {
+ol.interaction.KeyboardPan = function(opt_options) {
+
+  goog.base(this, {
+    handleEvent: ol.interaction.KeyboardPan.handleEvent
+  });
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {ol.events.ConditionType}
+   */
+  this.condition_ = goog.isDef(options.condition) ? options.condition :
+      goog.functions.and(ol.events.condition.noModifierKeys,
+          ol.events.condition.targetNotEditable);
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.pixelDelta_ = goog.isDef(options.pixelDelta) ? options.pixelDelta : 128;
+
 };
+goog.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction);
 
 
 /**
- * @param {Document|Node|string} source Source.
- * @return {Object}
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.KeyboardPan}
+ * @api
  */
-ol.format.XML.prototype.read = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFromDocument(/** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readFromNode(/** @type {Node} */ (source));
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readFromDocument(doc);
-  } else {
-    goog.asserts.fail();
-    return null;
+ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) {
+  var stopEvent = false;
+  if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) {
+    var keyEvent = /** @type {goog.events.KeyEvent} */
+        (mapBrowserEvent.browserEvent);
+    var keyCode = keyEvent.keyCode;
+    if (this.condition_(mapBrowserEvent) &&
+        (keyCode == goog.events.KeyCodes.DOWN ||
+        keyCode == goog.events.KeyCodes.LEFT ||
+        keyCode == goog.events.KeyCodes.RIGHT ||
+        keyCode == goog.events.KeyCodes.UP)) {
+      var map = mapBrowserEvent.map;
+      var view = map.getView();
+      goog.asserts.assert(!goog.isNull(view));
+      var viewState = view.getState();
+      var mapUnitsDelta = viewState.resolution * this.pixelDelta_;
+      var deltaX = 0, deltaY = 0;
+      if (keyCode == goog.events.KeyCodes.DOWN) {
+        deltaY = -mapUnitsDelta;
+      } else if (keyCode == goog.events.KeyCodes.LEFT) {
+        deltaX = -mapUnitsDelta;
+      } else if (keyCode == goog.events.KeyCodes.RIGHT) {
+        deltaX = mapUnitsDelta;
+      } else {
+        deltaY = mapUnitsDelta;
+      }
+      var delta = [deltaX, deltaY];
+      ol.coordinate.rotate(delta, viewState.rotation);
+      ol.interaction.Interaction.pan(
+          map, view, delta, ol.KEYBOARD_PAN_DURATION);
+      mapBrowserEvent.preventDefault();
+      stopEvent = true;
+    }
   }
+  return !stopEvent;
 };
 
+goog.provide('ol.interaction.KeyboardZoom');
+
+goog.require('goog.asserts');
+goog.require('goog.events.KeyHandler.EventType');
+goog.require('ol.events.ConditionType');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Interaction');
 
-/**
- * @param {Document} doc Document.
- * @return {Object}
- */
-ol.format.XML.prototype.readFromDocument = goog.abstractMethod;
 
 
 /**
- * @param {Node} node Node.
- * @return {Object}
+ * @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.format.XML.prototype.readFromNode = goog.abstractMethod;
+ol.interaction.KeyboardZoom = function(opt_options) {
 
-goog.provide('ol.format.OWS');
+  goog.base(this, {
+    handleEvent: ol.interaction.KeyboardZoom.handleEvent
+  });
 
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.object');
-goog.require('ol.format.XLink');
-goog.require('ol.format.XML');
-goog.require('ol.format.XSD');
-goog.require('ol.xml');
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {ol.events.ConditionType}
+   */
+  this.condition_ = goog.isDef(options.condition) ? options.condition :
+          ol.events.condition.targetNotEditable;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.delta_ = goog.isDef(options.delta) ? options.delta : 1;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.duration_ = goog.isDef(options.duration) ? options.duration : 100;
 
-/**
- * @constructor
- * @extends {ol.format.XML}
- */
-ol.format.OWS = function() {
-  goog.base(this);
 };
-goog.inherits(ol.format.OWS, ol.format.XML);
+goog.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction);
 
 
 /**
- * @param {Document} doc Document.
- * @return {Object} OWS object.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.KeyboardZoom}
+ * @api
  */
-ol.format.OWS.prototype.readFromDocument = function(doc) {
-  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
-  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
-    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
-      return this.readFromNode(n);
+ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) {
+  var stopEvent = false;
+  if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) {
+    var keyEvent = /** @type {goog.events.KeyEvent} */
+        (mapBrowserEvent.browserEvent);
+    var charCode = keyEvent.charCode;
+    if (this.condition_(mapBrowserEvent) &&
+        (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) {
+      var map = mapBrowserEvent.map;
+      var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_;
+      map.render();
+      var view = map.getView();
+      goog.asserts.assert(!goog.isNull(view));
+      ol.interaction.Interaction.zoomByDelta(
+          map, view, delta, undefined, this.duration_);
+      mapBrowserEvent.preventDefault();
+      stopEvent = true;
     }
   }
-  return null;
+  return !stopEvent;
 };
 
+goog.provide('ol.interaction.MouseWheelZoom');
+
+goog.require('goog.asserts');
+goog.require('goog.events.MouseWheelEvent');
+goog.require('goog.events.MouseWheelHandler.EventType');
+goog.require('goog.math');
+goog.require('ol');
+goog.require('ol.Coordinate');
+goog.require('ol.interaction.Interaction');
 
-/**
- * @param {Node} node Node.
- * @return {Object} OWS object.
- */
-ol.format.OWS.prototype.readFromNode = function(node) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  var owsObject = ol.xml.pushParseAndPop({},
-      ol.format.OWS.PARSERS_, node, []);
-  return goog.isDef(owsObject) ? owsObject : null;
-};
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @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.format.OWS.readAddress_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Address');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.ADDRESS_PARSERS_, node, objectStack);
-};
+ol.interaction.MouseWheelZoom = function(opt_options) {
 
+  goog.base(this, {
+    handleEvent: ol.interaction.MouseWheelZoom.handleEvent
+  });
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readAllowedValues_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'AllowedValues');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack);
-};
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.delta_ = 0;
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readConstraint_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Constraint');
-  var object = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(object));
-  var name = node.getAttribute('name');
-  var value = ol.xml.pushParseAndPop({},
-      ol.format.OWS.CONSTRAINT_PARSERS_, node,
-      objectStack);
-  if (!goog.isDef(value)) {
-    return undefined;
-  }
-  if (!goog.isDef(object.constraints)) {
-    object.constraints = {};
-  }
-  object.constraints[name] = value;
+  /**
+   * @private
+   * @type {number}
+   */
+  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
 
-};
+  /**
+   * @private
+   * @type {?ol.Coordinate}
+   */
+  this.lastAnchor_ = null;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.startTime_ = undefined;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.timeoutId_ = undefined;
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readContactInfo_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ContactInfo');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack);
 };
+goog.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.MouseWheelZoom}
+ * @api
  */
-ol.format.OWS.readDcp_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'DCP');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.DCP_PARSERS_, node, objectStack);
-};
+ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) {
+  var stopEvent = false;
+  if (mapBrowserEvent.type ==
+      goog.events.MouseWheelHandler.EventType.MOUSEWHEEL) {
+    var map = mapBrowserEvent.map;
+    var mouseWheelEvent = mapBrowserEvent.browserEvent;
+    goog.asserts.assertInstanceof(mouseWheelEvent, goog.events.MouseWheelEvent);
 
+    this.lastAnchor_ = mapBrowserEvent.coordinate;
+    this.delta_ += mouseWheelEvent.deltaY;
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readGet_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Get');
-  var object = objectStack[objectStack.length - 1];
-  var url = ol.format.XLink.readHref(node);
-  goog.asserts.assert(goog.isObject(object));
-  var value = ol.xml.pushParseAndPop({'url': url},
-      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
-  if (!goog.isDef(value)) {
-    return undefined;
-  }
-  var get = goog.object.get(object, 'get');
-  if (!goog.isDef(get)) {
-    goog.object.set(object, 'get', [value]);
-  }else {
-    goog.asserts.assert(goog.isArray(get));
-    get.push(value);
-  }
+    if (!goog.isDef(this.startTime_)) {
+      this.startTime_ = goog.now();
+    }
 
-};
+    var duration = ol.MOUSEWHEELZOOM_TIMEOUT_DURATION;
+    var timeLeft = Math.max(duration - (goog.now() - this.startTime_), 0);
 
+    goog.global.clearTimeout(this.timeoutId_);
+    this.timeoutId_ = goog.global.setTimeout(
+        goog.bind(this.doZoom_, this, map), timeLeft);
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readHttp_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'HTTP');
-  return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_,
-      node, objectStack);
+    mapBrowserEvent.preventDefault();
+    stopEvent = true;
+  }
+  return !stopEvent;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
  * @private
- * @return {Object|undefined}
+ * @param {ol.Map} map Map.
  */
-ol.format.OWS.readOperation_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Operation');
-  var name = node.getAttribute('name');
-  var value = ol.xml.pushParseAndPop({},
-      ol.format.OWS.OPERATION_PARSERS_, node, objectStack);
-  if (!goog.isDef(value)) {
-    return undefined;
-  }
-  var object = /** @type {Object} */
-      (objectStack[objectStack.length - 1]);
-  goog.asserts.assert(goog.isObject(object));
-  goog.object.set(object, name, value);
+ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) {
+  var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA;
+  var delta = goog.math.clamp(this.delta_, -maxDelta, maxDelta);
 
-};
+  var view = map.getView();
+  goog.asserts.assert(!goog.isNull(view));
 
+  map.render();
+  ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_,
+      this.duration_);
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readOperationsMetadata_ = function(node,
-    objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'OperationsMetadata');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node,
-      objectStack);
+  this.delta_ = 0;
+  this.lastAnchor_ = null;
+  this.startTime_ = undefined;
+  this.timeoutId_ = undefined;
 };
 
+goog.provide('ol.interaction.PinchRotate');
+
+goog.require('goog.asserts');
+goog.require('goog.style');
+goog.require('ol');
+goog.require('ol.Coordinate');
+goog.require('ol.ViewHint');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readPhone_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == '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}
+ * @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.format.OWS.readServiceIdentification_ = function(node,
-    objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ServiceIdentification');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node,
-      objectStack);
-};
+ol.interaction.PinchRotate = function(opt_options) {
 
+  goog.base(this, {
+    handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_,
+    handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_,
+    handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_
+  });
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readServiceContact_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ServiceContact');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node,
-      objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ServiceProvider');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node,
-      objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined}
- */
-ol.format.OWS.readValue_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Value');
-  var object = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(object));
-  var key = ol.format.XSD.readString(node);
-  if (!goog.isDef(key)) {
-    return undefined;
-  }
-  goog.object.set(object, key, true);
-};
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.OWS.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/ows/1.1'
-];
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'ServiceIdentification': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceIdentification_,
-          'serviceIdentification'),
-      'ServiceProvider': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceProvider_,
-          'serviceProvider'),
-      'OperationsMetadata': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readOperationsMetadata_,
-          'operationsMetadata')
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'DeliveryPoint': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'deliveryPoint'),
-      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'city'),
-      'AdministrativeArea': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'administrativeArea'),
-      'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'postalCode'),
-      'Country': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'country'),
-      'ElectronicMailAddress': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'electronicMailAddress')
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Value': ol.format.OWS.readValue_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'AllowedValues': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readAllowedValues_, 'allowedValues'
-      )
-    });
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.anchor_ = null;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Phone': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readPhone_, 'phone'),
-      'Address': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readAddress_, 'address')
-    });
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.lastAngle_ = undefined;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.rotating_ = false;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.DCP_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readHttp_, 'http')
-    });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.rotationDelta_ = 0.0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.threshold_ = goog.isDef(options.threshold) ? options.threshold : 0.3;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Get': ol.format.OWS.readGet_,
-      'Post': undefined // TODO
-    });
+};
+goog.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.PinchRotate}
  * @private
  */
-ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'DCP': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readDcp_, 'dcp')
-    });
+ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) {
+  goog.asserts.assert(this.targetPointers.length >= 2);
+  var rotationDelta = 0.0;
 
+  var touch0 = this.targetPointers[0];
+  var touch1 = this.targetPointers[1];
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Operation': ol.format.OWS.readOperation_
-    });
+  // angle between touches
+  var angle = Math.atan2(
+      touch1.clientY - touch0.clientY,
+      touch1.clientX - touch0.clientX);
 
+  if (goog.isDef(this.lastAngle_)) {
+    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;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'voice'),
-      'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'facsimile')
-    });
+  var map = mapBrowserEvent.map;
 
+  // rotate anchor point.
+  // FIXME: should be the intersection point between the lines:
+  //     touch0,touch1 and previousTouch0,previousTouch1
+  var viewportPosition = goog.style.getClientPosition(map.getViewport());
+  var centroid =
+      ol.interaction.Pointer.centroid(this.targetPointers);
+  centroid[0] -= viewportPosition.x;
+  centroid[1] -= viewportPosition.y;
+  this.anchor_ = map.getCoordinateFromPixel(centroid);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Constraint': ol.format.OWS.readConstraint_
-    });
+  // rotate
+  if (this.rotating_) {
+    var view = map.getView();
+    var viewState = view.getState();
+    map.render();
+    ol.interaction.Interaction.rotateWithoutConstraints(map, view,
+        viewState.rotation + rotationDelta, this.anchor_);
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.PinchRotate}
  * @private
  */
-ol.format.OWS.SERVICE_CONTACT_PARSERS_ =
-    ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'IndividualName': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'individualName'),
-      'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'positionName'),
-      'ContactInfo': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readContactInfo_, 'contactInfo')
-    });
+ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) {
+  if (this.targetPointers.length < 2) {
+    var map = mapBrowserEvent.map;
+    var view = map.getView();
+    view.setHint(ol.ViewHint.INTERACTING, -1);
+    if (this.rotating_) {
+      var viewState = view.getState();
+      ol.interaction.Interaction.rotate(
+          map, view, viewState.rotation, this.anchor_,
+          ol.ROTATE_ANIMATION_DURATION);
+    }
+    return false;
+  } else {
+    return true;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchRotate}
  * @private
  */
-ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
-    ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'title'),
-      'ServiceTypeVersion': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'serviceTypeVersion'),
-      'ServiceType': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'serviceType')
-    });
+ol.interaction.PinchRotate.handleDownEvent_ = function(mapBrowserEvent) {
+  if (this.targetPointers.length >= 2) {
+    var map = mapBrowserEvent.map;
+    this.anchor_ = null;
+    this.lastAngle_ = undefined;
+    this.rotating_ = false;
+    this.rotationDelta_ = 0.0;
+    if (!this.handlingDownUpSequence) {
+      map.getView().setHint(ol.ViewHint.INTERACTING, 1);
+    }
+    map.render();
+    return true;
+  } else {
+    return false;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.OWS.SERVICE_PROVIDER_PARSERS_ =
-    ol.xml.makeParsersNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'providerName'),
-      'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref,
-          'providerSite'),
-      'ServiceContact': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceContact_, 'serviceContact')
-    });
+ol.interaction.PinchRotate.prototype.shouldStopEvent = goog.functions.FALSE;
 
-goog.provide('ol.format.Polyline');
+goog.provide('ol.interaction.PinchZoom');
 
 goog.require('goog.asserts');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.TextFeature');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.proj');
+goog.require('goog.style');
+goog.require('ol.Coordinate');
+goog.require('ol.ViewHint');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
 
 
 
 /**
+ * @classdesc
+ * Allows the user to zoom the map by pinching with two fingers
+ * on a touch screen.
+ *
  * @constructor
- * @extends {ol.format.TextFeature}
- * @param {olx.format.PolylineOptions=} opt_options
- *     Optional configuration object.
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.PinchZoomOptions=} opt_options Options.
  * @api stable
  */
-ol.format.Polyline = function(opt_options) {
+ol.interaction.PinchZoom = function(opt_options) {
+
+  goog.base(this, {
+    handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_,
+    handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_,
+    handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_
+  });
 
   var options = goog.isDef(opt_options) ? opt_options : {};
 
-  goog.base(this);
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.anchor_ = null;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {number}
    */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+  this.duration_ = goog.isDef(options.duration) ? options.duration : 400;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.lastDistance_ = undefined;
 
   /**
    * @private
    * @type {number}
    */
-  this.factor_ = goog.isDef(options.factor) ? options.factor : 1e5;
+  this.lastScaleDelta_ = 1;
+
 };
-goog.inherits(ol.format.Polyline, ol.format.TextFeature);
+goog.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer);
 
 
 /**
- * Encode a list of n-dimensional points and return an encoded string
- *
- * Attention: This function will modify the passed array!
- *
- * @param {Array.<number>} numbers A list of n-dimensional points.
- * @param {number} stride The number of dimension of the points in the list.
- * @param {number=} opt_factor The factor by which the numbers will be
- *     multiplied. The remaining decimal places will get rounded away.
- *     Default is `1e5`.
- * @return {string} The encoded string.
- * @api
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.PinchZoom}
+ * @private
  */
-ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) {
-  var factor = goog.isDef(opt_factor) ? opt_factor : 1e5;
-  var d;
+ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) {
+  goog.asserts.assert(this.targetPointers.length >= 2);
+  var scaleDelta = 1.0;
 
-  var lastNumbers = new Array(stride);
-  for (d = 0; d < stride; ++d) {
-    lastNumbers[d] = 0;
-  }
+  var touch0 = this.targetPointers[0];
+  var touch1 = this.targetPointers[1];
+  var dx = touch0.clientX - touch1.clientX;
+  var dy = touch0.clientY - touch1.clientY;
 
-  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;
+  // distance between touches
+  var distance = Math.sqrt(dx * dx + dy * dy);
 
-      numbers[i] = delta;
-    }
+  if (goog.isDef(this.lastDistance_)) {
+    scaleDelta = this.lastDistance_ / distance;
+  }
+  this.lastDistance_ = distance;
+  if (scaleDelta != 1.0) {
+    this.lastScaleDelta_ = scaleDelta;
   }
 
-  return ol.format.Polyline.encodeFloats(numbers, factor);
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  var viewState = view.getState();
+
+  // scale anchor point.
+  var viewportPosition = goog.style.getClientPosition(map.getViewport());
+  var centroid =
+      ol.interaction.Pointer.centroid(this.targetPointers);
+  centroid[0] -= viewportPosition.x;
+  centroid[1] -= viewportPosition.y;
+  this.anchor_ = map.getCoordinateFromPixel(centroid);
+
+  // scale, bypass the resolution constraint
+  map.render();
+  ol.interaction.Interaction.zoomWithoutConstraints(
+      map, view, viewState.resolution * scaleDelta, this.anchor_);
+
 };
 
 
 /**
- * Decode a list of n-dimensional points from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @param {number} stride The number of dimension of the points in the
- *     encoded string.
- * @param {number=} opt_factor The factor by which the resulting numbers will
- *     be divided. Default is `1e5`.
- * @return {Array.<number>} A list of n-dimensional points.
- * @api
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.PinchZoom}
+ * @private
  */
-ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) {
-  var factor = goog.isDef(opt_factor) ? opt_factor : 1e5;
-  var d;
-
-  /** @type {Array.<number>} */
-  var lastNumbers = new Array(stride);
-  for (d = 0; d < stride; ++d) {
-    lastNumbers[d] = 0;
+ol.interaction.PinchZoom.handleUpEvent_ =
+    function(mapBrowserEvent) {
+  if (this.targetPointers.length < 2) {
+    var map = mapBrowserEvent.map;
+    var view = map.getView();
+    view.setHint(ol.ViewHint.INTERACTING, -1);
+    var viewState = view.getState();
+    // Zoom to final resolution, with an animation, and provide a
+    // direction not to zoom out/in if user was pinching in/out.
+    // Direction is > 0 if pinching out, and < 0 if pinching in.
+    var direction = this.lastScaleDelta_ - 1;
+    ol.interaction.Interaction.zoom(map, view, viewState.resolution,
+        this.anchor_, this.duration_, direction);
+    return false;
+  } else {
+    return true;
   }
+};
 
-  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];
+/**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchZoom}
+ * @private
+ */
+ol.interaction.PinchZoom.handleDownEvent_ =
+    function(mapBrowserEvent) {
+  if (this.targetPointers.length >= 2) {
+    var map = mapBrowserEvent.map;
+    this.anchor_ = null;
+    this.lastDistance_ = undefined;
+    this.lastScaleDelta_ = 1;
+    if (!this.handlingDownUpSequence) {
+      map.getView().setHint(ol.ViewHint.INTERACTING, 1);
     }
+    map.render();
+    return true;
+  } else {
+    return false;
   }
-
-  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
+ * @inheritDoc
  */
-ol.format.Polyline.encodeFloats = function(numbers, opt_factor) {
-  var factor = goog.isDef(opt_factor) ? opt_factor : 1e5;
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    numbers[i] = Math.round(numbers[i] * factor);
-  }
-
-  return ol.format.Polyline.encodeSignedIntegers(numbers);
-};
+ol.interaction.PinchZoom.prototype.shouldStopEvent = goog.functions.FALSE;
 
+goog.provide('ol.interaction');
 
-/**
- * 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 = goog.isDef(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;
-};
+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');
 
 
 /**
- * Encode a list of signed integers and return an encoded string
+ * 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}
  *
- * Attention: This function will modify the passed array!
+ * Note that DragZoom renders a box as a vector polygon, so this interaction
+ * should be excluded if you want a build with no vector support.
  *
- * @param {Array.<number>} numbers A list of signed integers.
- * @return {string} The encoded string.
+ * @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.format.Polyline.encodeSignedIntegers = function(numbers) {
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    var num = numbers[i];
-    numbers[i] = (num < 0) ? ~(num << 1) : (num << 1);
+ol.interaction.defaults = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  var interactions = new ol.Collection();
+
+  var kinetic = new ol.Kinetic(-0.005, 0.05, 100);
+
+  var altShiftDragRotate = goog.isDef(options.altShiftDragRotate) ?
+      options.altShiftDragRotate : true;
+  if (altShiftDragRotate) {
+    interactions.push(new ol.interaction.DragRotate());
   }
-  return ol.format.Polyline.encodeUnsignedIntegers(numbers);
-};
 
+  var doubleClickZoom = goog.isDef(options.doubleClickZoom) ?
+      options.doubleClickZoom : true;
+  if (doubleClickZoom) {
+    interactions.push(new ol.interaction.DoubleClickZoom({
+      delta: options.zoomDelta,
+      duration: options.zoomDuration
+    }));
+  }
 
-/**
- * Decode a list of signed integers from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @return {Array.<number>} A list of signed integers.
- */
-ol.format.Polyline.decodeSignedIntegers = function(encoded) {
-  var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded);
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    var num = numbers[i];
-    numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1);
+  var dragPan = goog.isDef(options.dragPan) ?
+      options.dragPan : true;
+  if (dragPan) {
+    interactions.push(new ol.interaction.DragPan({
+      kinetic: kinetic
+    }));
   }
-  return numbers;
+
+  var pinchRotate = goog.isDef(options.pinchRotate) ?
+      options.pinchRotate : true;
+  if (pinchRotate) {
+    interactions.push(new ol.interaction.PinchRotate());
+  }
+
+  var pinchZoom = goog.isDef(options.pinchZoom) ?
+      options.pinchZoom : true;
+  if (pinchZoom) {
+    interactions.push(new ol.interaction.PinchZoom({
+      duration: options.zoomDuration
+    }));
+  }
+
+  var keyboard = goog.isDef(options.keyboard) ?
+      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 = goog.isDef(options.mouseWheelZoom) ?
+      options.mouseWheelZoom : true;
+  if (mouseWheelZoom) {
+    interactions.push(new ol.interaction.MouseWheelZoom({
+      duration: options.zoomDuration
+    }));
+  }
+
+  var shiftDragZoom = goog.isDef(options.shiftDragZoom) ?
+      options.shiftDragZoom : true;
+  if (shiftDragZoom) {
+    interactions.push(new ol.interaction.DragZoom());
+  }
+
+  return interactions;
+
 };
 
+goog.provide('ol.layer.Group');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.math');
+goog.require('goog.object');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEvent');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Object');
+goog.require('ol.ObjectEventType');
+goog.require('ol.extent');
+goog.require('ol.layer.Base');
+goog.require('ol.source.State');
+
 
 /**
- * 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.
+ * @enum {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;
+ol.layer.GroupProperty = {
+  LAYERS: 'layers'
 };
 
 
+
 /**
- * Decode a list of unsigned integers from an encoded string
+ * @classdesc
+ * A {@link ol.Collection} of layers that are handled together.
  *
- * @param {string} encoded An encoded string.
- * @return {Array.<number>} A list of unsigned integers.
+ * @constructor
+ * @extends {ol.layer.Base}
+ * @fires change Triggered when the group/Collection changes.
+ * @param {olx.layer.GroupOptions=} opt_options Layer options.
+ * @api stable
  */
-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;
+ol.layer.Group = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+  var baseOptions = /** @type {olx.layer.GroupOptions} */
+      (goog.object.clone(options));
+  delete baseOptions.layers;
+
+  var layers = options.layers;
+
+  goog.base(this, baseOptions);
+
+  /**
+   * @private
+   * @type {Object.<string, goog.events.Key>}
+   */
+  this.listenerKeys_ = null;
+
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.GroupProperty.LAYERS),
+      this.handleLayersChanged_, false, this);
+
+  if (goog.isDefAndNotNull(layers)) {
+    if (goog.isArray(layers)) {
+      layers = new ol.Collection(goog.array.clone(layers));
     } else {
-      shift += 5;
+      goog.asserts.assertInstanceof(layers, ol.Collection);
+      layers = layers;
     }
+  } else {
+    layers = new ol.Collection();
   }
-  return numbers;
+
+  this.setLayers(layers);
+
 };
+goog.inherits(ol.layer.Group, ol.layer.Base);
 
 
 /**
- * Encode one single unsigned integer and return an encoded string
- *
- * @param {number} num Unsigned integer that should be encoded.
- * @return {string} The encoded string.
+ * @private
  */
-ol.format.Polyline.encodeUnsignedInteger = function(num) {
-  var value, encoded = '';
-  while (num >= 0x20) {
-    value = (0x20 | (num & 0x1f)) + 63;
-    encoded += String.fromCharCode(value);
-    num >>= 5;
+ol.layer.Group.prototype.handleLayerChange_ = function() {
+  if (this.getVisible()) {
+    this.changed();
   }
-  value = num + 63;
-  encoded += String.fromCharCode(value);
-  return encoded;
 };
 
 
 /**
- * Read the feature from the Polyline source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
+ * @param {goog.events.Event} event Event.
+ * @private
  */
-ol.format.Polyline.prototype.readFeature;
+ol.layer.Group.prototype.handleLayersChanged_ = function(event) {
+  if (!goog.isNull(this.listenerKeys_)) {
+    goog.array.forEach(
+        goog.object.getValues(this.listenerKeys_), goog.events.unlistenByKey);
+    this.listenerKeys_ = null;
+  }
 
+  var layers = this.getLayers();
+  if (goog.isDefAndNotNull(layers)) {
+    this.listenerKeys_ = {
+      'add': goog.events.listen(layers, ol.CollectionEventType.ADD,
+          this.handleLayersAdd_, false, this),
+      'remove': goog.events.listen(layers, ol.CollectionEventType.REMOVE,
+          this.handleLayersRemove_, false, this)
+    };
 
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) {
-  var geometry = this.readGeometryFromText(text, opt_options);
-  return new ol.Feature(geometry);
+    var layersArray = layers.getArray();
+    var i, ii, layer;
+    for (i = 0, ii = layersArray.length; i < ii; i++) {
+      layer = layersArray[i];
+      this.listenerKeys_[goog.getUid(layer).toString()] =
+          goog.events.listen(layer,
+              [ol.ObjectEventType.PROPERTYCHANGE, goog.events.EventType.CHANGE],
+              this.handleLayerChange_, false, this);
+    }
+  }
+
+  this.changed();
 };
 
 
 /**
- * Read the feature from the source. As Polyline sources contain a single
- * feature, this will return the feature in an array.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * @param {ol.CollectionEvent} collectionEvent Collection event.
+ * @private
  */
-ol.format.Polyline.prototype.readFeatures;
+ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) {
+  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
+  this.listenerKeys_[goog.getUid(layer).toString()] = goog.events.listen(
+      layer, [ol.ObjectEventType.PROPERTYCHANGE, goog.events.EventType.CHANGE],
+      this.handleLayerChange_, false, this);
+  this.changed();
+};
 
 
 /**
- * @inheritDoc
+ * @param {ol.CollectionEvent} collectionEvent Collection event.
+ * @private
  */
-ol.format.Polyline.prototype.readFeaturesFromText =
-    function(text, opt_options) {
-  var feature = this.readFeatureFromText(text, opt_options);
-  return [feature];
+ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) {
+  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
+  var key = goog.getUid(layer).toString();
+  goog.events.unlistenByKey(this.listenerKeys_[key]);
+  delete this.listenerKeys_[key];
+  this.changed();
 };
 
 
 /**
- * Read the geometry from the source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
+ * @return {!ol.Collection.<ol.layer.Base>} Collection of
+ * {@link ol.layer.Layer layers} that are part of this group.
+ * @observable
  * @api stable
  */
-ol.format.Polyline.prototype.readGeometry;
-
-
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.readGeometryFromText =
-    function(text, opt_options) {
-  var flatCoordinates = ol.format.Polyline.decodeDeltas(text, 2, this.factor_);
-  var coordinates = ol.geom.flat.inflate.coordinates(
-      flatCoordinates, 0, flatCoordinates.length, 2);
-
-  return /** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(
-          new ol.geom.LineString(coordinates), false,
-          this.adaptOptions(opt_options)));
+ol.layer.Group.prototype.getLayers = function() {
+  return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get(
+      ol.layer.GroupProperty.LAYERS));
 };
+goog.exportProperty(
+    ol.layer.Group.prototype,
+    'getLayers',
+    ol.layer.Group.prototype.getLayers);
 
 
 /**
- * Read the projection from a Polyline source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
+ * @param {!ol.Collection.<ol.layer.Base>} layers Collection of
+ * {@link ol.layer.Layer layers} that are part of this group.
+ * @observable
  * @api stable
  */
-ol.format.Polyline.prototype.readProjection;
+ol.layer.Group.prototype.setLayers = function(layers) {
+  this.set(ol.layer.GroupProperty.LAYERS, layers);
+};
+goog.exportProperty(
+    ol.layer.Group.prototype,
+    'setLayers',
+    ol.layer.Group.prototype.setLayers);
 
 
 /**
  * @inheritDoc
  */
-ol.format.Polyline.prototype.readProjectionFromText = function(text) {
-  return this.defaultDataProjection;
+ol.layer.Group.prototype.getLayersArray = function(opt_array) {
+  var array = goog.isDef(opt_array) ? opt_array : [];
+  this.getLayers().forEach(function(layer) {
+    layer.getLayersArray(array);
+  });
+  return array;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) {
-  var geometry = feature.getGeometry();
-  if (goog.isDefAndNotNull(geometry)) {
-    return this.writeGeometryText(geometry, opt_options);
-  } else {
-    goog.asserts.fail();
-    return '';
-  }
-};
+ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) {
+  var states = goog.isDef(opt_states) ? opt_states : [];
 
+  var pos = states.length;
 
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.writeFeaturesText =
-    function(features, opt_options) {
-  goog.asserts.assert(features.length == 1);
-  return this.writeFeatureText(features[0], opt_options);
-};
+  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.brightness = goog.math.clamp(
+        layerState.brightness + ownLayerState.brightness, -1, 1);
+    layerState.contrast *= ownLayerState.contrast;
+    layerState.hue += ownLayerState.hue;
+    layerState.opacity *= ownLayerState.opacity;
+    layerState.saturation *= ownLayerState.saturation;
+    layerState.visible = layerState.visible && ownLayerState.visible;
+    layerState.maxResolution = Math.min(
+        layerState.maxResolution, ownLayerState.maxResolution);
+    layerState.minResolution = Math.max(
+        layerState.minResolution, ownLayerState.minResolution);
+    if (goog.isDef(ownLayerState.extent)) {
+      if (goog.isDef(layerState.extent)) {
+        layerState.extent = ol.extent.getIntersection(
+            layerState.extent, ownLayerState.extent);
+      } else {
+        layerState.extent = ownLayerState.extent;
+      }
+    }
+  }
 
-/**
- * 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;
+  return states;
+};
 
 
 /**
  * @inheritDoc
  */
-ol.format.Polyline.prototype.writeGeometryText =
-    function(geometry, opt_options) {
-  goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-  geometry = /** @type {ol.geom.LineString} */
-      (ol.format.Feature.transformWithOptions(
-          geometry, true, this.adaptOptions(opt_options)));
-  var flatCoordinates = geometry.getFlatCoordinates();
-  var stride = geometry.getStride();
-  return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_);
+ol.layer.Group.prototype.getSourceState = function() {
+  return ol.source.State.READY;
 };
 
-goog.provide('ol.format.TopoJSON');
+goog.provide('ol.proj.EPSG3857');
 
+goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.JSONFeature');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
+goog.require('ol.math');
 goog.require('ol.proj');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
 
 
 
 /**
  * @classdesc
- * Feature format for reading and writing data in the TopoJSON format.
+ * Projection object for web/spherical Mercator (EPSG:3857).
  *
  * @constructor
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.TopoJSONOptions=} opt_options Options.
- * @api stable
+ * @extends {ol.proj.Projection}
+ * @param {string} code Code.
+ * @private
  */
-ol.format.TopoJSON = function(opt_options) {
+ol.proj.EPSG3857_ = function(code) {
+  goog.base(this, {
+    code: code,
+    units: ol.proj.Units.METERS,
+    extent: ol.proj.EPSG3857.EXTENT,
+    global: true,
+    worldExtent: ol.proj.EPSG3857.WORLD_EXTENT
+  });
+};
+goog.inherits(ol.proj.EPSG3857_, ol.proj.Projection);
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  goog.base(this);
+/**
+ * @inheritDoc
+ */
+ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) {
+  return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS);
+};
 
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get(
-      goog.isDefAndNotNull(options.defaultDataProjection) ?
-          options.defaultDataProjection : 'EPSG:4326');
 
-};
-goog.inherits(ol.format.TopoJSON, ol.format.JSONFeature);
+/**
+ * @const
+ * @type {number}
+ */
+ol.proj.EPSG3857.RADIUS = 6378137;
 
 
 /**
- * @const {Array.<string>}
- * @private
+ * @const
+ * @type {number}
  */
-ol.format.TopoJSON.EXTENSIONS_ = ['.topojson'];
+ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS;
 
 
 /**
- * Concatenate arcs into a coordinate array.
- * @param {Array.<number>} indices Indices of arcs to concatenate.  Negative
- *     values indicate arcs need to be reversed.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs (already
- *     transformed).
- * @return {Array.<ol.Coordinate>} Coordinates array.
- * @private
+ * @const
+ * @type {ol.Extent}
  */
-ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) {
-  /** @type {Array.<ol.Coordinate>} */
-  var coordinates = [];
-  var index, arc;
-  var i, ii;
-  var j, jj;
-  for (i = 0, ii = indices.length; i < ii; ++i) {
-    index = indices[i];
-    if (i > 0) {
-      // splicing together arcs, discard last point
-      coordinates.pop();
-    }
-    if (index >= 0) {
-      // forward arc
-      arc = arcs[index];
-    } else {
-      // reverse arc
-      arc = arcs[~index].slice().reverse();
-    }
-    coordinates.push.apply(coordinates, arc);
-  }
-  // provide fresh copies of coordinate arrays
-  for (j = 0, jj = coordinates.length; j < jj; ++j) {
-    coordinates[j] = coordinates[j].slice();
-  }
-  return coordinates;
-};
+ol.proj.EPSG3857.EXTENT = [
+  -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE,
+  ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE
+];
 
 
 /**
- * Create a point from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @return {ol.geom.Point} Geometry.
- * @private
+ * @const
+ * @type {ol.Extent}
  */
-ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) {
-  var coordinates = object.coordinates;
-  if (!goog.isNull(scale) && !goog.isNull(translate)) {
-    ol.format.TopoJSON.transformVertex_(coordinates, scale, translate);
-  }
-  return new ol.geom.Point(coordinates);
-};
+ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85];
 
 
 /**
- * Create a multi-point from a TopoJSON geometry object.
+ * Lists several projection codes with the same meaning as EPSG:3857.
  *
- * @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
+ * @type {Array.<string>}
  */
-ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale,
-    translate) {
-  var coordinates = object.coordinates;
-  var i, ii;
-  if (!goog.isNull(scale) && !goog.isNull(translate)) {
-    for (i = 0, ii = coordinates.length; i < ii; ++i) {
-      ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate);
-    }
-  }
-  return new ol.geom.MultiPoint(coordinates);
-};
+ol.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'
+];
 
 
 /**
- * Create a linestring from a TopoJSON geometry object.
+ * Projections equal to EPSG:3857.
  *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.LineString} Geometry.
- * @private
+ * @const
+ * @type {Array.<ol.proj.Projection>}
  */
-ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
-  var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
-  return new ol.geom.LineString(coordinates);
-};
+ol.proj.EPSG3857.PROJECTIONS = goog.array.map(
+    ol.proj.EPSG3857.CODES,
+    function(code) {
+      return new ol.proj.EPSG3857_(code);
+    });
 
 
 /**
- * Create a multi-linestring from a TopoJSON geometry object.
+ * Transformation from EPSG:4326 to EPSG:3857.
  *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.MultiLineString} Geometry.
- * @private
+ * @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.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) {
-  var coordinates = [];
-  var i, ii;
-  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
-    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
+ol.proj.EPSG3857.fromEPSG4326 = function(input, opt_output, opt_dimension) {
+  var length = input.length,
+      dimension = opt_dimension > 1 ? opt_dimension : 2,
+      output = opt_output;
+  if (!goog.isDef(output)) {
+    if (dimension > 2) {
+      // preserve values beyond second dimension
+      output = input.slice();
+    } else {
+      output = new Array(length);
+    }
   }
-  return new ol.geom.MultiLineString(coordinates);
+  goog.asserts.assert(output.length % dimension === 0);
+  for (var i = 0; i < length; i += dimension) {
+    output[i] = ol.proj.EPSG3857.RADIUS * Math.PI * input[i] / 180;
+    output[i + 1] = ol.proj.EPSG3857.RADIUS *
+        Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360));
+  }
+  return output;
 };
 
 
 /**
- * Create a polygon from a TopoJSON geometry object.
+ * Transformation from EPSG:3857 to EPSG:4326.
  *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.Polygon} Geometry.
- * @private
+ * @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.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) {
-  var coordinates = [];
-  var i, ii;
-  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
-    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
+ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) {
+  var length = input.length,
+      dimension = opt_dimension > 1 ? opt_dimension : 2,
+      output = opt_output;
+  if (!goog.isDef(output)) {
+    if (dimension > 2) {
+      // preserve values beyond second dimension
+      output = input.slice();
+    } else {
+      output = new Array(length);
+    }
   }
-  return new ol.geom.Polygon(coordinates);
+  goog.asserts.assert(output.length % dimension === 0);
+  for (var i = 0; i < length; i += dimension) {
+    output[i] = 180 * input[i] / (ol.proj.EPSG3857.RADIUS * Math.PI);
+    output[i + 1] = 360 * Math.atan(
+        Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90;
+  }
+  return output;
 };
 
+goog.provide('ol.proj.EPSG4326');
+
+goog.require('ol.proj');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+
+
 
 /**
- * Create a multi-polygon from a TopoJSON geometry object.
+ * @classdesc
+ * Projection object for WGS84 geographic coordinates (EPSG:4326).
  *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.MultiPolygon} Geometry.
+ * 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.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);
+ol.proj.EPSG4326_ = function(code, opt_axisOrientation) {
+  goog.base(this, {
+    code: code,
+    units: ol.proj.Units.DEGREES,
+    extent: ol.proj.EPSG4326.EXTENT,
+    axisOrientation: opt_axisOrientation,
+    global: true,
+    worldExtent: ol.proj.EPSG4326.EXTENT
+  });
 };
+goog.inherits(ol.proj.EPSG4326_, ol.proj.Projection);
 
 
 /**
  * @inheritDoc
  */
-ol.format.TopoJSON.prototype.getExtensions = function() {
-  return ol.format.TopoJSON.EXTENSIONS_;
+ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) {
+  return resolution;
 };
 
 
 /**
- * Create features from a TopoJSON GeometryCollection object.
+ * Extent of the EPSG:4326 projection which is the whole world.
  *
- * @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
+ * @const
+ * @type {ol.Extent}
  */
-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;
-};
+ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90];
 
 
 /**
- * Create a feature from a TopoJSON geometry object.
+ * Projections equal to EPSG:4326.
  *
- * @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
+ * @const
+ * @type {Array.<ol.proj.Projection>}
  */
-ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs,
-    scale, translate, opt_options) {
-  var geometry;
-  var type = object.type;
-  var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type];
-  goog.asserts.assert(goog.isDef(geometryReader));
-  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 (goog.isDef(object.id)) {
-    feature.setId(object.id);
-  }
-  if (goog.isDef(object.properties)) {
-    feature.setProperties(object.properties);
-  }
-  return feature;
-};
+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');
 
-/**
- * Read all features from a TopoJSON source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.TopoJSON.prototype.readFeatures;
+goog.require('ol.proj');
+goog.require('ol.proj.EPSG3857');
+goog.require('ol.proj.EPSG4326');
 
 
 /**
- * @inheritDoc
+ * FIXME empty description for jsdoc
+ * @api
  */
-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 (goog.isDef(topoJSONTopology.transform)) {
-      transform = /** @type {TopoJSONTransform} */
-          (topoJSONTopology.transform);
-      scale = transform.scale;
-      translate = transform.translate;
-    }
-    var arcs = topoJSONTopology.arcs;
-    if (goog.isDef(transform)) {
-      ol.format.TopoJSON.transformArcs_(arcs, scale, translate);
-    }
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    var topoJSONFeatures = goog.object.getValues(topoJSONTopology.objects);
-    var i, ii;
-    var feature;
-    for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) {
-      if (topoJSONFeatures[i].type === 'GeometryCollection') {
-        feature = /** @type {TopoJSONGeometryCollection} */
-            (topoJSONFeatures[i]);
-        features.push.apply(features,
-            ol.format.TopoJSON.readFeaturesFromGeometryCollection_(
-                feature, arcs, scale, translate, opt_options));
-      } else {
-        feature = /** @type {TopoJSONGeometry} */
-            (topoJSONFeatures[i]);
-        features.push(ol.format.TopoJSON.readFeatureFromGeometry_(
-            feature, arcs, scale, translate, opt_options));
-      }
-    }
-    return features;
-  } else {
-    return [];
-  }
+ol.proj.common.add = function() {
+  // Add transformations that don't alter coordinates to convert within set of
+  // projections with equal meaning.
+  ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS);
+  ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS);
+  // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like
+  // coordinates and back.
+  ol.proj.addEquivalentTransforms(
+      ol.proj.EPSG4326.PROJECTIONS,
+      ol.proj.EPSG3857.PROJECTIONS,
+      ol.proj.EPSG3857.fromEPSG4326,
+      ol.proj.EPSG3857.toEPSG4326);
 };
 
+goog.provide('ol.layer.Image');
 
-/**
- * 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);
-  }
-};
-
+goog.require('ol.layer.Layer');
 
-/**
- * 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.
+ * @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.
  *
- * @param {ol.Coordinate} vertex Vertex.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @private
+ * @constructor
+ * @extends {ol.layer.Layer}
+ * @fires ol.render.Event
+ * @param {olx.layer.ImageOptions=} opt_options Layer options.
+ * @api stable
  */
-ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
-  vertex[0] = vertex[0] * scale[0] + translate[0];
-  vertex[1] = vertex[1] * scale[1] + translate[1];
+ol.layer.Image = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
+  goog.base(this,  /** @type {olx.layer.LayerOptions} */ (options));
 };
+goog.inherits(ol.layer.Image, ol.layer.Layer);
 
 
 /**
- * Read the projection from a TopoJSON source.
- *
  * @function
- * @param {ArrayBuffer|Document|Node|Object|string} object Source.
- * @return {ol.proj.Projection} Projection.
+ * @return {ol.source.Image} Source.
  * @api stable
  */
-ol.format.TopoJSON.prototype.readProjection = function(object) {
-  return this.defaultDataProjection;
-};
+ol.layer.Image.prototype.getSource;
+
+goog.provide('ol.layer.Tile');
+
+goog.require('ol.layer.Layer');
 
 
 /**
- * @const
- * @private
- * @type {Object.<string, function(TopoJSONGeometry, Array, ...[Array]): ol.geom.Geometry>}
+ * @enum {string}
  */
-ol.format.TopoJSON.GEOMETRY_READERS_ = {
-  'Point': ol.format.TopoJSON.readPointGeometry_,
-  'LineString': ol.format.TopoJSON.readLineStringGeometry_,
-  'Polygon': ol.format.TopoJSON.readPolygonGeometry_,
-  'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_,
-  'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_,
-  'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_
+ol.layer.TileProperty = {
+  PRELOAD: 'preload',
+  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
 };
 
-goog.provide('ol.format.WFS');
-
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.object');
-goog.require('ol.format.GML');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.format.XSD');
-goog.require('ol.geom.Geometry');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
 
 
 /**
  * @classdesc
- * Feature format for reading and writing data in the WFS format.
- * Currently only supports WFS version 1.1.0.
- * Also see {@link ol.format.GML} which is used by this format.
+ * 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
- * @param {olx.format.WFSOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.XMLFeature}
+ * @extends {ol.layer.Layer}
+ * @fires ol.render.Event
+ * @param {olx.layer.TileOptions=} opt_options Tile layer options.
  * @api stable
  */
-ol.format.WFS = function(opt_options) {
-  var options = /** @type {olx.format.WFSOptions} */
-      (goog.isDef(opt_options) ? opt_options : {});
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.featureType_ = options.featureType;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.featureNS_ = options.featureNS;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.schemaLocation_ = goog.isDef(options.schemaLocation) ?
-      options.schemaLocation : ol.format.WFS.schemaLocation_;
-
-  goog.base(this);
+ol.layer.Tile = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
+  goog.base(this,  /** @type {olx.layer.LayerOptions} */ (options));
 };
-goog.inherits(ol.format.WFS, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.featurePrefix = 'feature';
+goog.inherits(ol.layer.Tile, ol.layer.Layer);
 
 
 /**
- * @const
- * @type {string}
+ * @return {number|undefined} The level to preload tiles up to.
+ * @observable
+ * @api
  */
-ol.format.WFS.xmlns = 'http://www.w3.org/2000/xmlns/';
+ol.layer.Tile.prototype.getPreload = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.layer.TileProperty.PRELOAD));
+};
+goog.exportProperty(
+    ol.layer.Tile.prototype,
+    'getPreload',
+    ol.layer.Tile.prototype.getPreload);
 
 
 /**
- * Number of features; bounds/extent.
- * @typedef {{numberOfFeatures: number,
- *            bounds: ol.Extent}}
+ * @function
+ * @return {ol.source.Tile} Source.
  * @api stable
  */
-ol.format.WFS.FeatureCollectionMetadata;
+ol.layer.Tile.prototype.getSource;
 
 
 /**
- * Total deleted; total inserted; total updated; array of insert ids.
- * @typedef {{totalDeleted: number,
- *            totalInserted: number,
- *            totalUpdated: number,
- *            insertIds: Array.<string>}}
- * @api stable
+ * @param {number} preload The level to preload tiles up to.
+ * @observable
+ * @api
  */
-ol.format.WFS.TransactionResponse;
+ol.layer.Tile.prototype.setPreload = function(preload) {
+  this.set(ol.layer.TileProperty.PRELOAD, preload);
+};
+goog.exportProperty(
+    ol.layer.Tile.prototype,
+    'setPreload',
+    ol.layer.Tile.prototype.setPreload);
 
 
 /**
- * @const
- * @type {string}
- * @private
- */
-ol.format.WFS.schemaLocation_ = 'http://www.opengis.net/wfs ' +
-    'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd';
-
-
-/**
- * Read all features from a WFS FeatureCollection.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * @return {boolean|undefined} Use interim tiles on error.
+ * @observable
+ * @api
  */
-ol.format.WFS.prototype.readFeatures;
+ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
+  return /** @type {boolean|undefined} */ (
+      this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
+};
+goog.exportProperty(
+    ol.layer.Tile.prototype,
+    'getUseInterimTilesOnError',
+    ol.layer.Tile.prototype.getUseInterimTilesOnError);
 
 
 /**
- * @inheritDoc
+ * @param {boolean|undefined} useInterimTilesOnError Use interim tiles on error.
+ * @observable
+ * @api
  */
-ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) {
-  var context = {
-    'featureType': this.featureType_,
-    'featureNS': this.featureNS_
-  };
-  goog.object.extend(context, this.getReadOptions(node,
-      goog.isDef(opt_options) ? opt_options : {}));
-  var objectStack = [context];
-  var features = ol.xml.pushParseAndPop([],
-      ol.format.GML.FEATURE_COLLECTION_PARSERS, node, objectStack);
-  if (!goog.isDef(features)) {
-    features = [];
-  }
-  return features;
+ol.layer.Tile.prototype.setUseInterimTilesOnError =
+    function(useInterimTilesOnError) {
+  this.set(
+      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
 };
+goog.exportProperty(
+    ol.layer.Tile.prototype,
+    'setUseInterimTilesOnError',
+    ol.layer.Tile.prototype.setUseInterimTilesOnError);
 
+goog.provide('ol.layer.Vector');
 
-/**
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
- * @api stable
- */
-ol.format.WFS.prototype.readTransactionResponse = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readTransactionResponseFromDocument(
-        /** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readTransactionResponseFromNode(/** @type {Node} */ (source));
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readTransactionResponseFromDocument(doc);
-  } else {
-    goog.asserts.fail();
-    return undefined;
-  }
-};
+goog.require('goog.object');
+goog.require('ol.layer.Layer');
+goog.require('ol.style.Style');
 
 
 /**
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
- * @api stable
+ * @enum {string}
  */
-ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFeatureCollectionMetadataFromDocument(
-        /** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readFeatureCollectionMetadataFromNode(
-        /** @type {Node} */ (source));
-  } else if (goog.isString(source)) {
-    var doc = ol.xml.load(source);
-    return this.readFeatureCollectionMetadataFromDocument(doc);
-  } else {
-    goog.asserts.fail();
-    return undefined;
-  }
+ol.layer.VectorProperty = {
+  RENDER_ORDER: 'renderOrder'
 };
 
 
+
 /**
- * @param {Document} doc Document.
- * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
+ * @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.format.WFS.prototype.readFeatureCollectionMetadataFromDocument =
-    function(doc) {
-  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
-  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
-    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
-      return this.readFeatureCollectionMetadataFromNode(n);
-    }
-  }
-  return undefined;
-};
+ol.layer.Vector = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ?
+      opt_options : /** @type {olx.layer.VectorOptions} */ ({});
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = {
-  'http://www.opengis.net/gml': {
-    'boundedBy': ol.xml.makeObjectPropertySetter(
-        ol.format.GML.readGeometry, 'bounds')
-  }
-};
+  var baseOptions = goog.object.clone(options);
 
+  delete baseOptions.style;
+  goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
 
-/**
- * @param {Node} node Node.
- * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
- */
-ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'FeatureCollection');
-  var result = {};
-  var value = ol.format.XSD.readNonNegativeIntegerString(
-      node.getAttribute('numberOfFeatures'));
-  goog.object.set(result, 'numberOfFeatures', value);
-  return ol.xml.pushParseAndPop(
-      /** @type {ol.format.WFS.FeatureCollectionMetadata} */ (result),
-      ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, []);
-};
+  /**
+   * @type {number}
+   * @private
+   */
+  this.renderBuffer_ = goog.isDef(options.renderBuffer) ?
+      options.renderBuffer : 100;
 
+  /**
+   * User provided style.
+   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
+   * @private
+   */
+  this.style_ = null;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'totalInserted': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger),
-    'totalUpdated': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger),
-    'totalDeleted': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger)
-  }
-};
+  /**
+   * Style function for use within the library.
+   * @type {ol.style.StyleFunction|undefined}
+   * @private
+   */
+  this.styleFunction_ = undefined;
 
+  this.setStyle(options.style);
 
-/**
- * @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);
 };
+goog.inherits(ol.layer.Vector, ol.layer.Layer);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @return {number|undefined} Render buffer.
  */
-ol.format.WFS.OGC_FID_PARSERS_ = {
-  'http://www.opengis.net/ogc': {
-    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
-      return node.getAttribute('fid');
-    })
-  }
+ol.layer.Vector.prototype.getRenderBuffer = function() {
+  return this.renderBuffer_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
+ * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
+ *     order.
  */
-ol.format.WFS.fidParser_ = function(node, objectStack) {
-  ol.xml.parse(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
+ol.layer.Vector.prototype.getRenderOrder = function() {
+  return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ (
+      this.get(ol.layer.VectorProperty.RENDER_ORDER));
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @function
+ * @return {ol.source.Vector} Source.
+ * @api stable
  */
-ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Feature': ol.format.WFS.fidParser_
-  }
-};
+ol.layer.Vector.prototype.getSource;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Insert results.
- * @private
+ * Get the style for features.  This returns whatever was passed to the `style`
+ * option at construction or to the `setStyle` method.
+ * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
+ *     Layer style.
+ * @api stable
  */
-ol.format.WFS.readInsertResults_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
+ol.layer.Vector.prototype.getStyle = function() {
+  return this.style_;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * Get the style function.
+ * @return {ol.style.StyleFunction|undefined} Layer style function.
+ * @api stable
  */
-ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'TransactionSummary': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readTransactionSummary_, 'transactionSummary'),
-    'InsertResults': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readInsertResults_, 'insertIds')
-  }
+ol.layer.Vector.prototype.getStyleFunction = function() {
+  return this.styleFunction_;
 };
 
 
 /**
- * @param {Document} doc Document.
- * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
+ *     Render order.
  */
-ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) {
-  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
-  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
-    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
-      return this.readTransactionResponseFromNode(n);
-    }
-  }
-  return undefined;
+ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
+  this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder);
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ * Set the style for features.  This can be a single style object, an array
+ * of styles, or a function that takes a feature and resolution and returns
+ * an array of styles. If it is `undefined` the default style is used. If
+ * it is `null` the layer has no style (a `null` style), so only features
+ * that have their own styles will be rendered in the layer. See
+ * {@link ol.style} for information on the default style.
+ * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined}
+ *     style Layer style.
+ * @api stable
  */
-ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'TransactionResponse');
-  return ol.xml.pushParseAndPop(
-      /** @type {ol.format.WFS.TransactionResponse} */({}),
-      ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []);
+ol.layer.Vector.prototype.setStyle = function(style) {
+  this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction;
+  this.styleFunction_ = goog.isNull(style) ?
+      undefined : ol.style.createStyleFunction(this.style_);
+  this.changed();
 };
 
+// FIXME test, especially polygons with holes and multipolygons
+// FIXME need to handle large thick features (where pixel size matters)
+// FIXME add offset and end to ol.geom.flat.transform.transform2D?
 
-/**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.WFS.QUERY_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-  }
-};
+goog.provide('ol.render.canvas.Immediate');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('ol.color');
+goog.require('ol.extent');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.has');
+goog.require('ol.render.IVectorContext');
+goog.require('ol.render.canvas');
+goog.require('ol.vec.Mat4');
 
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeFeature_ = function(node, feature, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featureNS = goog.object.get(context, 'featureNS');
-  var child = ol.xml.createElementNS(featureNS, featureType);
-  node.appendChild(child);
-  ol.format.GML.writeFeature(child, feature, objectStack);
-};
 
 
 /**
- * @param {Node} node Node.
- * @param {number|string} fid Feature identifier.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @classdesc
+ * A concrete subclass of {@link ol.render.IVectorContext} 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
+ * @implements {ol.render.IVectorContext}
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.Extent} extent Extent.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @struct
  */
-ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) {
-  var filter = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
-  var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'FeatureId');
-  filter.appendChild(child);
-  child.setAttribute('fid', fid);
-  node.appendChild(filter);
-};
+ol.render.canvas.Immediate =
+    function(context, pixelRatio, extent, transform, viewRotation) {
 
+  /**
+   * @private
+   * @type {Object.<string,
+   *        Array.<function(ol.render.canvas.Immediate)>>}
+   */
+  this.callbacksByZIndex_ = {};
 
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeDelete_ = function(node, feature, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featurePrefix = goog.object.get(context, 'featurePrefix');
-  featurePrefix = goog.isDef(featurePrefix) ? featurePrefix :
-      ol.format.WFS.featurePrefix;
-  var featureNS = goog.object.get(context, 'featureNS');
-  node.setAttribute('typeName', featurePrefix + ':' + featureType);
-  ol.xml.setAttributeNS(node, ol.format.WFS.xmlns, 'xmlns:' + featurePrefix,
-      featureNS);
-  var fid = feature.getId();
-  if (goog.isDef(fid)) {
-    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
-  }
-};
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = context;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.pixelRatio_ = pixelRatio;
 
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featurePrefix = goog.object.get(context, 'featurePrefix');
-  featurePrefix = goog.isDef(featurePrefix) ? featurePrefix :
-      ol.format.WFS.featurePrefix;
-  var featureNS = goog.object.get(context, 'featureNS');
-  node.setAttribute('typeName', featurePrefix + ':' + featureType);
-  ol.xml.setAttributeNS(node, ol.format.WFS.xmlns, 'xmlns:' + featurePrefix,
-      featureNS);
-  var fid = feature.getId();
-  if (goog.isDef(fid)) {
-    var keys = feature.getKeys();
-    var values = [];
-    for (var i = 0, ii = keys.length; i < ii; i++) {
-      var value = feature.get(keys[i]);
-      if (goog.isDef(value)) {
-        values.push({name: keys[i], value: value});
-      }
-    }
-    ol.xml.pushSerializeAndPop({node: node, srsName:
-          goog.object.get(context, 'srsName')},
-    ol.format.WFS.TRANSACTION_SERIALIZERS_,
-    ol.xml.makeSimpleNodeFactory('Property'), values,
-    objectStack);
-    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
-  }
-};
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.extent_ = extent;
 
+  /**
+   * @private
+   * @type {goog.vec.Mat4.Number}
+   */
+  this.transform_ = transform;
 
-/**
- * @param {Node} node Node.
- * @param {Object} pair Property name and value.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeProperty_ = function(node, pair, objectStack) {
-  var name = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Name');
-  node.appendChild(name);
-  ol.format.XSD.writeStringTextNode(name, pair.name);
-  if (goog.isDefAndNotNull(pair.value)) {
-    var value = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Value');
-    node.appendChild(value);
-    if (pair.value instanceof ol.geom.Geometry) {
-      ol.format.GML.writeGeometry(value, pair.value, objectStack);
-    } else {
-      ol.format.XSD.writeStringTextNode(value, pair.value);
-    }
-  }
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.viewRotation_ = viewRotation;
 
+  /**
+   * @private
+   * @type {?ol.render.canvas.FillState}
+   */
+  this.contextFillState_ = null;
 
-/**
- * @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 (goog.isDef(nativeElement.vendorId)) {
-    node.setAttribute('vendorId', nativeElement.vendorId);
-  }
-  if (goog.isDef(nativeElement.safeToIgnore)) {
-    node.setAttribute('safeToIgnore', nativeElement.safeToIgnore);
-  }
-  if (goog.isDef(nativeElement.value)) {
-    ol.format.XSD.writeStringTextNode(node, nativeElement.value);
-  }
-};
+  /**
+   * @private
+   * @type {?ol.render.canvas.StrokeState}
+   */
+  this.contextStrokeState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.render.canvas.TextState}
+   */
+  this.contextTextState_ = null;
 
-/**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
- * @private
- */
-ol.format.WFS.TRANSACTION_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_),
-    'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_),
-    'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_),
-    'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_),
-    'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_)
-  }
-};
+  /**
+   * @private
+   * @type {?ol.render.canvas.FillState}
+   */
+  this.fillState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.render.canvas.StrokeState}
+   */
+  this.strokeState_ = null;
 
-/**
- * @param {Node} node Node.
- * @param {string} featureType Feature type.
- * @param {Array.<*>} objectStack Node stack.
+  /**
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   */
+  this.image_ = null;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageAnchorX_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageAnchorY_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageHeight_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageOpacity_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageOriginX_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageOriginY_ = 0;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.imageRotateWithView_ = false;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageRotation_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageScale_ = 0;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.imageSnapToPixel_ = false;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageWidth_ = 0;
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.text_ = '';
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetX_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetY_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textRotation_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textScale_ = 0;
+
+  /**
+   * @private
+   * @type {?ol.render.canvas.FillState}
+   */
+  this.textFillState_ = null;
+
+  /**
+   * @private
+   * @type {?ol.render.canvas.StrokeState}
+   */
+  this.textStrokeState_ = null;
+
+  /**
+   * @private
+   * @type {?ol.render.canvas.TextState}
+   */
+  this.textState_ = null;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.pixelCoordinates_ = [];
+
+  /**
+   * @private
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.tmpLocalTransform_ = goog.vec.Mat4.createNumber();
+
+};
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  * @private
  */
-ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var featurePrefix = goog.object.get(context, 'featurePrefix');
-  var featureNS = goog.object.get(context, 'featureNS');
-  var propertyNames = goog.object.get(context, 'propertyNames');
-  var srsName = goog.object.get(context, 'srsName');
-  var prefix = goog.isDef(featurePrefix) ? featurePrefix + ':' : '';
-  node.setAttribute('typeName', prefix + featureType);
-  if (goog.isDef(srsName)) {
-    node.setAttribute('srsName', srsName);
+ol.render.canvas.Immediate.prototype.drawImages_ =
+    function(flatCoordinates, offset, end, stride) {
+  if (goog.isNull(this.image_)) {
+    return;
   }
-  if (goog.isDef(featureNS)) {
-    ol.xml.setAttributeNS(node, ol.format.WFS.xmlns, 'xmlns:' + featurePrefix,
-        featureNS);
+  goog.asserts.assert(offset === 0);
+  goog.asserts.assert(end == flatCoordinates.length);
+  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 item = goog.object.clone(context);
-  item.node = node;
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.QUERY_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames,
-      objectStack);
-  var bbox = goog.object.get(context, 'bbox');
-  if (goog.isDef(bbox)) {
-    var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
-    ol.format.WFS.writeOgcBBOX_(child, bbox, objectStack);
-    node.appendChild(child);
+  var rotation = this.imageRotation_;
+  if (this.imageRotateWithView_) {
+    rotation += this.viewRotation_;
+  }
+  var i, ii;
+  for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) {
+    var x = pixelCoordinates[i] - this.imageAnchorX_;
+    var y = pixelCoordinates[i + 1] - this.imageAnchorY_;
+    if (this.imageSnapToPixel_) {
+      x = (x + 0.5) | 0;
+      y = (y + 0.5) | 0;
+    }
+    if (rotation !== 0 || this.imageScale_ != 1) {
+      var centerX = x + this.imageAnchorX_;
+      var centerY = y + this.imageAnchorY_;
+      ol.vec.Mat4.makeTransform2D(localTransform,
+          centerX, centerY, this.imageScale_, this.imageScale_,
+          rotation, -centerX, -centerY);
+      context.setTransform(
+          goog.vec.Mat4.getElement(localTransform, 0, 0),
+          goog.vec.Mat4.getElement(localTransform, 1, 0),
+          goog.vec.Mat4.getElement(localTransform, 0, 1),
+          goog.vec.Mat4.getElement(localTransform, 1, 1),
+          goog.vec.Mat4.getElement(localTransform, 0, 3),
+          goog.vec.Mat4.getElement(localTransform, 1, 3));
+    }
+    context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_,
+        this.imageWidth_, this.imageHeight_, x, y,
+        this.imageWidth_, this.imageHeight_);
+  }
+  if (rotation !== 0 || this.imageScale_ != 1) {
+    context.setTransform(1, 0, 0, 1, 0, 0);
+  }
+  if (this.imageOpacity_ != 1) {
+    context.globalAlpha = alpha;
   }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {string} value PropertyName value.
- * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  * @private
  */
-ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) {
-  var property = ol.xml.createElementNS('http://www.opengis.net/ogc',
-      'PropertyName');
-  ol.format.XSD.writeStringTextNode(property, value);
-  node.appendChild(property);
+ol.render.canvas.Immediate.prototype.drawText_ =
+    function(flatCoordinates, offset, end, stride) {
+  if (goog.isNull(this.textState_) || this.text_ === '') {
+    return;
+  }
+  if (!goog.isNull(this.textFillState_)) {
+    this.setContextFillState_(this.textFillState_);
+  }
+  if (!goog.isNull(this.textStrokeState_)) {
+    this.setContextStrokeState_(this.textStrokeState_);
+  }
+  this.setContextTextState_(this.textState_);
+  goog.asserts.assert(offset === 0);
+  goog.asserts.assert(end == flatCoordinates.length);
+  var pixelCoordinates = ol.geom.flat.transform.transform2D(
+      flatCoordinates, offset, end, stride, this.transform_,
+      this.pixelCoordinates_);
+  var context = this.context_;
+  for (; offset < end; offset += stride) {
+    var x = pixelCoordinates[offset] + this.textOffsetX_;
+    var y = pixelCoordinates[offset + 1] + this.textOffsetY_;
+    if (this.textRotation_ !== 0 || this.textScale_ != 1) {
+      var localTransform = ol.vec.Mat4.makeTransform2D(this.tmpLocalTransform_,
+          x, y, this.textScale_, this.textScale_, this.textRotation_, -x, -y);
+      context.setTransform(
+          goog.vec.Mat4.getElement(localTransform, 0, 0),
+          goog.vec.Mat4.getElement(localTransform, 1, 0),
+          goog.vec.Mat4.getElement(localTransform, 0, 1),
+          goog.vec.Mat4.getElement(localTransform, 1, 1),
+          goog.vec.Mat4.getElement(localTransform, 0, 3),
+          goog.vec.Mat4.getElement(localTransform, 1, 3));
+    }
+    if (!goog.isNull(this.textStrokeState_)) {
+      context.strokeText(this.text_, x, y);
+    }
+    if (!goog.isNull(this.textFillState_)) {
+      context.fillText(this.text_, x, y);
+    }
+  }
+  if (this.textRotation_ !== 0 || this.textScale_ != 1) {
+    context.setTransform(1, 0, 0, 1, 0, 0);
+  }
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Extent} bbox Bounding box.
- * @param {Array.<*>} objectStack Node stack.
+ * @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.format.WFS.writeOgcBBOX_ = function(node, bbox, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var geometryName = goog.object.get(context, 'geometryName');
-  var bboxNode = ol.xml.createElementNS('http://www.opengis.net/ogc', 'BBOX');
-  node.appendChild(bboxNode);
-  ol.format.WFS.writeOgcPropertyName_(bboxNode, geometryName, objectStack);
-  ol.format.GML.writeGeometry(bboxNode, bbox, objectStack);
+ol.render.canvas.Immediate.prototype.moveToLineTo_ =
+    function(flatCoordinates, offset, end, stride, close) {
+  var context = this.context_;
+  var pixelCoordinates = ol.geom.flat.transform.transform2D(
+      flatCoordinates, offset, end, stride, this.transform_,
+      this.pixelCoordinates_);
+  context.moveTo(pixelCoordinates[0], pixelCoordinates[1]);
+  var i;
+  for (i = 2; i < pixelCoordinates.length; i += 2) {
+    context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]);
+  }
+  if (close) {
+    context.lineTo(pixelCoordinates[0], pixelCoordinates[1]);
+  }
+  return end;
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
  * @private
+ * @return {number} End.
  */
-ol.format.WFS.GETFEATURE_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Query': ol.xml.makeChildAppender(
-        ol.format.WFS.writeQuery_)
+ol.render.canvas.Immediate.prototype.drawRings_ =
+    function(flatCoordinates, offset, ends, stride) {
+  var context = this.context_;
+  var i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    offset = this.moveToLineTo_(
+        flatCoordinates, offset, ends[i], stride, true);
+    context.closePath(); // FIXME is this needed here?
   }
+  return offset;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<{string}>} featureTypes Feature types.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Register a function to be called for rendering at a given zIndex.  The
+ * function will be called asynchronously.  The callback will receive a
+ * reference to {@link ol.render.canvas.Immediate} context for drawing.
+ *
+ * @param {number} zIndex Z index.
+ * @param {function(ol.render.canvas.Immediate)} callback Callback.
+ * @api
  */
-ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(context));
-  var item = goog.object.clone(context);
-  item.node = node;
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.GETFEATURE_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory('Query'), featureTypes,
-      objectStack);
+ol.render.canvas.Immediate.prototype.drawAsync = function(zIndex, callback) {
+  var zIndexKey = zIndex.toString();
+  var callbacks = this.callbacksByZIndex_[zIndexKey];
+  if (goog.isDef(callbacks)) {
+    callbacks.push(callback);
+  } else {
+    this.callbacksByZIndex_[zIndexKey] = [callback];
+  }
 };
 
 
 /**
- * @param {olx.format.WFSWriteGetFeatureOptions} options Options.
- * @return {Node} Result.
- * @api stable
+ * Render a circle geometry into the canvas.  Rendering is immediate and uses
+ * the current fill and stroke styles.
+ *
+ * @param {ol.geom.Circle} circleGeometry Circle geometry.
+ * @param {ol.Feature} feature Feature,
+ * @api
  */
-ol.format.WFS.prototype.writeGetFeature = function(options) {
-  var node = ol.xml.createElementNS('http://www.opengis.net/wfs',
-      'GetFeature');
-  node.setAttribute('service', 'WFS');
-  node.setAttribute('version', '1.1.0');
-  if (goog.isDef(options)) {
-    if (goog.isDef(options.handle)) {
-      node.setAttribute('handle', options.handle);
-    }
-    if (goog.isDef(options.outputFormat)) {
-      node.setAttribute('outputFormat', options.outputFormat);
-    }
-    if (goog.isDef(options.maxFeatures)) {
-      node.setAttribute('maxFeatures', options.maxFeatures);
+ol.render.canvas.Immediate.prototype.drawCircleGeometry =
+    function(circleGeometry, feature) {
+  if (!ol.extent.intersects(this.extent_, circleGeometry.getExtent())) {
+    return;
+  }
+  if (!goog.isNull(this.fillState_) || !goog.isNull(this.strokeState_)) {
+    if (!goog.isNull(this.fillState_)) {
+      this.setContextFillState_(this.fillState_);
     }
-    if (goog.isDef(options.resultType)) {
-      node.setAttribute('resultType', options.resultType);
+    if (!goog.isNull(this.strokeState_)) {
+      this.setContextStrokeState_(this.strokeState_);
     }
-    if (goog.isDef(options.startIndex)) {
-      node.setAttribute('startIndex', options.startIndex);
+    var pixelCoordinates = ol.geom.transformSimpleGeometry2D(
+        circleGeometry, this.transform_, this.pixelCoordinates_);
+    var dx = pixelCoordinates[2] - pixelCoordinates[0];
+    var dy = pixelCoordinates[3] - pixelCoordinates[1];
+    var radius = Math.sqrt(dx * dx + dy * dy);
+    var context = this.context_;
+    context.beginPath();
+    context.arc(
+        pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI);
+    if (!goog.isNull(this.fillState_)) {
+      context.fill();
     }
-    if (goog.isDef(options.count)) {
-      node.setAttribute('count', options.count);
+    if (!goog.isNull(this.strokeState_)) {
+      context.stroke();
     }
   }
-  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
-      'xsi:schemaLocation', this.schemaLocation_);
-  var context = {
-    node: node,
-    srsName: options.srsName,
-    featureNS: goog.isDef(options.featureNS) ?
-        options.featureNS : this.featureNS_,
-    featurePrefix: options.featurePrefix,
-    geometryName: options.geometryName,
-    bbox: options.bbox,
-    propertyNames: goog.isDef(options.propertyNames) ?
-        options.propertyNames : []
-  };
-  goog.asserts.assert(goog.isArray(options.featureTypes));
-  ol.format.WFS.writeGetFeature_(node, options.featureTypes, [context]);
-  return node;
+  if (this.text_ !== '') {
+    this.drawText_(circleGeometry.getCenter(), 0, 2, 2);
+  }
 };
 
 
 /**
- * @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
+ * Render a feature into the canvas.  In order to respect the zIndex of the
+ * style this method draws asynchronously and thus *after* calls to
+ * drawXxxxGeometry have been finished, effectively drawing the feature
+ * *on top* of everything else.  You probably should be using
+ * {@link ol.FeatureOverlay} instead of calling this method directly.
+ *
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
+ * @api
  */
-ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes,
-    options) {
-  var objectStack = [];
-  var node = ol.xml.createElementNS('http://www.opengis.net/wfs',
-      'Transaction');
-  node.setAttribute('service', 'WFS');
-  node.setAttribute('version', '1.1.0');
-  var baseObj, obj;
-  if (goog.isDef(options)) {
-    baseObj = goog.isDef(options.gmlOptions) ? options.gmlOptions : {};
-    if (goog.isDef(options.handle)) {
-      node.setAttribute('handle', options.handle);
-    }
-  }
-  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
-      'xsi:schemaLocation', this.schemaLocation_);
-  if (goog.isDefAndNotNull(inserts)) {
-    obj = {node: node, featureNS: options.featureNS,
-      featureType: options.featureType, featurePrefix: options.featurePrefix};
-    goog.object.extend(obj, baseObj);
-    ol.xml.pushSerializeAndPop(obj,
-        ol.format.WFS.TRANSACTION_SERIALIZERS_,
-        ol.xml.makeSimpleNodeFactory('Insert'), inserts,
-        objectStack);
-  }
-  if (goog.isDefAndNotNull(updates)) {
-    obj = {node: node, featureNS: options.featureNS,
-      featureType: options.featureType, featurePrefix: options.featurePrefix};
-    goog.object.extend(obj, baseObj);
-    ol.xml.pushSerializeAndPop(obj,
-        ol.format.WFS.TRANSACTION_SERIALIZERS_,
-        ol.xml.makeSimpleNodeFactory('Update'), updates,
-        objectStack);
-  }
-  if (goog.isDefAndNotNull(deletes)) {
-    ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS,
-      featureType: options.featureType, featurePrefix: options.featurePrefix},
-    ol.format.WFS.TRANSACTION_SERIALIZERS_,
-    ol.xml.makeSimpleNodeFactory('Delete'), deletes,
-    objectStack);
+ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) {
+  var geometry = style.getGeometryFunction()(feature);
+  if (!goog.isDefAndNotNull(geometry) ||
+      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
+    return;
   }
-  if (goog.isDef(options.nativeElements)) {
-    ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS,
-      featureType: options.featureType, featurePrefix: options.featurePrefix},
-    ol.format.WFS.TRANSACTION_SERIALIZERS_,
-    ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements,
-    objectStack);
+  var zIndex = style.getZIndex();
+  if (!goog.isDef(zIndex)) {
+    zIndex = 0;
   }
-  return node;
+  this.drawAsync(zIndex, function(render) {
+    render.setFillStrokeStyle(style.getFill(), style.getStroke());
+    render.setImageStyle(style.getImage());
+    render.setTextStyle(style.getText());
+    var renderGeometry =
+        ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()];
+    goog.asserts.assert(goog.isDef(renderGeometry));
+    renderGeometry.call(render, geometry, null);
+  });
 };
 
 
 /**
- * Read the projection from a WFS source.
+ * Render a GeometryCollection to the canvas.  Rendering is immediate and
+ * uses the current styles appropriate for each geometry in the collection.
  *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {?ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.WFS.prototype.readProjection;
-
-
-/**
- * @inheritDoc
+ * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
+ *     collection.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.WFS.prototype.readProjectionFromDocument = function(doc) {
-  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
-  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
-    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
-      return this.readProjectionFromNode(n);
-    }
+ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry =
+    function(geometryCollectionGeometry, feature) {
+  var geometries = geometryCollectionGeometry.getGeometriesArray();
+  var i, ii;
+  for (i = 0, ii = geometries.length; i < ii; ++i) {
+    var geometry = geometries[i];
+    var geometryRenderer =
+        ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()];
+    goog.asserts.assert(goog.isDef(geometryRenderer));
+    geometryRenderer.call(this, geometry, feature);
   }
-  return null;
 };
 
 
 /**
- * @inheritDoc
+ * Render a Point geometry into the canvas.  Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.Point} pointGeometry Point geometry.
+ * @param {ol.Feature} feature Feature.
+ * @api
  */
-ol.format.WFS.prototype.readProjectionFromNode = function(node) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'FeatureCollection');
-  node = node.firstElementChild.firstElementChild;
-  if (goog.isDefAndNotNull(node)) {
-    for (var n = node.firstElementChild; !goog.isNull(n);
-        n = n.nextElementSibling) {
-      if (!(n.childNodes.length === 0 ||
-          (n.childNodes.length === 1 &&
-          n.firstChild.nodeType === 3))) {
-        var objectStack = [{}];
-        ol.format.GML.readGeometry(n, objectStack);
-        return ol.proj.get(objectStack.pop().srsName);
-      }
-    }
+ol.render.canvas.Immediate.prototype.drawPointGeometry =
+    function(pointGeometry, feature) {
+  var flatCoordinates = pointGeometry.getFlatCoordinates();
+  var stride = pointGeometry.getStride();
+  if (!goog.isNull(this.image_)) {
+    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
+  }
+  if (this.text_ !== '') {
+    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
   }
-  return null;
 };
 
-goog.provide('ol.format.WKT');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.TextFeature');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-
-
 
 /**
- * @constructor
- * @extends {ol.format.TextFeature}
- * @param {olx.format.WKTOptions=} opt_options Options.
- * @api stable
+ * Render a MultiPoint geometry  into the canvas.  Rendering is immediate and
+ * uses the current style.
+ *
+ * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry.
+ * @param {ol.Feature} feature Feature.
+ * @api
  */
-ol.format.WKT = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  goog.base(this);
-
-  /**
-   * Split GeometryCollection into multiple features.
-   * @type {boolean}
-   * @private
-   */
-  this.splitCollection_ = goog.isDef(options.splitCollection) ?
-      options.splitCollection : false;
-
+ol.render.canvas.Immediate.prototype.drawMultiPointGeometry =
+    function(multiPointGeometry, feature) {
+  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
+  var stride = multiPointGeometry.getStride();
+  if (!goog.isNull(this.image_)) {
+    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
+  }
+  if (this.text_ !== '') {
+    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
+  }
 };
-goog.inherits(ol.format.WKT, ol.format.TextFeature);
 
 
 /**
- * @const
- * @type {string}
- */
-ol.format.WKT.EMPTY = 'EMPTY';
-
-
-/**
- * @param {ol.geom.Point} geom Point geometry.
- * @return {string} Coordinates part of Point as WKT.
- * @private
+ * Render a LineString into the canvas.  Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.LineString} lineStringGeometry Line string geometry.
+ * @param {ol.Feature} feature Feature.
+ * @api
  */
-ol.format.WKT.encodePointGeometry_ = function(geom) {
-  var coordinates = geom.getCoordinates();
-  if (goog.array.isEmpty(coordinates)) {
-    return '';
+ol.render.canvas.Immediate.prototype.drawLineStringGeometry =
+    function(lineStringGeometry, feature) {
+  if (!ol.extent.intersects(this.extent_, lineStringGeometry.getExtent())) {
+    return;
+  }
+  if (!goog.isNull(this.strokeState_)) {
+    this.setContextStrokeState_(this.strokeState_);
+    var context = this.context_;
+    var flatCoordinates = lineStringGeometry.getFlatCoordinates();
+    context.beginPath();
+    this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length,
+        lineStringGeometry.getStride(), false);
+    context.stroke();
+  }
+  if (this.text_ !== '') {
+    var flatMidpoint = lineStringGeometry.getFlatMidpoint();
+    this.drawText_(flatMidpoint, 0, 2, 2);
   }
-  return coordinates[0] + ' ' + coordinates[1];
 };
 
 
 /**
- * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
- * @return {string} Coordinates part of MultiPoint as WKT.
- * @private
+ * Render a MultiLineString geometry into the canvas.  Rendering is immediate
+ * and uses the current style.
+ *
+ * @param {ol.geom.MultiLineString} multiLineStringGeometry
+ *     MultiLineString geometry.
+ * @param {ol.Feature} feature Feature.
+ * @api
  */
-ol.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]) + ')');
+ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry =
+    function(multiLineStringGeometry, feature) {
+  var geometryExtent = multiLineStringGeometry.getExtent();
+  if (!ol.extent.intersects(this.extent_, geometryExtent)) {
+    return;
+  }
+  if (!goog.isNull(this.strokeState_)) {
+    this.setContextStrokeState_(this.strokeState_);
+    var context = this.context_;
+    var flatCoordinates = multiLineStringGeometry.getFlatCoordinates();
+    var offset = 0;
+    var ends = multiLineStringGeometry.getEnds();
+    var stride = multiLineStringGeometry.getStride();
+    context.beginPath();
+    var i, ii;
+    for (i = 0, ii = ends.length; i < ii; ++i) {
+      offset = this.moveToLineTo_(
+          flatCoordinates, offset, ends[i], stride, false);
+    }
+    context.stroke();
+  }
+  if (this.text_ !== '') {
+    var flatMidpoints = multiLineStringGeometry.getFlatMidpoints();
+    this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2);
   }
-  return array.join(',');
 };
 
 
 /**
- * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
- * @return {string} Coordinates part of GeometryCollection as WKT.
- * @private
+ * Render a Polygon geometry into the canvas.  Rendering is immediate and uses
+ * the current style.
+ *
+ * @param {ol.geom.Polygon} polygonGeometry Polygon geometry.
+ * @param {ol.Feature} feature Feature.
+ * @api
  */
-ol.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]));
+ol.render.canvas.Immediate.prototype.drawPolygonGeometry =
+    function(polygonGeometry, feature) {
+  if (!ol.extent.intersects(this.extent_, polygonGeometry.getExtent())) {
+    return;
+  }
+  if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) {
+    if (!goog.isNull(this.fillState_)) {
+      this.setContextFillState_(this.fillState_);
+    }
+    if (!goog.isNull(this.strokeState_)) {
+      this.setContextStrokeState_(this.strokeState_);
+    }
+    var context = this.context_;
+    context.beginPath();
+    this.drawRings_(polygonGeometry.getOrientedFlatCoordinates(),
+        0, polygonGeometry.getEnds(), polygonGeometry.getStride());
+    if (!goog.isNull(this.fillState_)) {
+      context.fill();
+    }
+    if (!goog.isNull(this.strokeState_)) {
+      context.stroke();
+    }
+  }
+  if (this.text_ !== '') {
+    var flatInteriorPoint = polygonGeometry.getFlatInteriorPoint();
+    this.drawText_(flatInteriorPoint, 0, 2, 2);
   }
-  return array.join(',');
 };
 
 
 /**
- * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
- * @return {string} Coordinates part of LineString as WKT.
- * @private
+ * Render MultiPolygon geometry into the canvas.  Rendering is immediate and
+ * uses the current style.
+ * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
+ * @param {ol.Feature} feature Feature.
+ * @api
  */
-ol.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]);
+ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry =
+    function(multiPolygonGeometry, feature) {
+  if (!ol.extent.intersects(this.extent_, multiPolygonGeometry.getExtent())) {
+    return;
+  }
+  if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) {
+    if (!goog.isNull(this.fillState_)) {
+      this.setContextFillState_(this.fillState_);
+    }
+    if (!goog.isNull(this.strokeState_)) {
+      this.setContextStrokeState_(this.strokeState_);
+    }
+    var context = this.context_;
+    var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates();
+    var offset = 0;
+    var endss = multiPolygonGeometry.getEndss();
+    var stride = multiPolygonGeometry.getStride();
+    var i, ii;
+    for (i = 0, ii = endss.length; i < ii; ++i) {
+      var ends = endss[i];
+      context.beginPath();
+      offset = this.drawRings_(flatCoordinates, offset, ends, stride);
+      if (!goog.isNull(this.fillState_)) {
+        context.fill();
+      }
+      if (!goog.isNull(this.strokeState_)) {
+        context.stroke();
+      }
+    }
+  }
+  if (this.text_ !== '') {
+    var flatInteriorPoints = multiPolygonGeometry.getFlatInteriorPoints();
+    this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2);
   }
-  return array.join(',');
 };
 
 
 /**
- * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
- * @return {string} Coordinates part of MultiLineString as WKT.
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.encodeMultiLineStringGeometry_ = function(geom) {
-  var array = [];
-  var components = geom.getLineStrings();
-  for (var i = 0, ii = components.length; i < ii; ++i) {
-    array.push('(' + ol.format.WKT.encodeLineStringGeometry_(
-        components[i]) + ')');
-  }
-  return array.join(',');
-};
+ol.render.canvas.Immediate.prototype.drawText = goog.abstractMethod;
 
 
 /**
- * @param {ol.geom.Polygon} geom Polygon geometry.
- * @return {string} Coordinates part of Polygon as WKT.
- * @private
+ * FIXME: empty description for jsdoc
  */
-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]) + ')');
+ol.render.canvas.Immediate.prototype.flush = function() {
+  /** @type {Array.<number>} */
+  var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number);
+  goog.array.sort(zs);
+  var i, ii, callbacks, j, jj;
+  for (i = 0, ii = zs.length; i < ii; ++i) {
+    callbacks = this.callbacksByZIndex_[zs[i].toString()];
+    for (j = 0, jj = callbacks.length; j < jj; ++j) {
+      callbacks[j](this);
+    }
   }
-  return array.join(',');
 };
 
 
 /**
- * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
- * @return {string} Coordinates part of MultiPolygon as WKT.
+ * @param {ol.render.canvas.FillState} fillState Fill state.
  * @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]) + ')');
+ol.render.canvas.Immediate.prototype.setContextFillState_ =
+    function(fillState) {
+  var context = this.context_;
+  var contextFillState = this.contextFillState_;
+  if (goog.isNull(contextFillState)) {
+    context.fillStyle = fillState.fillStyle;
+    this.contextFillState_ = {
+      fillStyle: fillState.fillStyle
+    };
+  } else {
+    if (contextFillState.fillStyle != fillState.fillStyle) {
+      contextFillState.fillStyle = context.fillStyle = fillState.fillStyle;
+    }
   }
-  return array.join(',');
 };
 
 
 /**
- * Encode a geometry as WKT.
- * @param {ol.geom.Geometry} geom The geometry to encode.
- * @return {string} WKT string for the geometry.
+ * @param {ol.render.canvas.StrokeState} strokeState Stroke state.
  * @private
  */
-ol.format.WKT.encode_ = function(geom) {
-  var type = geom.getType();
-  var geometryEncoder = ol.format.WKT.GeometryEncoder_[type];
-  goog.asserts.assert(goog.isDef(geometryEncoder));
-  var enc = geometryEncoder(geom);
-  type = type.toUpperCase();
-  if (enc.length === 0) {
-    return type + ' ' + ol.format.WKT.EMPTY;
+ol.render.canvas.Immediate.prototype.setContextStrokeState_ =
+    function(strokeState) {
+  var context = this.context_;
+  var contextStrokeState = this.contextStrokeState_;
+  if (goog.isNull(contextStrokeState)) {
+    context.lineCap = strokeState.lineCap;
+    if (ol.has.CANVAS_LINE_DASH) {
+      context.setLineDash(strokeState.lineDash);
+    }
+    context.lineJoin = strokeState.lineJoin;
+    context.lineWidth = strokeState.lineWidth;
+    context.miterLimit = strokeState.miterLimit;
+    context.strokeStyle = strokeState.strokeStyle;
+    this.contextStrokeState_ = {
+      lineCap: strokeState.lineCap,
+      lineDash: strokeState.lineDash,
+      lineJoin: strokeState.lineJoin,
+      lineWidth: strokeState.lineWidth,
+      miterLimit: strokeState.miterLimit,
+      strokeStyle: strokeState.strokeStyle
+    };
+  } else {
+    if (contextStrokeState.lineCap != strokeState.lineCap) {
+      contextStrokeState.lineCap = context.lineCap = strokeState.lineCap;
+    }
+    if (ol.has.CANVAS_LINE_DASH) {
+      if (!goog.array.equals(
+          contextStrokeState.lineDash, strokeState.lineDash)) {
+        context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash);
+      }
+    }
+    if (contextStrokeState.lineJoin != strokeState.lineJoin) {
+      contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin;
+    }
+    if (contextStrokeState.lineWidth != strokeState.lineWidth) {
+      contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth;
+    }
+    if (contextStrokeState.miterLimit != strokeState.miterLimit) {
+      contextStrokeState.miterLimit = context.miterLimit =
+          strokeState.miterLimit;
+    }
+    if (contextStrokeState.strokeStyle != strokeState.strokeStyle) {
+      contextStrokeState.strokeStyle = context.strokeStyle =
+          strokeState.strokeStyle;
+    }
   }
-  return type + '(' + enc + ')';
 };
 
 
 /**
- * @const
- * @type {Object.<string, function(ol.geom.Geometry): string>}
+ * @param {ol.render.canvas.TextState} textState Text state.
  * @private
  */
-ol.format.WKT.GeometryEncoder_ = {
-  'Point': ol.format.WKT.encodePointGeometry_,
-  'LineString': ol.format.WKT.encodeLineStringGeometry_,
-  'Polygon': ol.format.WKT.encodePolygonGeometry_,
-  'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_,
-  'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_,
-  'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_,
-  'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_
+ol.render.canvas.Immediate.prototype.setContextTextState_ =
+    function(textState) {
+  var context = this.context_;
+  var contextTextState = this.contextTextState_;
+  if (goog.isNull(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;
+    }
+  }
 };
 
 
 /**
- * Parse a WKT string.
- * @param {string} wkt WKT string.
- * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined}
- *     The geometry created.
- * @private
+ * Set the fill and stroke style for subsequent draw operations.  To clear
+ * either fill or stroke styles, pass null for the appropriate parameter.
+ *
+ * @param {ol.style.Fill} fillStyle Fill style.
+ * @param {ol.style.Stroke} strokeStyle Stroke style.
+ * @api
  */
-ol.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();
+ol.render.canvas.Immediate.prototype.setFillStrokeStyle =
+    function(fillStyle, strokeStyle) {
+  if (goog.isNull(fillStyle)) {
+    this.fillState_ = null;
+  } else {
+    var fillStyleColor = fillStyle.getColor();
+    this.fillState_ = {
+      fillStyle: ol.color.asString(!goog.isNull(fillStyleColor) ?
+          fillStyleColor : ol.render.canvas.defaultFillStyle)
+    };
+  }
+  if (goog.isNull(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: goog.isDef(strokeStyleLineCap) ?
+          strokeStyleLineCap : ol.render.canvas.defaultLineCap,
+      lineDash: goog.isDefAndNotNull(strokeStyleLineDash) ?
+          strokeStyleLineDash : ol.render.canvas.defaultLineDash,
+      lineJoin: goog.isDef(strokeStyleLineJoin) ?
+          strokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
+      lineWidth: this.pixelRatio_ * (goog.isDef(strokeStyleWidth) ?
+          strokeStyleWidth : ol.render.canvas.defaultLineWidth),
+      miterLimit: goog.isDef(strokeStyleMiterLimit) ?
+          strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
+      strokeStyle: ol.color.asString(!goog.isNull(strokeStyleColor) ?
+          strokeStyleColor : ol.render.canvas.defaultStrokeStyle)
+    };
+  }
 };
 
 
 /**
- * Read a feature from a WKT source.
+ * Set the image style for subsequent draw operations.  Pass null to remove
+ * the image style.
  *
- * @function
- * @param {ArrayBuffer|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
+ * @param {ol.style.Image} imageStyle Image style.
+ * @api
  */
-ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) {
-  var geom = this.readGeometryFromText(text, opt_options);
-  if (goog.isDef(geom)) {
-    var feature = new ol.Feature();
-    feature.setGeometry(geom);
-    return feature;
+ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) {
+  if (goog.isNull(imageStyle)) {
+    this.image_ = null;
+  } else {
+    var imageAnchor = imageStyle.getAnchor();
+    // FIXME pixel ratio
+    var imageImage = imageStyle.getImage(1);
+    var imageOrigin = imageStyle.getOrigin();
+    var imageSize = imageStyle.getSize();
+    goog.asserts.assert(!goog.isNull(imageAnchor));
+    goog.asserts.assert(!goog.isNull(imageImage));
+    goog.asserts.assert(!goog.isNull(imageOrigin));
+    goog.asserts.assert(!goog.isNull(imageSize));
+    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];
   }
-  return null;
 };
 
 
 /**
- * Read all features from a WKT source.
+ * Set the text style for subsequent draw operations.  Pass null to
+ * remove the text style.
  *
- * @function
- * @param {ArrayBuffer|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 {ArrayBuffer|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
+ * @param {ol.style.Text} textStyle Text style.
+ * @api
  */
-ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) {
-  var geometry = this.parse_(text);
-  if (goog.isDef(geometry)) {
-    return /** @type {ol.geom.Geometry} */ (
-        ol.format.Feature.transformWithOptions(geometry, false, opt_options));
+ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) {
+  if (goog.isNull(textStyle)) {
+    this.text_ = '';
   } else {
-    return null;
+    var textFillStyle = textStyle.getFill();
+    if (goog.isNull(textFillStyle)) {
+      this.textFillState_ = null;
+    } else {
+      var textFillStyleColor = textFillStyle.getColor();
+      this.textFillState_ = {
+        fillStyle: ol.color.asString(!goog.isNull(textFillStyleColor) ?
+            textFillStyleColor : ol.render.canvas.defaultFillStyle)
+      };
+    }
+    var textStrokeStyle = textStyle.getStroke();
+    if (goog.isNull(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: goog.isDef(textStrokeStyleLineCap) ?
+            textStrokeStyleLineCap : ol.render.canvas.defaultLineCap,
+        lineDash: goog.isDefAndNotNull(textStrokeStyleLineDash) ?
+            textStrokeStyleLineDash : ol.render.canvas.defaultLineDash,
+        lineJoin: goog.isDef(textStrokeStyleLineJoin) ?
+            textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
+        lineWidth: goog.isDef(textStrokeStyleWidth) ?
+            textStrokeStyleWidth : ol.render.canvas.defaultLineWidth,
+        miterLimit: goog.isDef(textStrokeStyleMiterLimit) ?
+            textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
+        strokeStyle: ol.color.asString(!goog.isNull(textStrokeStyleColor) ?
+            textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle)
+      };
+    }
+    var textFont = textStyle.getFont();
+    var textOffsetX = textStyle.getOffsetX();
+    var textOffsetY = textStyle.getOffsetY();
+    var textRotation = textStyle.getRotation();
+    var textScale = textStyle.getScale();
+    var textText = textStyle.getText();
+    var textTextAlign = textStyle.getTextAlign();
+    var textTextBaseline = textStyle.getTextBaseline();
+    this.textState_ = {
+      font: goog.isDef(textFont) ?
+          textFont : ol.render.canvas.defaultFont,
+      textAlign: goog.isDef(textTextAlign) ?
+          textTextAlign : ol.render.canvas.defaultTextAlign,
+      textBaseline: goog.isDef(textTextBaseline) ?
+          textTextBaseline : ol.render.canvas.defaultTextBaseline
+    };
+    this.text_ = goog.isDef(textText) ? textText : '';
+    this.textOffsetX_ =
+        goog.isDef(textOffsetX) ? (this.pixelRatio_ * textOffsetX) : 0;
+    this.textOffsetY_ =
+        goog.isDef(textOffsetY) ? (this.pixelRatio_ * textOffsetY) : 0;
+    this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0;
+    this.textScale_ = this.pixelRatio_ * (goog.isDef(textScale) ?
+        textScale : 1);
   }
 };
 
 
 /**
- * @inheritDoc
+ * @const
+ * @private
+ * @type {Object.<ol.geom.GeometryType,
+ *                function(this: ol.render.canvas.Immediate, ol.geom.Geometry,
+ *                         Object)>}
  */
-ol.format.WKT.prototype.readProjectionFromText = function(text) {
-  return null;
+ol.render.canvas.Immediate.GEOMETRY_RENDERERS_ = {
+  'Point': ol.render.canvas.Immediate.prototype.drawPointGeometry,
+  'LineString': ol.render.canvas.Immediate.prototype.drawLineStringGeometry,
+  'Polygon': ol.render.canvas.Immediate.prototype.drawPolygonGeometry,
+  'MultiPoint': ol.render.canvas.Immediate.prototype.drawMultiPointGeometry,
+  'MultiLineString':
+      ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry,
+  'MultiPolygon': ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry,
+  'GeometryCollection':
+      ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry,
+  'Circle': ol.render.canvas.Immediate.prototype.drawCircleGeometry
 };
 
+goog.provide('ol.render.IReplayGroup');
 
-/**
- * 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;
+goog.require('goog.functions');
+goog.require('ol.render.IVectorContext');
 
 
 /**
- * @inheritDoc
+ * @enum {string}
  */
-ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
-  var geometry = feature.getGeometry();
-  if (goog.isDef(geometry)) {
-    return this.writeGeometryText(geometry, opt_options);
-  }
-  return '';
+ol.render.ReplayType = {
+  IMAGE: 'Image',
+  LINE_STRING: 'LineString',
+  POLYGON: 'Polygon',
+  TEXT: 'Text'
 };
 
 
 /**
- * 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
+ * @const
+ * @type {Array.<ol.render.ReplayType>}
  */
-ol.format.WKT.prototype.writeFeatures;
+ol.render.REPLAY_ORDER = [
+  ol.render.ReplayType.POLYGON,
+  ol.render.ReplayType.LINE_STRING,
+  ol.render.ReplayType.IMAGE,
+  ol.render.ReplayType.TEXT
+];
+
 
 
 /**
- * @inheritDoc
+ * @interface
  */
-ol.format.WKT.prototype.writeFeaturesText = function(features, opt_options) {
-  if (features.length == 1) {
-    return this.writeFeatureText(features[0], opt_options);
-  }
-  var geometries = [];
-  for (var i = 0, ii = features.length; i < ii; ++i) {
-    geometries.push(features[i].getGeometry());
-  }
-  var collection = new ol.geom.GeometryCollection(geometries);
-  return this.writeGeometryText(collection, opt_options);
+ol.render.IReplayGroup = function() {
 };
 
 
 /**
- * Write a single geometry as a WKT string.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @return {string} WKT string.
- * @api stable
+ * @param {number|undefined} zIndex Z index.
+ * @param {ol.render.ReplayType} replayType Replay type.
+ * @return {ol.render.IVectorContext} Replay.
  */
-ol.format.WKT.prototype.writeGeometry;
+ol.render.IReplayGroup.prototype.getReplay = function(zIndex, replayType) {
+};
 
 
 /**
- * @inheritDoc
+ * @return {boolean} Is empty.
  */
-ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) {
-  return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(geometry, true, opt_options)));
+ol.render.IReplayGroup.prototype.isEmpty = function() {
 };
 
+// FIXME add option to apply snapToPixel to all coordinates?
+// FIXME can eliminate empty set styles and strokes (when all geoms skipped)
 
-/**
- * @typedef {{type: number, value: (number|string|undefined), position: number}}
- */
-ol.format.WKT.Token;
+goog.provide('ol.render.canvas.Replay');
+goog.provide('ol.render.canvas.ReplayGroup');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('ol.array');
+goog.require('ol.color');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.extent.Relationship');
+goog.require('ol.geom.flat.simplify');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.has');
+goog.require('ol.render.IReplayGroup');
+goog.require('ol.render.IVectorContext');
+goog.require('ol.render.canvas');
+goog.require('ol.vec.Mat4');
 
 
 /**
- * @const
  * @enum {number}
  */
-ol.format.WKT.TokenType = {
-  TEXT: 1,
-  LEFT_PAREN: 2,
-  RIGHT_PAREN: 3,
-  NUMBER: 4,
-  COMMA: 5,
-  EOF: 6
+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
 };
 
 
 
 /**
- * Class to tokenize a WKT string.
- * @param {string} wkt WKT string.
  * @constructor
+ * @implements {ol.render.IVectorContext}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
  * @protected
+ * @struct
  */
-ol.format.WKT.Lexer = function(wkt) {
+ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) {
 
   /**
-   * @type {string}
+   * @protected
+   * @type {number}
    */
-  this.wkt = wkt;
+  this.tolerance = tolerance;
 
   /**
-   * @type {number}
-   * @private
+   * @protected
+   * @type {ol.Extent}
    */
-  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 = goog.isDef(opt_decimal) ? opt_decimal : false;
-  return c >= '0' && c <= '9' || c == '.' && !decimal;
-};
-
-
-/**
- * @param {string} c Character.
- * @return {boolean} Whether the character is whitespace.
- * @private
- */
-ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
-  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
-};
-
-
-/**
- * @return {string} Next string character.
- * @private
- */
-ol.format.WKT.Lexer.prototype.nextChar_ = function() {
-  return this.wkt.charAt(++this.index_);
-};
-
-
-/**
- * Fetch and return the next token.
- * @return {!ol.format.WKT.Token} Next string token.
- */
-ol.format.WKT.Lexer.prototype.nextToken = function() {
-  var c = this.nextChar_();
-  var token = {position: this.index_, value: c};
-
-  if (c == '(') {
-    token.type = ol.format.WKT.TokenType.LEFT_PAREN;
-  } else if (c == ',') {
-    token.type = ol.format.WKT.TokenType.COMMA;
-  } else if (c == ')') {
-    token.type = ol.format.WKT.TokenType.RIGHT_PAREN;
-  } else if (this.isNumeric_(c) || c == '-') {
-    token.type = ol.format.WKT.TokenType.NUMBER;
-    token.value = this.readNumber_();
-  } else if (this.isAlpha_(c)) {
-    token.type = ol.format.WKT.TokenType.TEXT;
-    token.value = this.readText_();
-  } else if (this.isWhiteSpace_(c)) {
-    return this.nextToken();
-  } else if (c === '') {
-    token.type = ol.format.WKT.TokenType.EOF;
-  } else {
-    throw new Error('Unexpected character: ' + c);
-  }
-
-  return token;
-};
-
+  this.maxExtent = maxExtent;
 
-/**
- * @return {number} Numeric token value.
- * @private
- */
-ol.format.WKT.Lexer.prototype.readNumber_ = function() {
-  var c, index = this.index_;
-  var decimal = false;
-  do {
-    if (c == '.') {
-      decimal = true;
-    }
-    c = this.nextChar_();
-  } while (this.isNumeric_(c, decimal));
-  return parseFloat(this.wkt.substring(index, this.index_--));
-};
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.maxLineWidth = 0;
 
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.resolution = resolution;
 
-/**
- * @return {string} String token value.
- * @private
- */
-ol.format.WKT.Lexer.prototype.readText_ = function() {
-  var c, index = this.index_;
-  do {
-    c = this.nextChar_();
-  } while (this.isAlpha_(c));
-  return this.wkt.substring(index, this.index_--).toUpperCase();
-};
+  /**
+   * @private
+   * @type {Array.<*>}
+   */
+  this.beginGeometryInstruction1_ = null;
 
+  /**
+   * @private
+   * @type {Array.<*>}
+   */
+  this.beginGeometryInstruction2_ = null;
 
+  /**
+   * @protected
+   * @type {Array.<*>}
+   */
+  this.instructions = [];
 
-/**
- * Class to parse the tokens from the WKT string.
- * @param {ol.format.WKT.Lexer} lexer
- * @constructor
- * @protected
- */
-ol.format.WKT.Parser = function(lexer) {
+  /**
+   * @protected
+   * @type {Array.<number>}
+   */
+  this.coordinates = [];
 
   /**
-   * @type {ol.format.WKT.Lexer}
    * @private
+   * @type {goog.vec.Mat4.Number}
    */
-  this.lexer_ = lexer;
+  this.renderedTransform_ = goog.vec.Mat4.createNumber();
+
+  /**
+   * @protected
+   * @type {Array.<*>}
+   */
+  this.hitDetectionInstructions = [];
 
   /**
-   * @type {ol.format.WKT.Token}
    * @private
+   * @type {Array.<number>}
    */
-  this.token_;
+  this.pixelCoordinates_ = [];
 
   /**
-   * @type {number}
    * @private
+   * @type {!goog.vec.Mat4.Number}
    */
-  this.dimension_ = 2;
+  this.tmpLocalTransform_ = goog.vec.Mat4.createNumber();
+
 };
 
 
 /**
- * Fetch the next token form the lexer and replace the active token.
- * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {boolean} close Close.
+ * @protected
+ * @return {number} My end.
  */
-ol.format.WKT.Parser.prototype.consume_ = function() {
-  this.token_ = this.lexer_.nextToken();
-};
+ol.render.canvas.Replay.prototype.appendFlatCoordinates =
+    function(flatCoordinates, offset, end, stride, close) {
+
+  var myEnd = this.coordinates.length;
+  var extent = this.getBufferedMaxExtent();
+  var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
+  var nextCoord = [NaN, NaN];
+  var skipped = true;
 
+  var i, lastRel, nextRel;
+  for (i = offset + stride; i < end; i += stride) {
+    nextCoord[0] = flatCoordinates[i];
+    nextCoord[1] = flatCoordinates[i + 1];
+    nextRel = ol.extent.coordinateRelationship(extent, nextCoord);
+    if (nextRel !== lastRel) {
+      if (skipped) {
+        this.coordinates[myEnd++] = lastCoord[0];
+        this.coordinates[myEnd++] = lastCoord[1];
+      }
+      this.coordinates[myEnd++] = nextCoord[0];
+      this.coordinates[myEnd++] = nextCoord[1];
+      skipped = false;
+    } else if (nextRel === ol.extent.Relationship.INTERSECTING) {
+      this.coordinates[myEnd++] = nextCoord[0];
+      this.coordinates[myEnd++] = nextCoord[1];
+      skipped = false;
+    } else {
+      skipped = true;
+    }
+    lastCoord[0] = nextCoord[0];
+    lastCoord[1] = nextCoord[1];
+    lastRel = nextRel;
+  }
 
-/**
- * If the given type matches the current token, consume it.
- * @param {ol.format.WKT.TokenType.<number>} type Token type.
- * @return {boolean} Whether the token matches the given type.
- */
-ol.format.WKT.Parser.prototype.match = function(type) {
-  var isMatch = this.token_.type == type;
-  if (isMatch) {
-    this.consume_();
+  // handle case where there is only one point to append
+  if (i === offset + stride) {
+    this.coordinates[myEnd++] = lastCoord[0];
+    this.coordinates[myEnd++] = lastCoord[1];
   }
-  return isMatch;
+
+  if (close) {
+    this.coordinates[myEnd++] = flatCoordinates[offset];
+    this.coordinates[myEnd++] = flatCoordinates[offset + 1];
+  }
+  return myEnd;
 };
 
 
 /**
- * Try to parse the tokens provided by the lexer.
- * @return {ol.geom.Geometry|ol.geom.GeometryCollection} The geometry.
+ * @protected
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.WKT.Parser.prototype.parse = function() {
-  this.consume_();
-  var geometry = this.parseGeometry_();
-  goog.asserts.assert(this.token_.type == ol.format.WKT.TokenType.EOF);
-  return geometry;
+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_);
 };
 
 
 /**
- * @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 (!goog.isDef(parser) || !goog.isDef(ctor)) {
-        throw new Error('Invalid geometry type: ' + geomType);
-      }
-      var coordinates = parser.call(this);
-      return new ctor(coordinates);
-    }
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {Array.<*>} instructions Instructions array.
+ * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.canvas.Replay.prototype.replay_ = function(
+    context, pixelRatio, transform, viewRotation, skippedFeaturesHash,
+    instructions, featureCallback) {
+  /** @type {Array.<number>} */
+  var pixelCoordinates;
+  if (ol.vec.Mat4.equals2D(transform, this.renderedTransform_)) {
+    pixelCoordinates = this.pixelCoordinates_;
+  } else {
+    pixelCoordinates = ol.geom.flat.transform.transform2D(
+        this.coordinates, 0, this.coordinates.length, 2,
+        transform, this.pixelCoordinates_);
+    goog.vec.Mat4.setFromArray(this.renderedTransform_, transform);
+    goog.asserts.assert(pixelCoordinates === this.pixelCoordinates_);
   }
-  this.raiseError_();
-};
+  var i = 0; // instruction index
+  var ii = instructions.length; // end of instructions
+  var d = 0; // data index
+  var dd; // end of per-instruction data
+  var localTransform = this.tmpLocalTransform_;
+  while (i < ii) {
+    var instruction = instructions[i];
+    var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
+    var feature, fill, stroke, text, x, y;
+    switch (type) {
+      case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
+        feature = /** @type {ol.Feature} */ (instruction[1]);
+        var featureUid = goog.getUid(feature).toString();
+        if (!goog.isDef(goog.object.get(skippedFeaturesHash, featureUid))) {
+          ++i;
+        } else {
+          i = /** @type {number} */ (instruction[2]);
+        }
+        break;
+      case ol.render.canvas.Instruction.BEGIN_PATH:
+        context.beginPath();
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.CIRCLE:
+        goog.asserts.assert(goog.isNumber(instruction[1]));
+        d = /** @type {number} */ (instruction[1]);
+        var x1 = pixelCoordinates[d];
+        var y1 = pixelCoordinates[d + 1];
+        var x2 = pixelCoordinates[d + 2];
+        var y2 = pixelCoordinates[d + 3];
+        var dx = x2 - x1;
+        var dy = y2 - y1;
+        var r = Math.sqrt(dx * dx + dy * dy);
+        context.arc(x1, y1, r, 0, 2 * Math.PI, true);
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.CLOSE_PATH:
+        context.closePath();
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.DRAW_IMAGE:
+        goog.asserts.assert(goog.isNumber(instruction[1]));
+        d = /** @type {number} */ (instruction[1]);
+        goog.asserts.assert(goog.isNumber(instruction[2]));
+        dd = /** @type {number} */ (instruction[2]);
+        var image =  /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */
+            (instruction[3]);
+        // Remaining arguments in DRAW_IMAGE are in alphabetical order
+        var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio;
+        var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio;
+        var height = /** @type {number} */ (instruction[6]);
+        var opacity = /** @type {number} */ (instruction[7]);
+        var originX = /** @type {number} */ (instruction[8]);
+        var originY = /** @type {number} */ (instruction[9]);
+        var rotateWithView = /** @type {boolean} */ (instruction[10]);
+        var rotation = /** @type {number} */ (instruction[11]);
+        var scale = /** @type {number} */ (instruction[12]);
+        var snapToPixel = /** @type {boolean} */ (instruction[13]);
+        var width = /** @type {number} */ (instruction[14]);
+        if (rotateWithView) {
+          rotation += viewRotation;
+        }
+        for (; d < dd; d += 2) {
+          x = pixelCoordinates[d] - anchorX;
+          y = pixelCoordinates[d + 1] - anchorY;
+          if (snapToPixel) {
+            x = (x + 0.5) | 0;
+            y = (y + 0.5) | 0;
+          }
+          if (scale != 1 || rotation !== 0) {
+            var centerX = x + anchorX;
+            var centerY = y + anchorY;
+            ol.vec.Mat4.makeTransform2D(
+                localTransform, centerX, centerY, scale, scale,
+                rotation, -centerX, -centerY);
+            context.setTransform(
+                goog.vec.Mat4.getElement(localTransform, 0, 0),
+                goog.vec.Mat4.getElement(localTransform, 1, 0),
+                goog.vec.Mat4.getElement(localTransform, 0, 1),
+                goog.vec.Mat4.getElement(localTransform, 1, 1),
+                goog.vec.Mat4.getElement(localTransform, 0, 3),
+                goog.vec.Mat4.getElement(localTransform, 1, 3));
+          }
+          var alpha = context.globalAlpha;
+          if (opacity != 1) {
+            context.globalAlpha = alpha * opacity;
+          }
 
+          context.drawImage(image, originX, originY, width, height,
+              x, y, width * pixelRatio, height * pixelRatio);
 
-/**
- * @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;
+          if (opacity != 1) {
+            context.globalAlpha = alpha;
+          }
+          if (scale != 1 || rotation !== 0) {
+            context.setTransform(1, 0, 0, 1, 0, 0);
+          }
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.DRAW_TEXT:
+        goog.asserts.assert(goog.isNumber(instruction[1]));
+        d = /** @type {number} */ (instruction[1]);
+        goog.asserts.assert(goog.isNumber(instruction[2]));
+        dd = /** @type {number} */ (instruction[2]);
+        goog.asserts.assert(goog.isString(instruction[3]));
+        text = /** @type {string} */ (instruction[3]);
+        goog.asserts.assert(goog.isNumber(instruction[4]));
+        var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio;
+        goog.asserts.assert(goog.isNumber(instruction[5]));
+        var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio;
+        goog.asserts.assert(goog.isNumber(instruction[6]));
+        rotation = /** @type {number} */ (instruction[6]);
+        goog.asserts.assert(goog.isNumber(instruction[7]));
+        scale = /** @type {number} */ (instruction[7]) * pixelRatio;
+        goog.asserts.assert(goog.isBoolean(instruction[8]));
+        fill = /** @type {boolean} */ (instruction[8]);
+        goog.asserts.assert(goog.isBoolean(instruction[9]));
+        stroke = /** @type {boolean} */ (instruction[9]);
+        for (; d < dd; d += 2) {
+          x = pixelCoordinates[d] + offsetX;
+          y = pixelCoordinates[d + 1] + offsetY;
+          if (scale != 1 || rotation !== 0) {
+            ol.vec.Mat4.makeTransform2D(
+                localTransform, x, y, scale, scale, rotation, -x, -y);
+            context.setTransform(
+                goog.vec.Mat4.getElement(localTransform, 0, 0),
+                goog.vec.Mat4.getElement(localTransform, 1, 0),
+                goog.vec.Mat4.getElement(localTransform, 0, 1),
+                goog.vec.Mat4.getElement(localTransform, 1, 1),
+                goog.vec.Mat4.getElement(localTransform, 0, 3),
+                goog.vec.Mat4.getElement(localTransform, 1, 3));
+          }
+          if (stroke) {
+            context.strokeText(text, x, y);
+          }
+          if (fill) {
+            context.fillText(text, x, y);
+          }
+          if (scale != 1 || rotation !== 0) {
+            context.setTransform(1, 0, 0, 1, 0, 0);
+          }
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.END_GEOMETRY:
+        if (goog.isDef(featureCallback)) {
+          feature = /** @type {ol.Feature} */ (instruction[1]);
+          var result = featureCallback(feature);
+          if (result) {
+            return result;
+          }
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.FILL:
+        context.fill();
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
+        goog.asserts.assert(goog.isNumber(instruction[1]));
+        d = /** @type {number} */ (instruction[1]);
+        goog.asserts.assert(goog.isNumber(instruction[2]));
+        dd = /** @type {number} */ (instruction[2]);
+        context.moveTo(pixelCoordinates[d], pixelCoordinates[d + 1]);
+        for (d += 2; d < dd; d += 2) {
+          context.lineTo(pixelCoordinates[d], pixelCoordinates[d + 1]);
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.SET_FILL_STYLE:
+        goog.asserts.assert(goog.isString(instruction[1]));
+        context.fillStyle = /** @type {string} */ (instruction[1]);
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
+        goog.asserts.assert(goog.isString(instruction[1]));
+        goog.asserts.assert(goog.isNumber(instruction[2]));
+        goog.asserts.assert(goog.isString(instruction[3]));
+        goog.asserts.assert(goog.isString(instruction[4]));
+        goog.asserts.assert(goog.isNumber(instruction[5]));
+        goog.asserts.assert(!goog.isNull(instruction[6]));
+        var usePixelRatio = goog.isDef(instruction[7]) ? instruction[7] : true;
+        var lineWidth = /** @type {number} */ (instruction[2]);
+        context.strokeStyle = /** @type {string} */ (instruction[1]);
+        context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth;
+        context.lineCap = /** @type {string} */ (instruction[3]);
+        context.lineJoin = /** @type {string} */ (instruction[4]);
+        context.miterLimit = /** @type {number} */ (instruction[5]);
+        if (ol.has.CANVAS_LINE_DASH) {
+          context.setLineDash(/** @type {Array.<number>} */ (instruction[6]));
+        }
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.SET_TEXT_STYLE:
+        goog.asserts.assert(goog.isString(instruction[1]));
+        goog.asserts.assert(goog.isString(instruction[2]));
+        goog.asserts.assert(goog.isString(instruction[3]));
+        context.font = /** @type {string} */ (instruction[1]);
+        context.textAlign = /** @type {string} */ (instruction[2]);
+        context.textBaseline = /** @type {string} */ (instruction[3]);
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.STROKE:
+        context.stroke();
+        ++i;
+        break;
+      default:
+        goog.asserts.fail();
+        ++i; // consume the instruction anyway, to avoid an infinite loop
+        break;
     }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
   }
-  this.raiseError_();
+  // assert that all instructions were consumed
+  goog.asserts.assert(i == instructions.length);
+  return undefined;
 };
 
 
 /**
- * @return {Array.<number>} All values in a point.
- * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip
  */
-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;
-  }
-  this.raiseError_();
+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);
 };
 
 
 /**
- * @return {!Array.<!Array.<number>>} All points in a linestring.
- * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip
+ * @param {function(ol.Feature): T=} opt_featureCallback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
  */
-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 [];
-  }
-  this.raiseError_();
+ol.render.canvas.Replay.prototype.replayHitDetection = function(
+    context, transform, viewRotation, skippedFeaturesHash,
+    opt_featureCallback) {
+  var instructions = this.hitDetectionInstructions;
+  return this.replay_(context, 1, transform, viewRotation,
+      skippedFeaturesHash, instructions, opt_featureCallback);
 };
 
 
 /**
- * @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;
+ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions_ =
+    function() {
+  var hitDetectionInstructions = this.hitDetectionInstructions;
+  // step 1 - reverse array
+  hitDetectionInstructions.reverse();
+  // step 2 - reverse instructions within geometry blocks
+  var i;
+  var n = hitDetectionInstructions.length;
+  var instruction;
+  var type;
+  var begin = -1;
+  for (i = 0; i < n; ++i) {
+    instruction = hitDetectionInstructions[i];
+    type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
+    if (type == ol.render.canvas.Instruction.END_GEOMETRY) {
+      goog.asserts.assert(begin == -1);
+      begin = i;
+    } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) {
+      instruction[2] = i;
+      goog.asserts.assert(begin >= 0);
+      ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i);
+      begin = -1;
     }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
   }
-  this.raiseError_();
 };
 
 
 /**
- * @return {!Array.<!Array.<number>>} All points in a multipoint.
- * @private
+ * @inheritDoc
  */
-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 [];
-  }
-  this.raiseError_();
-};
+ol.render.canvas.Replay.prototype.drawAsync = goog.abstractMethod;
 
 
 /**
- * @return {!Array.<!Array.<number>>} All linestring points
- *                                        in a multilinestring.
- * @private
+ * @inheritDoc
  */
-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 [];
-  }
-  this.raiseError_();
-};
+ol.render.canvas.Replay.prototype.drawCircleGeometry = goog.abstractMethod;
 
 
 /**
- * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon.
- * @private
+ * @inheritDoc
  */
-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 [];
-  }
-  this.raiseError_();
-};
+ol.render.canvas.Replay.prototype.drawFeature = goog.abstractMethod;
 
 
 /**
- * @return {!Array.<number>} A point.
- * @private
+ * @inheritDoc
  */
-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;
-  }
-  this.raiseError_();
-};
+ol.render.canvas.Replay.prototype.drawGeometryCollectionGeometry =
+    goog.abstractMethod;
 
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Parser.prototype.parsePointList_ = function() {
-  var coordinates = [this.parsePoint_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parsePoint_());
-  }
-  return coordinates;
-};
+ol.render.canvas.Replay.prototype.drawLineStringGeometry = goog.abstractMethod;
 
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Parser.prototype.parsePointTextList_ = function() {
-  var coordinates = [this.parsePointText_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parsePointText_());
-  }
-  return coordinates;
-};
+ol.render.canvas.Replay.prototype.drawMultiLineStringGeometry =
+    goog.abstractMethod;
 
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() {
-  var coordinates = [this.parseLineStringText_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parseLineStringText_());
-  }
-  return coordinates;
-};
+ol.render.canvas.Replay.prototype.drawPointGeometry = goog.abstractMethod;
 
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() {
-  var coordinates = [this.parsePolygonText_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parsePolygonText_());
-  }
-  return coordinates;
-};
+ol.render.canvas.Replay.prototype.drawMultiPointGeometry = goog.abstractMethod;
 
 
 /**
- * @return {boolean} Whether the token implies an empty geometry.
- * @private
+ * @inheritDoc
  */
-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;
-};
+ol.render.canvas.Replay.prototype.drawPolygonGeometry = goog.abstractMethod;
 
 
 /**
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Parser.prototype.raiseError_ = function() {
-  throw new Error('Unexpected `' + this.token_.value +
-      '` at position ' + this.token_.position +
-      ' in `' + this.lexer_.wkt + '`');
-};
+ol.render.canvas.Replay.prototype.drawMultiPolygonGeometry =
+    goog.abstractMethod;
 
 
 /**
- * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout.<string>=)}
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Parser.GeometryConstructor_ = {
-  'POINT': ol.geom.Point,
-  'LINESTRING': ol.geom.LineString,
-  'POLYGON': ol.geom.Polygon,
-  'MULTIPOINT': ol.geom.MultiPoint,
-  'MULTILINESTRING': ol.geom.MultiLineString,
-  'MULTIPOLYGON': ol.geom.MultiPolygon
-};
+ol.render.canvas.Replay.prototype.drawText = goog.abstractMethod;
 
 
 /**
- * @enum {(function(): Array)}
- * @private
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.Feature} feature Feature.
  */
-ol.format.WKT.Parser.GeometryParser_ = {
-  'POINT': ol.format.WKT.Parser.prototype.parsePointText_,
-  'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_,
-  'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_,
-  'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_,
-  'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_,
-  'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_
+ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) {
+  goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction1_));
+  this.beginGeometryInstruction1_[2] = this.instructions.length;
+  this.beginGeometryInstruction1_ = null;
+  goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction2_));
+  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);
 };
 
-goog.provide('ol.format.WMSCapabilities');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom.NodeType');
-goog.require('goog.object');
-goog.require('goog.string');
-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
+ * FIXME empty description for jsdoc
  */
-ol.format.WMSCapabilities = function() {
+ol.render.canvas.Replay.prototype.finish = goog.nullFunction;
 
-  goog.base(this);
 
-  /**
-   * @type {string|undefined}
-   */
-  this.version = undefined;
+/**
+ * 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.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
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.prototype.read;
+ol.render.canvas.Replay.prototype.setFillStrokeStyle = goog.abstractMethod;
 
 
 /**
- * @param {Document} doc Document.
- * @return {Object} WMS Capability object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) {
-  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
-  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
-    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
-      return this.readFromNode(n);
-    }
-  }
-  return null;
-};
+ol.render.canvas.Replay.prototype.setImageStyle = goog.abstractMethod;
 
 
 /**
- * @param {Node} node Node.
- * @return {Object} WMS Capability object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.prototype.readFromNode = function(node) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'WMS_Capabilities' ||
-      node.localName == 'WMT_MS_Capabilities');
-  this.version = goog.string.trim(node.getAttribute('version'));
-  goog.asserts.assertString(this.version);
-  var wmsCapabilityObject = ol.xml.pushParseAndPop({
-    'version': this.version
-  }, ol.format.WMSCapabilities.PARSERS_, node, []);
-  return goog.isDef(wmsCapabilityObject) ? wmsCapabilityObject : null;
-};
+ol.render.canvas.Replay.prototype.setTextStyle = goog.abstractMethod;
+
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Attribution object.
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @protected
+ * @struct
  */
-ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Attribution');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack);
-};
+ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution) {
 
+  goog.base(this, tolerance, maxExtent, resolution);
 
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object} Bounding box object.
- */
-ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'BoundingBox');
+  /**
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   */
+  this.hitDetectionImage_ = null;
 
-  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'))
-  ];
+  /**
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   */
+  this.image_ = null;
 
-  var resolutions = [
-    ol.format.XSD.readDecimalString(node.getAttribute('resx')),
-    ol.format.XSD.readDecimalString(node.getAttribute('resy'))
-  ];
+  /**
+   * @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;
 
-  return {
-    'crs': node.getAttribute('CRS'),
-    'extent': extent,
-    'res': resolutions
-  };
 };
+goog.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay);
 
 
 /**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.Extent|undefined} Bounding box object.
+ * @return {number} My end.
  */
-ol.format.WMSCapabilities.readEXGeographicBoundingBox_ =
-    function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'EX_GeographicBoundingBox');
-  var geographicBoundingBox = ol.xml.pushParseAndPop(
-      {},
-      ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_,
-      node, objectStack);
-  if (!goog.isDef(geographicBoundingBox)) {
-    return undefined;
-  }
-  var westBoundLongitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'westBoundLongitude'));
-  var southBoundLatitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'southBoundLatitude'));
-  var eastBoundLongitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'eastBoundLongitude'));
-  var northBoundLatitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'northBoundLatitude'));
-  if (!goog.isDef(westBoundLongitude) || !goog.isDef(southBoundLatitude) ||
-      !goog.isDef(eastBoundLongitude) || !goog.isDef(northBoundLatitude)) {
-    return undefined;
-  }
-  return [
-    westBoundLongitude, southBoundLatitude,
-    eastBoundLongitude, northBoundLatitude
-  ];
+ol.render.canvas.ImageReplay.prototype.drawCoordinates_ =
+    function(flatCoordinates, offset, end, stride) {
+  return this.appendFlatCoordinates(
+      flatCoordinates, offset, end, stride, false);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Capability object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Capability');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack);
+ol.render.canvas.ImageReplay.prototype.drawPointGeometry =
+    function(pointGeometry, feature) {
+  if (goog.isNull(this.image_)) {
+    return;
+  }
+  goog.asserts.assert(goog.isDef(this.anchorX_));
+  goog.asserts.assert(goog.isDef(this.anchorY_));
+  goog.asserts.assert(goog.isDef(this.height_));
+  goog.asserts.assert(goog.isDef(this.opacity_));
+  goog.asserts.assert(goog.isDef(this.originX_));
+  goog.asserts.assert(goog.isDef(this.originY_));
+  goog.asserts.assert(goog.isDef(this.rotateWithView_));
+  goog.asserts.assert(goog.isDef(this.rotation_));
+  goog.asserts.assert(goog.isDef(this.scale_));
+  goog.asserts.assert(goog.isDef(this.width_));
+  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);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Service object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readService_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Service');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack);
+ol.render.canvas.ImageReplay.prototype.drawMultiPointGeometry =
+    function(multiPointGeometry, feature) {
+  if (goog.isNull(this.image_)) {
+    return;
+  }
+  goog.asserts.assert(goog.isDef(this.anchorX_));
+  goog.asserts.assert(goog.isDef(this.anchorY_));
+  goog.asserts.assert(goog.isDef(this.height_));
+  goog.asserts.assert(goog.isDef(this.opacity_));
+  goog.asserts.assert(goog.isDef(this.originX_));
+  goog.asserts.assert(goog.isDef(this.originY_));
+  goog.asserts.assert(goog.isDef(this.rotateWithView_));
+  goog.asserts.assert(goog.isDef(this.rotation_));
+  goog.asserts.assert(goog.isDef(this.scale_));
+  goog.asserts.assert(goog.isDef(this.width_));
+  this.beginGeometry(multiPointGeometry, feature);
+  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
+  var stride = multiPointGeometry.getStride();
+  var myBegin = this.coordinates.length;
+  var myEnd = this.drawCoordinates_(
+      flatCoordinates, 0, flatCoordinates.length, stride);
+  this.instructions.push([
+    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
+    // Remaining arguments to DRAW_IMAGE are in alphabetical order
+    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
+    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
+    this.scale_, this.snapToPixel_, this.width_
+  ]);
+  this.hitDetectionInstructions.push([
+    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
+    this.hitDetectionImage_,
+    // Remaining arguments to DRAW_IMAGE are in alphabetical order
+    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
+    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
+    this.scale_, this.snapToPixel_, this.width_
+  ]);
+  this.endGeometry(multiPointGeometry, feature);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact information object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readContactInformation_ =
-    function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ContactInformation');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_,
-      node, objectStack);
+ol.render.canvas.ImageReplay.prototype.finish = function() {
+  this.reverseHitDetectionInstructions_();
+  // FIXME this doesn't really protect us against further calls to draw*Geometry
+  this.anchorX_ = undefined;
+  this.anchorY_ = undefined;
+  this.hitDetectionImage_ = null;
+  this.image_ = null;
+  this.height_ = undefined;
+  this.scale_ = undefined;
+  this.opacity_ = undefined;
+  this.originX_ = undefined;
+  this.originY_ = undefined;
+  this.rotateWithView_ = undefined;
+  this.rotation_ = undefined;
+  this.snapToPixel_ = undefined;
+  this.width_ = undefined;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact person object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readContactPersonPrimary_ =
-    function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ContactPersonPrimary');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_,
-      node, objectStack);
+ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
+  goog.asserts.assert(!goog.isNull(imageStyle));
+  var anchor = imageStyle.getAnchor();
+  goog.asserts.assert(!goog.isNull(anchor));
+  var size = imageStyle.getSize();
+  goog.asserts.assert(!goog.isNull(size));
+  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
+  goog.asserts.assert(!goog.isNull(hitDetectionImage));
+  var image = imageStyle.getImage(1);
+  goog.asserts.assert(!goog.isNull(image));
+  var origin = imageStyle.getOrigin();
+  goog.asserts.assert(!goog.isNull(origin));
+  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];
 };
 
 
+
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact address object.
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @protected
+ * @struct
  */
-ol.format.WMSCapabilities.readContactAddress_ =
-    function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'ContactAddress');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_,
-      node, objectStack);
+ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution) {
+
+  goog.base(this, tolerance, maxExtent, resolution);
+
+  /**
+   * @private
+   * @type {{currentStrokeStyle: (string|undefined),
+   *         currentLineCap: (string|undefined),
+   *         currentLineDash: Array.<number>,
+   *         currentLineJoin: (string|undefined),
+   *         currentLineWidth: (number|undefined),
+   *         currentMiterLimit: (number|undefined),
+   *         lastStroke: number,
+   *         strokeStyle: (string|undefined),
+   *         lineCap: (string|undefined),
+   *         lineDash: Array.<number>,
+   *         lineJoin: (string|undefined),
+   *         lineWidth: (number|undefined),
+   *         miterLimit: (number|undefined)}|null}
+   */
+  this.state_ = {
+    currentStrokeStyle: undefined,
+    currentLineCap: undefined,
+    currentLineDash: null,
+    currentLineJoin: undefined,
+    currentLineWidth: undefined,
+    currentMiterLimit: undefined,
+    lastStroke: 0,
+    strokeStyle: undefined,
+    lineCap: undefined,
+    lineDash: null,
+    lineJoin: undefined,
+    lineWidth: undefined,
+    miterLimit: undefined
+  };
+
 };
+goog.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay);
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  * @private
- * @return {Array.<string>|undefined} Format array.
+ * @return {number} end.
  */
-ol.format.WMSCapabilities.readException_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Exception');
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack);
+ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ =
+    function(flatCoordinates, offset, end, stride) {
+  var myBegin = this.coordinates.length;
+  var myEnd = this.appendFlatCoordinates(
+      flatCoordinates, offset, end, stride, false);
+  var moveToLineToInstruction =
+      [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
+  this.instructions.push(moveToLineToInstruction);
+  this.hitDetectionInstructions.push(moveToLineToInstruction);
+  return end;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Layer object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Layer');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
+ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() {
+  var extent = this.maxExtent;
+  if (this.maxLineWidth) {
+    extent = ol.extent.buffer(
+        extent, this.resolution * (this.maxLineWidth + 1) / 2);
+  }
+  return extent;
 };
 
 
 /**
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Layer object.
  */
-ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Layer');
-  var parentLayerObject = /**  @type {Object.<string,*>} */
-      (objectStack[objectStack.length - 1]);
-
-  var layerObject = /**  @type {Object.<string,*>} */ (ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack));
-
-  if (!goog.isDef(layerObject)) {
-    return undefined;
-  }
-  var queryable =
-      ol.format.XSD.readBooleanString(node.getAttribute('queryable'));
-  if (!goog.isDef(queryable)) {
-    queryable = goog.object.get(parentLayerObject, 'queryable');
+ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() {
+  var state = this.state_;
+  var strokeStyle = state.strokeStyle;
+  var lineCap = state.lineCap;
+  var lineDash = state.lineDash;
+  var lineJoin = state.lineJoin;
+  var lineWidth = state.lineWidth;
+  var miterLimit = state.miterLimit;
+  goog.asserts.assert(goog.isDef(strokeStyle));
+  goog.asserts.assert(goog.isDef(lineCap));
+  goog.asserts.assert(!goog.isNull(lineDash));
+  goog.asserts.assert(goog.isDef(lineJoin));
+  goog.asserts.assert(goog.isDef(lineWidth));
+  goog.asserts.assert(goog.isDef(miterLimit));
+  if (state.currentStrokeStyle != strokeStyle ||
+      state.currentLineCap != lineCap ||
+      !goog.array.equals(state.currentLineDash, lineDash) ||
+      state.currentLineJoin != lineJoin ||
+      state.currentLineWidth != lineWidth ||
+      state.currentMiterLimit != miterLimit) {
+    if (state.lastStroke != this.coordinates.length) {
+      this.instructions.push(
+          [ol.render.canvas.Instruction.STROKE]);
+      state.lastStroke = this.coordinates.length;
+    }
+    this.instructions.push(
+        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
+         strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash],
+        [ol.render.canvas.Instruction.BEGIN_PATH]);
+    state.currentStrokeStyle = strokeStyle;
+    state.currentLineCap = lineCap;
+    state.currentLineDash = lineDash;
+    state.currentLineJoin = lineJoin;
+    state.currentLineWidth = lineWidth;
+    state.currentMiterLimit = miterLimit;
   }
-  goog.object.set(
-      layerObject, 'queryable', goog.isDef(queryable) ? queryable : false);
+};
 
-  var cascaded = ol.format.XSD.readNonNegativeIntegerString(
-      node.getAttribute('cascaded'));
-  if (!goog.isDef(cascaded)) {
-    cascaded = goog.object.get(parentLayerObject, 'cascaded');
-  }
-  goog.object.set(layerObject, 'cascaded', cascaded);
 
-  var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
-  if (!goog.isDef(opaque)) {
-    opaque = goog.object.get(parentLayerObject, 'opaque');
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.LineStringReplay.prototype.drawLineStringGeometry =
+    function(lineStringGeometry, feature) {
+  var state = this.state_;
+  goog.asserts.assert(!goog.isNull(state));
+  var strokeStyle = state.strokeStyle;
+  var lineWidth = state.lineWidth;
+  if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) {
+    return;
   }
-  goog.object.set(layerObject, 'opaque', goog.isDef(opaque) ? opaque : false);
+  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);
+};
 
-  var noSubsets =
-      ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
-  if (!goog.isDef(noSubsets)) {
-    noSubsets = goog.object.get(parentLayerObject, 'noSubsets');
-  }
-  goog.object.set(
-      layerObject, 'noSubsets', goog.isDef(noSubsets) ? noSubsets : false);
 
-  var fixedWidth =
-      ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
-  if (!goog.isDef(fixedWidth)) {
-    fixedWidth = goog.object.get(parentLayerObject, 'fixedWidth');
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.LineStringReplay.prototype.drawMultiLineStringGeometry =
+    function(multiLineStringGeometry, feature) {
+  var state = this.state_;
+  goog.asserts.assert(!goog.isNull(state));
+  var strokeStyle = state.strokeStyle;
+  var lineWidth = state.lineWidth;
+  if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) {
+    return;
   }
-  goog.object.set(layerObject, 'fixedWidth', fixedWidth);
-
-  var fixedHeight =
-      ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
-  if (!goog.isDef(fixedHeight)) {
-    fixedHeight = goog.object.get(parentLayerObject, 'fixedHeight');
+  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);
   }
-  goog.object.set(layerObject, 'fixedHeight', fixedHeight);
-
-  // See 7.2.4.8
-  var addKeys = ['Style', 'CRS', 'AuthorityURL'];
-  goog.array.forEach(addKeys, function(key) {
-    var parentValue = goog.object.get(parentLayerObject, key);
-    if (goog.isDef(parentValue)) {
-      var childValue = goog.object.setIfUndefined(layerObject, key, []);
-      childValue = childValue.concat(parentValue);
-    }
-  });
-
-  var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
-    'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'];
-  goog.array.forEach(replaceKeys, function(key) {
-    var childValue = goog.object.get(layerObject, key);
-    if (!goog.isDef(childValue)) {
-      var parentValue = goog.object.get(parentLayerObject, key);
-      goog.object.set(layerObject, key, parentValue);
-    }
-  });
-
-  return layerObject;
+  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
+  this.endGeometry(multiLineStringGeometry, feature);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object} Dimension object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Dimension');
-  var dimensionObject = {
-    'name': node.getAttribute('name'),
-    'units': node.getAttribute('units'),
-    'unitSymbol': node.getAttribute('unitSymbol'),
-    'default': node.getAttribute('default'),
-    'multipleValues': ol.format.XSD.readBooleanString(
-        node.getAttribute('multipleValues')),
-    'nearestValue': ol.format.XSD.readBooleanString(
-        node.getAttribute('nearestValue')),
-    'current': ol.format.XSD.readBooleanString(node.getAttribute('current')),
-    'values': ol.format.XSD.readString(node)
-  };
-  return dimensionObject;
+ol.render.canvas.LineStringReplay.prototype.finish = function() {
+  var state = this.state_;
+  goog.asserts.assert(!goog.isNull(state));
+  if (state.lastStroke != this.coordinates.length) {
+    this.instructions.push([ol.render.canvas.Instruction.STROKE]);
+  }
+  this.reverseHitDetectionInstructions_();
+  this.state_ = null;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readFormatOnlineresource_ =
-    function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_,
-      node, objectStack);
+ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle =
+    function(fillStyle, strokeStyle) {
+  goog.asserts.assert(!goog.isNull(this.state_));
+  goog.asserts.assert(goog.isNull(fillStyle));
+  goog.asserts.assert(!goog.isNull(strokeStyle));
+  var strokeStyleColor = strokeStyle.getColor();
+  this.state_.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ?
+      strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
+  var strokeStyleLineCap = strokeStyle.getLineCap();
+  this.state_.lineCap = goog.isDef(strokeStyleLineCap) ?
+      strokeStyleLineCap : ol.render.canvas.defaultLineCap;
+  var strokeStyleLineDash = strokeStyle.getLineDash();
+  this.state_.lineDash = !goog.isNull(strokeStyleLineDash) ?
+      strokeStyleLineDash : ol.render.canvas.defaultLineDash;
+  var strokeStyleLineJoin = strokeStyle.getLineJoin();
+  this.state_.lineJoin = goog.isDef(strokeStyleLineJoin) ?
+      strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
+  var strokeStyleWidth = strokeStyle.getWidth();
+  this.state_.lineWidth = goog.isDef(strokeStyleWidth) ?
+      strokeStyleWidth : ol.render.canvas.defaultLineWidth;
+  var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
+  this.state_.miterLimit = goog.isDef(strokeStyleMiterLimit) ?
+      strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
+  this.maxLineWidth = Math.max(this.maxLineWidth, this.state_.lineWidth);
 };
 
 
+
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Request object.
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @protected
+ * @struct
  */
-ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Request');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack);
-};
+ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) {
 
+  goog.base(this, tolerance, maxExtent, resolution);
+
+  /**
+   * @private
+   * @type {{currentFillStyle: (string|undefined),
+   *         currentStrokeStyle: (string|undefined),
+   *         currentLineCap: (string|undefined),
+   *         currentLineDash: Array.<number>,
+   *         currentLineJoin: (string|undefined),
+   *         currentLineWidth: (number|undefined),
+   *         currentMiterLimit: (number|undefined),
+   *         fillStyle: (string|undefined),
+   *         strokeStyle: (string|undefined),
+   *         lineCap: (string|undefined),
+   *         lineDash: Array.<number>,
+   *         lineJoin: (string|undefined),
+   *         lineWidth: (number|undefined),
+   *         miterLimit: (number|undefined)}|null}
+   */
+  this.state_ = {
+    currentFillStyle: undefined,
+    currentStrokeStyle: undefined,
+    currentLineCap: undefined,
+    currentLineDash: null,
+    currentLineJoin: undefined,
+    currentLineWidth: undefined,
+    currentMiterLimit: undefined,
+    fillStyle: undefined,
+    strokeStyle: undefined,
+    lineCap: undefined,
+    lineDash: null,
+    lineJoin: undefined,
+    lineWidth: undefined,
+    miterLimit: undefined
+  };
 
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} DCP type object.
- */
-ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'DCPType');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack);
 };
+goog.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);
 
 
 /**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} HTTP object.
+ * @return {number} End.
  */
-ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'HTTP');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack);
+ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ =
+    function(flatCoordinates, offset, ends, stride) {
+  var state = this.state_;
+  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
+  this.instructions.push(beginPathInstruction);
+  this.hitDetectionInstructions.push(beginPathInstruction);
+  var i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    var end = ends[i];
+    var myBegin = this.coordinates.length;
+    var myEnd = this.appendFlatCoordinates(
+        flatCoordinates, offset, end, stride, true);
+    var moveToLineToInstruction =
+        [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
+    var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH];
+    this.instructions.push(moveToLineToInstruction, closePathInstruction);
+    this.hitDetectionInstructions.push(moveToLineToInstruction,
+        closePathInstruction);
+    offset = end;
+  }
+  // FIXME is it quicker to fill and stroke each polygon individually,
+  // FIXME or all polygons together?
+  var fillInstruction = [ol.render.canvas.Instruction.FILL];
+  this.hitDetectionInstructions.push(fillInstruction);
+  if (goog.isDef(state.fillStyle)) {
+    this.instructions.push(fillInstruction);
+  }
+  if (goog.isDef(state.strokeStyle)) {
+    goog.asserts.assert(goog.isDef(state.lineWidth));
+    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
+    this.instructions.push(strokeInstruction);
+    this.hitDetectionInstructions.push(strokeInstruction);
+  }
+  return offset;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Operation type object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
+ol.render.canvas.PolygonReplay.prototype.drawCircleGeometry =
+    function(circleGeometry, feature) {
+  var state = this.state_;
+  goog.asserts.assert(!goog.isNull(state));
+  var fillStyle = state.fillStyle;
+  var strokeStyle = state.strokeStyle;
+  if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) {
+    return;
+  }
+  if (goog.isDef(strokeStyle)) {
+    goog.asserts.assert(goog.isDef(state.lineWidth));
+  }
+  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 (goog.isDef(state.strokeStyle)) {
+    this.hitDetectionInstructions.push(
+        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
+         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
+         state.miterLimit, state.lineDash]);
+  }
+  var flatCoordinates = circleGeometry.getFlatCoordinates();
+  var stride = circleGeometry.getStride();
+  var myBegin = this.coordinates.length;
+  this.appendFlatCoordinates(
+      flatCoordinates, 0, flatCoordinates.length, stride, false);
+  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
+  var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin];
+  this.instructions.push(beginPathInstruction, circleInstruction);
+  this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction);
+  var fillInstruction = [ol.render.canvas.Instruction.FILL];
+  this.hitDetectionInstructions.push(fillInstruction);
+  if (goog.isDef(state.fillStyle)) {
+    this.instructions.push(fillInstruction);
+  }
+  if (goog.isDef(state.strokeStyle)) {
+    goog.asserts.assert(goog.isDef(state.lineWidth));
+    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
+    this.instructions.push(strokeInstruction);
+    this.hitDetectionInstructions.push(strokeInstruction);
+  }
+  this.endGeometry(circleGeometry, feature);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readSizedFormatOnlineresource_ =
-    function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  var formatOnlineresource =
-      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
-  if (goog.isDef(formatOnlineresource)) {
-    var size = [
-      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')),
-      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height'))
-    ];
-    goog.object.set(formatOnlineresource, 'size', size);
-    return formatOnlineresource;
+ol.render.canvas.PolygonReplay.prototype.drawPolygonGeometry =
+    function(polygonGeometry, feature) {
+  var state = this.state_;
+  goog.asserts.assert(!goog.isNull(state));
+  var fillStyle = state.fillStyle;
+  var strokeStyle = state.strokeStyle;
+  if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) {
+    return;
   }
-  return undefined;
+  if (goog.isDef(strokeStyle)) {
+    goog.asserts.assert(goog.isDef(state.lineWidth));
+  }
+  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 (goog.isDef(state.strokeStyle)) {
+    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);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Authority URL object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'AuthorityURL');
-  var authorityObject =
-      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
-  if (goog.isDef(authorityObject)) {
-    goog.object.set(authorityObject, 'name', node.getAttribute('name'));
-    return authorityObject;
+ol.render.canvas.PolygonReplay.prototype.drawMultiPolygonGeometry =
+    function(multiPolygonGeometry, feature) {
+  var state = this.state_;
+  goog.asserts.assert(!goog.isNull(state));
+  var fillStyle = state.fillStyle;
+  var strokeStyle = state.strokeStyle;
+  if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) {
+    return;
   }
-  return undefined;
+  if (goog.isDef(strokeStyle)) {
+    goog.asserts.assert(goog.isDef(state.lineWidth));
+  }
+  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 (goog.isDef(state.strokeStyle)) {
+    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);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Metadata URL object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'MetadataURL');
-  var metadataObject =
-      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
-  if (goog.isDef(metadataObject)) {
-    goog.object.set(metadataObject, 'type', node.getAttribute('type'));
-    return metadataObject;
+ol.render.canvas.PolygonReplay.prototype.finish = function() {
+  goog.asserts.assert(!goog.isNull(this.state_));
+  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);
+    }
   }
-  return undefined;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Style object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'Style');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack);
+ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() {
+  var extent = this.maxExtent;
+  if (this.maxLineWidth) {
+    extent = ol.extent.buffer(
+        extent, this.resolution * (this.maxLineWidth + 1) / 2);
+  }
+  return extent;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Keyword list.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) {
-  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
-  goog.asserts.assert(node.localName == 'KeywordList');
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack);
+ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle =
+    function(fillStyle, strokeStyle) {
+  goog.asserts.assert(!goog.isNull(this.state_));
+  goog.asserts.assert(!goog.isNull(fillStyle) || !goog.isNull(strokeStyle));
+  var state = this.state_;
+  if (!goog.isNull(fillStyle)) {
+    var fillStyleColor = fillStyle.getColor();
+    state.fillStyle = ol.color.asString(!goog.isNull(fillStyleColor) ?
+        fillStyleColor : ol.render.canvas.defaultFillStyle);
+  } else {
+    state.fillStyle = undefined;
+  }
+  if (!goog.isNull(strokeStyle)) {
+    var strokeStyleColor = strokeStyle.getColor();
+    state.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ?
+        strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
+    var strokeStyleLineCap = strokeStyle.getLineCap();
+    state.lineCap = goog.isDef(strokeStyleLineCap) ?
+        strokeStyleLineCap : ol.render.canvas.defaultLineCap;
+    var strokeStyleLineDash = strokeStyle.getLineDash();
+    state.lineDash = !goog.isNull(strokeStyleLineDash) ?
+        strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
+    var strokeStyleLineJoin = strokeStyle.getLineJoin();
+    state.lineJoin = goog.isDef(strokeStyleLineJoin) ?
+        strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
+    var strokeStyleWidth = strokeStyle.getWidth();
+    state.lineWidth = goog.isDef(strokeStyleWidth) ?
+        strokeStyleWidth : ol.render.canvas.defaultLineWidth;
+    var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
+    state.miterLimit = goog.isDef(strokeStyleMiterLimit) ?
+        strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
+    this.maxLineWidth = Math.max(this.maxLineWidth, state.lineWidth);
+  } else {
+    state.strokeStyle = undefined;
+    state.lineCap = undefined;
+    state.lineDash = null;
+    state.lineJoin = undefined;
+    state.lineWidth = undefined;
+    state.miterLimit = undefined;
+  }
 };
 
 
 /**
- * @const
  * @private
- * @type {Array.<string>}
  */
-ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/wms'
-];
-
+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 (goog.isDef(fillStyle) && state.currentFillStyle != fillStyle) {
+    this.instructions.push(
+        [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]);
+    state.currentFillStyle = state.fillStyle;
+  }
+  if (goog.isDef(strokeStyle)) {
+    goog.asserts.assert(goog.isDef(lineCap));
+    goog.asserts.assert(!goog.isNull(lineDash));
+    goog.asserts.assert(goog.isDef(lineJoin));
+    goog.asserts.assert(goog.isDef(lineWidth));
+    goog.asserts.assert(goog.isDef(miterLimit));
+    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;
+    }
+  }
+};
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Service': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readService_),
-      'Capability': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readCapability_)
-    });
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @protected
+ * @struct
  */
-ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Request': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readRequest_),
-      'Exception': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readException_),
-      'Layer': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readCapabilityLayer_)
-    });
+ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution) {
 
+  goog.base(this, tolerance, maxExtent, resolution);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeParsersNS(
-    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)
-    });
+  /**
+   * @private
+   * @type {?ol.render.canvas.FillState}
+   */
+  this.replayFillState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.render.canvas.StrokeState}
+   */
+  this.replayStrokeState_ = null;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeParsersNS(
-    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)
-    });
+  /**
+   * @private
+   * @type {?ol.render.canvas.TextState}
+   */
+  this.replayTextState_ = null;
 
+  /**
+   * @private
+   * @type {string}
+   */
+  this.text_ = '';
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'ContactPerson': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'ContactOrganization': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetX_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textOffsetY_ = 0;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'StateOrProvince': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
-    });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textRotation_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textScale_ = 0;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
-    });
+  /**
+   * @private
+   * @type {?ol.render.canvas.FillState}
+   */
+  this.textFillState_ = null;
 
+  /**
+   * @private
+   * @type {?ol.render.canvas.StrokeState}
+   */
+  this.textStrokeState_ = null;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
- */
-ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'KeywordList': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readKeywordList_),
-      'CRS': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
-      'EX_GeographicBoundingBox': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readEXGeographicBoundingBox_),
-      'BoundingBox': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readBoundingBox_),
-      'Dimension': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readDimension_),
-      'Attribution': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readAttribution_),
-      'AuthorityURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readAuthorityURL_),
-      'Identifier': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
-      'MetadataURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readMetadataURL_),
-      'DataURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'FeatureListURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'Style': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readStyle_),
-      'MinScaleDenominator': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'MaxScaleDenominator': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'Layer': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readLayer_)
-    });
+  /**
+   * @private
+   * @type {?ol.render.canvas.TextState}
+   */
+  this.textState_ = null;
+
+};
+goog.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'OnlineResource': ol.xml.makeObjectPropertySetter(
-          ol.format.XLink.readHref),
-      'LogoURL': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readSizedFormatOnlineresource_)
-    });
+ol.render.canvas.TextReplay.prototype.drawText =
+    function(flatCoordinates, offset, end, stride, geometry, feature) {
+  if (this.text_ === '' ||
+      goog.isNull(this.textState_) ||
+      (goog.isNull(this.textFillState_) &&
+       goog.isNull(this.textStrokeState_))) {
+    return;
+  }
+  if (!goog.isNull(this.textFillState_)) {
+    this.setReplayFillState_(this.textFillState_);
+  }
+  if (!goog.isNull(this.textStrokeState_)) {
+    this.setReplayStrokeState_(this.textStrokeState_);
+  }
+  this.setReplayTextState_(this.textState_);
+  this.beginGeometry(geometry, feature);
+  var myBegin = this.coordinates.length;
+  var myEnd =
+      this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false);
+  var fill = !goog.isNull(this.textFillState_);
+  var stroke = !goog.isNull(this.textStrokeState_);
+  var drawTextInstruction = [
+    ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_,
+    this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_,
+    fill, stroke];
+  this.instructions.push(drawTextInstruction);
+  this.hitDetectionInstructions.push(drawTextInstruction);
+  this.endGeometry(geometry, feature);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.canvas.FillState} fillState Fill state.
  * @private
  */
-ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ =
-    ol.xml.makeParsersNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'westBoundLongitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'eastBoundLongitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'southBoundLatitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'northBoundLatitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal)
-    });
+ol.render.canvas.TextReplay.prototype.setReplayFillState_ =
+    function(fillState) {
+  var replayFillState = this.replayFillState_;
+  if (!goog.isNull(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 (goog.isNull(replayFillState)) {
+    this.replayFillState_ = {
+      fillStyle: fillState.fillStyle
+    };
+  } else {
+    replayFillState.fillStyle = fillState.fillStyle;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.canvas.StrokeState} strokeState Stroke state.
  * @private
  */
-ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeParsersNS(
-    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_)
-    });
+ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ =
+    function(strokeState) {
+  var replayStrokeState = this.replayStrokeState_;
+  if (!goog.isNull(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 (goog.isNull(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;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @param {ol.render.canvas.TextState} textState Text state.
  * @private
  */
-ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
-      'DCPType': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readDCPType_)
-    });
+ol.render.canvas.TextReplay.prototype.setReplayTextState_ =
+    function(textState) {
+  var replayTextState = this.replayTextState_;
+  if (!goog.isNull(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 (goog.isNull(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;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readHTTP_)
-    });
+ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
+  if (goog.isNull(textStyle)) {
+    this.text_ = '';
+  } else {
+    var textFillStyle = textStyle.getFill();
+    if (goog.isNull(textFillStyle)) {
+      this.textFillState_ = null;
+    } else {
+      var textFillStyleColor = textFillStyle.getColor();
+      var fillStyle = ol.color.asString(!goog.isNull(textFillStyleColor) ?
+          textFillStyleColor : ol.render.canvas.defaultFillStyle);
+      if (goog.isNull(this.textFillState_)) {
+        this.textFillState_ = {
+          fillStyle: fillStyle
+        };
+      } else {
+        var textFillState = this.textFillState_;
+        textFillState.fillStyle = fillStyle;
+      }
+    }
+    var textStrokeStyle = textStyle.getStroke();
+    if (goog.isNull(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 = goog.isDef(textStrokeStyleLineCap) ?
+          textStrokeStyleLineCap : ol.render.canvas.defaultLineCap;
+      var lineDash = goog.isDefAndNotNull(textStrokeStyleLineDash) ?
+          textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
+      var lineJoin = goog.isDef(textStrokeStyleLineJoin) ?
+          textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
+      var lineWidth = goog.isDef(textStrokeStyleWidth) ?
+          textStrokeStyleWidth : ol.render.canvas.defaultLineWidth;
+      var miterLimit = goog.isDef(textStrokeStyleMiterLimit) ?
+          textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
+      var strokeStyle = ol.color.asString(!goog.isNull(textStrokeStyleColor) ?
+          textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle);
+      if (goog.isNull(this.textStrokeState_)) {
+        this.textStrokeState_ = {
+          lineCap: lineCap,
+          lineDash: lineDash,
+          lineJoin: lineJoin,
+          lineWidth: lineWidth,
+          miterLimit: miterLimit,
+          strokeStyle: strokeStyle
+        };
+      } else {
+        var textStrokeState = this.textStrokeState_;
+        textStrokeState.lineCap = lineCap;
+        textStrokeState.lineDash = lineDash;
+        textStrokeState.lineJoin = lineJoin;
+        textStrokeState.lineWidth = lineWidth;
+        textStrokeState.miterLimit = miterLimit;
+        textStrokeState.strokeStyle = strokeStyle;
+      }
+    }
+    var textFont = textStyle.getFont();
+    var textOffsetX = textStyle.getOffsetX();
+    var textOffsetY = textStyle.getOffsetY();
+    var textRotation = textStyle.getRotation();
+    var textScale = textStyle.getScale();
+    var textText = textStyle.getText();
+    var textTextAlign = textStyle.getTextAlign();
+    var textTextBaseline = textStyle.getTextBaseline();
+    var font = goog.isDef(textFont) ?
+        textFont : ol.render.canvas.defaultFont;
+    var textAlign = goog.isDef(textTextAlign) ?
+        textTextAlign : ol.render.canvas.defaultTextAlign;
+    var textBaseline = goog.isDef(textTextBaseline) ?
+        textTextBaseline : ol.render.canvas.defaultTextBaseline;
+    if (goog.isNull(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_ = goog.isDef(textText) ? textText : '';
+    this.textOffsetX_ = goog.isDef(textOffsetX) ? textOffsetX : 0;
+    this.textOffsetY_ = goog.isDef(textOffsetY) ? textOffsetY : 0;
+    this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0;
+    this.textScale_ = goog.isDef(textScale) ? textScale : 1;
+  }
+};
+
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @constructor
+ * @implements {ol.render.IReplayGroup}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @param {number} resolution Resolution.
+ * @struct
  */
-ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Get': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'Post': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_)
-    });
+ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution) {
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.tolerance_ = tolerance;
+
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.maxExtent_ = maxExtent;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.resolution_ = resolution;
+
+  /**
+   * @private
+   * @type {Object.<string,
+   *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
+   */
+  this.replaysByZIndex_ = {};
+
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1);
+
+  /**
+   * @private
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.hitDetectionTransform_ = goog.vec.Mat4.createNumber();
+
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * FIXME empty description for jsdoc
  */
-ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'LegendURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readSizedFormatOnlineresource_),
-      'StyleSheetURL': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'StyleURL': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_)
-    });
+ol.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();
+    }
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {Object} skippedFeaturesHash Ids of features to skip
+ * @param {function(ol.Feature): T} callback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
  */
-ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'OnlineResource': ol.xml.makeObjectPropertySetter(
-          ol.format.XLink.readHref)
-    });
+ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function(
+    resolution, rotation, coordinate, skippedFeaturesHash, callback) {
+
+  var transform = this.hitDetectionTransform_;
+  ol.vec.Mat4.makeTransform2D(transform, 0.5, 0.5,
+      1 / resolution, -1 / resolution, -rotation,
+      -coordinate[0], -coordinate[1]);
+
+  var context = this.hitDetectionContext_;
+  context.clearRect(0, 0, 1, 1);
+
+  return this.replayHitDetection_(context, transform, rotation,
+      skippedFeaturesHash,
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {?} Callback result.
+       */
+      function(feature) {
+        var imageData = context.getImageData(0, 0, 1, 1).data;
+        if (imageData[3] > 0) {
+          var result = callback(feature);
+          if (result) {
+            return result;
+          }
+          context.clearRect(0, 0, 1, 1);
+        }
+      });
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.xml.Parser>>}
- * @private
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeParsersNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString)
-    });
+ol.render.canvas.ReplayGroup.prototype.getReplay =
+    function(zIndex, replayType) {
+  var zIndexKey = goog.isDef(zIndex) ? zIndex.toString() : '0';
+  var replays = this.replaysByZIndex_[zIndexKey];
+  if (!goog.isDef(replays)) {
+    replays = {};
+    this.replaysByZIndex_[zIndexKey] = replays;
+  }
+  var replay = replays[replayType];
+  if (!goog.isDef(replay)) {
+    var Constructor = ol.render.canvas.BATCH_CONSTRUCTORS_[replayType];
+    goog.asserts.assert(goog.isDef(Constructor));
+    replay = new Constructor(this.tolerance_, this.maxExtent_,
+        this.resolution_);
+    replays[replayType] = replay;
+  }
+  return replay;
+};
 
-goog.provide('ol.sphere.WGS84');
 
-goog.require('ol.Sphere');
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
+  return goog.object.isEmpty(this.replaysByZIndex_);
+};
 
 
 /**
- * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid.
- * @const
- * @type {ol.Sphere}
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip
  */
-ol.sphere.WGS84 = new ol.Sphere(6378137);
+ol.render.canvas.ReplayGroup.prototype.replay = function(
+    context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
 
-// FIXME handle geolocation not supported
+  /** @type {Array.<number>} */
+  var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number);
+  goog.array.sort(zs);
 
-goog.provide('ol.Geolocation');
-goog.provide('ol.GeolocationProperty');
+  var maxExtent = this.maxExtent_;
+  var minX = maxExtent[0];
+  var minY = maxExtent[1];
+  var maxX = maxExtent[2];
+  var maxY = maxExtent[3];
+  var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY];
+  ol.geom.flat.transform.transform2D(
+      flatClipCoords, 0, 8, 2, transform, flatClipCoords);
+  context.save();
+  context.beginPath();
+  context.moveTo(flatClipCoords[0], flatClipCoords[1]);
+  context.lineTo(flatClipCoords[2], flatClipCoords[3]);
+  context.lineTo(flatClipCoords[4], flatClipCoords[5]);
+  context.lineTo(flatClipCoords[6], flatClipCoords[7]);
+  context.closePath();
+  context.clip();
 
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.math');
-goog.require('ol.Coordinate');
-goog.require('ol.Object');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.Polygon');
-goog.require('ol.has');
-goog.require('ol.proj');
-goog.require('ol.sphere.WGS84');
+  var i, ii, j, jj, replays, replay, result;
+  for (i = 0, ii = zs.length; i < ii; ++i) {
+    replays = this.replaysByZIndex_[zs[i].toString()];
+    for (j = 0, jj = ol.render.REPLAY_ORDER.length; j < jj; ++j) {
+      replay = replays[ol.render.REPLAY_ORDER[j]];
+      if (goog.isDef(replay)) {
+        replay.replay(context, pixelRatio, transform, viewRotation,
+            skippedFeaturesHash);
+      }
+    }
+  }
+
+  context.restore();
+};
 
 
 /**
- * @enum {string}
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @param {number} viewRotation View rotation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip
+ * @param {function(ol.Feature): T} featureCallback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
  */
-ol.GeolocationProperty = {
-  ACCURACY: 'accuracy',
-  ACCURACY_GEOMETRY: 'accuracyGeometry',
-  ALTITUDE: 'altitude',
-  ALTITUDE_ACCURACY: 'altitudeAccuracy',
-  HEADING: 'heading',
-  POSITION: 'position',
-  PROJECTION: 'projection',
-  SPEED: 'speed',
-  TRACKING: 'tracking',
-  TRACKING_OPTIONS: 'trackingOptions'
-};
+ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
+    context, transform, viewRotation, skippedFeaturesHash,
+    featureCallback) {
+  /** @type {Array.<number>} */
+  var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number);
+  goog.array.sort(zs, function(a, b) { return b - a; });
 
+  var i, ii, j, replays, replay, result;
+  for (i = 0, ii = zs.length; i < ii; ++i) {
+    replays = this.replaysByZIndex_[zs[i].toString()];
+    for (j = ol.render.REPLAY_ORDER.length - 1; j >= 0; --j) {
+      replay = replays[ol.render.REPLAY_ORDER[j]];
+      if (goog.isDef(replay)) {
+        result = replay.replayHitDetection(context, transform, viewRotation,
+            skippedFeaturesHash, featureCallback);
+        if (result) {
+          return result;
+        }
+      }
+    }
+  }
+  return undefined;
+};
 
 
 /**
- * @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.
- *
- * Example:
- *
- *     var geolocation = new ol.Geolocation({
- *       // take the projection to use from the map's view
- *       projection: view.getprojection()
- *     });
- *     // listen to changes in position
- *     geolocation.on('change', function(evt) {
- *       window.console.log(geolocation.getPosition());
- *     });
- *
- * @constructor
- * @extends {ol.Object}
- * @fires change Triggered when the position changes.
- * @param {olx.GeolocationOptions=} opt_options Options.
- * @api stable
+ * @const
+ * @private
+ * @type {Object.<ol.render.ReplayType,
+ *                function(new: ol.render.canvas.Replay, number, ol.Extent,
+ *                number)>}
  */
-ol.Geolocation = function(opt_options) {
+ol.render.canvas.BATCH_CONSTRUCTORS_ = {
+  'Image': ol.render.canvas.ImageReplay,
+  'LineString': ol.render.canvas.LineStringReplay,
+  'Polygon': ol.render.canvas.PolygonReplay,
+  'Text': ol.render.canvas.TextReplay
+};
 
-  goog.base(this);
+goog.provide('ol.renderer.canvas.Layer');
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.vec.Mat4');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.layer.Layer');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas.Immediate');
+goog.require('ol.renderer.Layer');
+goog.require('ol.vec.Mat4');
 
-  /**
-   * The unprojected (EPSG:4326) device position.
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.position_ = null;
 
-  /**
-   * @private
-   * @type {ol.TransformFunction}
-   */
-  this.transform_ = ol.proj.identityTransform;
+
+/**
+ * @constructor
+ * @extends {ol.renderer.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Layer} layer Layer.
+ */
+ol.renderer.canvas.Layer = function(mapRenderer, layer) {
+
+  goog.base(this, mapRenderer, layer);
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {!goog.vec.Mat4.Number}
    */
-  this.watchId_ = undefined;
-
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.GeolocationProperty.PROJECTION),
-      this.handleProjectionChanged_, false, this);
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.GeolocationProperty.TRACKING),
-      this.handleTrackingChanged_, false, this);
-
-  if (goog.isDef(options.projection)) {
-    this.setProjection(ol.proj.get(options.projection));
-  }
-  if (goog.isDef(options.trackingOptions)) {
-    this.setTrackingOptions(options.trackingOptions);
-  }
-
-  this.setTracking(goog.isDef(options.tracking) ? options.tracking : false);
+  this.transform_ = goog.vec.Mat4.createNumber();
 
 };
-goog.inherits(ol.Geolocation, ol.Object);
+goog.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);
 
 
 /**
- * @inheritDoc
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @param {CanvasRenderingContext2D} context Context.
  */
-ol.Geolocation.prototype.disposeInternal = function() {
-  this.setTracking(false);
-  goog.base(this, 'disposeInternal');
-};
+ol.renderer.canvas.Layer.prototype.composeFrame =
+    function(frameState, layerState, context) {
 
+  this.dispatchPreComposeEvent(context, frameState);
 
-/**
- * @private
- */
-ol.Geolocation.prototype.handleProjectionChanged_ = function() {
-  var projection = this.getProjection();
-  if (goog.isDefAndNotNull(projection)) {
-    this.transform_ = ol.proj.getTransformFromProjections(
-        ol.proj.get('EPSG:4326'), projection);
-    if (!goog.isNull(this.position_)) {
-      this.set(
-          ol.GeolocationProperty.POSITION, this.transform_(this.position_));
+  var image = this.getImage();
+  if (!goog.isNull(image)) {
+
+    // clipped rendering if layer extent is set
+    var extent = layerState.extent;
+    var clipped = goog.isDef(extent);
+    if (clipped) {
+      goog.asserts.assert(goog.isDef(extent));
+      var topLeft = ol.extent.getTopLeft(extent);
+      var topRight = ol.extent.getTopRight(extent);
+      var bottomRight = ol.extent.getBottomRight(extent);
+      var bottomLeft = ol.extent.getBottomLeft(extent);
+
+      ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix,
+          topLeft, topLeft);
+      ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix,
+          topRight, topRight);
+      ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix,
+          bottomRight, bottomRight);
+      ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix,
+          bottomLeft, bottomLeft);
+
+      context.save();
+      context.beginPath();
+      context.moveTo(topLeft[0], topLeft[1]);
+      context.lineTo(topRight[0], topRight[1]);
+      context.lineTo(bottomRight[0], bottomRight[1]);
+      context.lineTo(bottomLeft[0], bottomLeft[1]);
+      context.clip();
+    }
+
+    var imageTransform = this.getImageTransform();
+    // for performance reasons, context.save / context.restore is not used
+    // to save and restore the transformation matrix and the opacity.
+    // see http://jsperf.com/context-save-restore-versus-variable
+    var alpha = context.globalAlpha;
+    context.globalAlpha = layerState.opacity;
+
+    // for performance reasons, context.setTransform is only used
+    // when the view is rotated. see http://jsperf.com/canvas-transform
+    if (frameState.viewState.rotation === 0) {
+      var dx = goog.vec.Mat4.getElement(imageTransform, 0, 3);
+      var dy = goog.vec.Mat4.getElement(imageTransform, 1, 3);
+      var dw = image.width * goog.vec.Mat4.getElement(imageTransform, 0, 0);
+      var dh = image.height * goog.vec.Mat4.getElement(imageTransform, 1, 1);
+      context.drawImage(image, 0, 0, +image.width, +image.height,
+          Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
+    } else {
+      context.setTransform(
+          goog.vec.Mat4.getElement(imageTransform, 0, 0),
+          goog.vec.Mat4.getElement(imageTransform, 1, 0),
+          goog.vec.Mat4.getElement(imageTransform, 0, 1),
+          goog.vec.Mat4.getElement(imageTransform, 1, 1),
+          goog.vec.Mat4.getElement(imageTransform, 0, 3),
+          goog.vec.Mat4.getElement(imageTransform, 1, 3));
+      context.drawImage(image, 0, 0);
+      context.setTransform(1, 0, 0, 1, 0, 0);
+    }
+    context.globalAlpha = alpha;
+
+    if (clipped) {
+      context.restore();
     }
   }
+
+  this.dispatchPostComposeEvent(context, frameState);
+
 };
 
 
 /**
+ * @param {ol.render.EventType} type Event type.
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number=} opt_transform Transform.
  * @private
  */
-ol.Geolocation.prototype.handleTrackingChanged_ = function() {
-  if (ol.has.GEOLOCATION) {
-    var tracking = this.getTracking();
-    if (tracking && !goog.isDef(this.watchId_)) {
-      this.watchId_ = goog.global.navigator.geolocation.watchPosition(
-          goog.bind(this.positionChange_, this),
-          goog.bind(this.positionError_, this),
-          this.getTrackingOptions());
-    } else if (!tracking && goog.isDef(this.watchId_)) {
-      goog.global.navigator.geolocation.clearWatch(this.watchId_);
-      this.watchId_ = undefined;
-    }
+ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ =
+    function(type, context, frameState, opt_transform) {
+  var layer = this.getLayer();
+  if (layer.hasListener(type)) {
+    var transform = goog.isDef(opt_transform) ?
+        opt_transform : this.getTransform(frameState);
+    var render = new ol.render.canvas.Immediate(
+        context, frameState.pixelRatio, frameState.extent, transform,
+        frameState.viewState.rotation);
+    var composeEvent = new ol.render.Event(type, layer, render, null,
+        frameState, context, null);
+    layer.dispatchEvent(composeEvent);
+    render.flush();
   }
 };
 
 
 /**
- * @private
- * @param {GeolocationPosition} position position event.
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * @protected
  */
-ol.Geolocation.prototype.positionChange_ = function(position) {
-  var coords = position.coords;
-  this.set(ol.GeolocationProperty.ACCURACY, coords.accuracy);
-  this.set(ol.GeolocationProperty.ALTITUDE,
-      goog.isNull(coords.altitude) ? undefined : coords.altitude);
-  this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY,
-      goog.isNull(coords.altitudeAccuracy) ?
-      undefined : coords.altitudeAccuracy);
-  this.set(ol.GeolocationProperty.HEADING, goog.isNull(coords.heading) ?
-      undefined : goog.math.toRadians(coords.heading));
-  if (goog.isNull(this.position_)) {
-    this.position_ = [coords.longitude, coords.latitude];
-  } else {
-    this.position_[0] = coords.longitude;
-    this.position_[1] = coords.latitude;
-  }
-  var projectedPosition = this.transform_(this.position_);
-  this.set(ol.GeolocationProperty.POSITION, projectedPosition);
-  this.set(ol.GeolocationProperty.SPEED,
-      goog.isNull(coords.speed) ? undefined : coords.speed);
-  var geometry = ol.geom.Polygon.circular(
-      ol.sphere.WGS84, this.position_, coords.accuracy);
-  geometry.applyTransform(this.transform_);
-  this.set(ol.GeolocationProperty.ACCURACY_GEOMETRY, geometry);
-  this.dispatchChangeEvent();
+ol.renderer.canvas.Layer.prototype.dispatchPostComposeEvent =
+    function(context, frameState, opt_transform) {
+  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context,
+      frameState, opt_transform);
 };
 
 
 /**
- * @private
- * @param {GeolocationPositionError} error error object.
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * @protected
  */
-ol.Geolocation.prototype.positionError_ = function(error) {
-  error.type = goog.events.EventType.ERROR;
-  this.setTracking(false);
-  this.dispatchEvent(error);
+ol.renderer.canvas.Layer.prototype.dispatchPreComposeEvent =
+    function(context, frameState, opt_transform) {
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context,
+      frameState, opt_transform);
 };
 
 
 /**
- * Get the accuracy of the position in meters.
- * @return {number|undefined} The accuracy of the position measurement in
- *     meters.
- * @observable
- * @api stable
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * @protected
  */
-ol.Geolocation.prototype.getAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ACCURACY));
+ol.renderer.canvas.Layer.prototype.dispatchRenderEvent =
+    function(context, frameState, opt_transform) {
+  this.dispatchComposeEvent_(ol.render.EventType.RENDER, context,
+      frameState, opt_transform);
 };
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAccuracy',
-    ol.Geolocation.prototype.getAccuracy);
 
 
 /**
- * Get a geometry of the position accuracy.
- * @return {?ol.geom.Geometry} A geometry of the position accuracy.
- * @observable
- * @api stable
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
  */
-ol.Geolocation.prototype.getAccuracyGeometry = function() {
-  return /** @type {?ol.geom.Geometry} */ (
-      this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null);
-};
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAccuracyGeometry',
-    ol.Geolocation.prototype.getAccuracyGeometry);
+ol.renderer.canvas.Layer.prototype.getImage = goog.abstractMethod;
 
 
 /**
- * Get the altitude associated with the position.
- * @return {number|undefined} The altitude of the position in meters above mean
- *     sea level.
- * @observable
- * @api stable
+ * @return {!goog.vec.Mat4.Number} Image transform.
  */
-ol.Geolocation.prototype.getAltitude = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ALTITUDE));
-};
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAltitude',
-    ol.Geolocation.prototype.getAltitude);
+ol.renderer.canvas.Layer.prototype.getImageTransform = goog.abstractMethod;
 
 
 /**
- * Get the altitude accuracy of the position.
- * @return {number|undefined} The accuracy of the altitude measurement in
- *     meters.
- * @observable
- * @api stable
+ * @param {olx.FrameState} frameState Frame state.
+ * @protected
+ * @return {!goog.vec.Mat4.Number} Transform.
  */
-ol.Geolocation.prototype.getAltitudeAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY));
+ol.renderer.canvas.Layer.prototype.getTransform = function(frameState) {
+  var viewState = frameState.viewState;
+  var pixelRatio = frameState.pixelRatio;
+  return ol.vec.Mat4.makeTransform2D(this.transform_,
+      pixelRatio * frameState.size[0] / 2,
+      pixelRatio * frameState.size[1] / 2,
+      pixelRatio / viewState.resolution,
+      -pixelRatio / viewState.resolution,
+      -viewState.rotation,
+      -viewState.center[0], -viewState.center[1]);
 };
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAltitudeAccuracy',
-    ol.Geolocation.prototype.getAltitudeAccuracy);
 
 
 /**
- * Get the heading as radians clockwise from North.
- * @return {number|undefined} The heading of the device in radians from north.
- * @observable
- * @api stable
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @return {boolean} whether composeFrame should be called.
  */
-ol.Geolocation.prototype.getHeading = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.HEADING));
-};
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getHeading',
-    ol.Geolocation.prototype.getHeading);
+ol.renderer.canvas.Layer.prototype.prepareFrame = goog.abstractMethod;
 
 
 /**
- * Get the position of the device.
- * @return {ol.Coordinate|undefined} The current position of the device reported
- *     in the current projection.
- * @observable
- * @api stable
+ * @param {ol.Size} size Size.
+ * @return {boolean} True when the canvas with the current size does not exceed
+ *     the maximum dimensions.
  */
-ol.Geolocation.prototype.getPosition = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.GeolocationProperty.POSITION));
-};
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getPosition',
-    ol.Geolocation.prototype.getPosition);
+ol.renderer.canvas.Layer.testCanvasSize = (function() {
+
+  /**
+   * @type {CanvasRenderingContext2D}
+   */
+  var context = null;
 
+  /**
+   * @type {ImageData}
+   */
+  var imageData = null;
 
-/**
- * Get the projection associated with the position.
- * @return {ol.proj.Projection|undefined} The projection the position is
- *     reported in.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getProjection = function() {
-  return /** @type {ol.proj.Projection|undefined} */ (
-      this.get(ol.GeolocationProperty.PROJECTION));
-};
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getProjection',
-    ol.Geolocation.prototype.getProjection);
+  return function(size) {
+    if (goog.isNull(context)) {
+      context = ol.dom.createCanvasContext2D(1, 1);
+      imageData = context.createImageData(1, 1);
+      var data = imageData.data;
+      data[0] = 42;
+      data[1] = 84;
+      data[2] = 126;
+      data[3] = 255;
+    }
+    var canvas = context.canvas;
+    var good = size[0] <= canvas.width && size[1] <= canvas.height;
+    if (!good) {
+      canvas.width = size[0];
+      canvas.height = size[1];
+      var x = size[0] - 1;
+      var y = size[1] - 1;
+      context.putImageData(imageData, x, y);
+      var result = context.getImageData(x, y, 1, 1);
+      good = goog.array.equals(imageData.data, result.data);
+    }
+    return good;
+  };
+})();
 
+goog.provide('ol.renderer.canvas.ImageLayer');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.vec.Mat4');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+goog.require('ol.ViewHint');
+goog.require('ol.extent');
+goog.require('ol.layer.Image');
+goog.require('ol.proj');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.vec.Mat4');
 
-/**
- * Get the speed in meters per second.
- * @return {number|undefined} The instantaneous speed of the device in meters
- *     per second.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getSpeed = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.SPEED));
-};
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getSpeed',
-    ol.Geolocation.prototype.getSpeed);
 
 
 /**
- * Are we tracking the user's position?
- * @return {boolean} Whether to track the device's position.
- * @observable
- * @api stable
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Image} imageLayer Single image layer.
  */
-ol.Geolocation.prototype.getTracking = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.GeolocationProperty.TRACKING));
+ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) {
+
+  goog.base(this, mapRenderer, imageLayer);
+
+  /**
+   * @private
+   * @type {?ol.ImageBase}
+   */
+  this.image_ = null;
+
+  /**
+   * @private
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.imageTransform_ = goog.vec.Mat4.createNumber();
+
 };
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getTracking',
-    ol.Geolocation.prototype.getTracking);
+goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer);
 
 
 /**
- * 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 at
- *     {@link http://www.w3.org/TR/geolocation-API/#position_options_interface}
- * @observable
- * @api stable
+ * @inheritDoc
  */
-ol.Geolocation.prototype.getTrackingOptions = function() {
-  return /** @type {GeolocationPositionOptions|undefined} */ (
-      this.get(ol.GeolocationProperty.TRACKING_OPTIONS));
+ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtPixel =
+    function(coordinate, frameState, callback, thisArg) {
+  var layer = this.getLayer();
+  var source = layer.getSource();
+  var resolution = frameState.viewState.resolution;
+  var rotation = frameState.viewState.rotation;
+  var skippedFeatureUids = frameState.skippedFeatureUids;
+  return source.forEachFeatureAtPixel(
+      resolution, rotation, coordinate, skippedFeatureUids,
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {?} Callback result.
+       */
+      function(feature) {
+        return callback.call(thisArg, feature, layer);
+      });
 };
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getTrackingOptions',
-    ol.Geolocation.prototype.getTrackingOptions);
 
 
 /**
- * Set the projection to use for transforming the coordinates.
- * @param {ol.proj.Projection} projection The projection the position is
- *     reported in.
- * @observable
- * @api stable
+ * @inheritDoc
  */
-ol.Geolocation.prototype.setProjection = function(projection) {
-  this.set(ol.GeolocationProperty.PROJECTION, projection);
+ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
+  return goog.isNull(this.image_) ?
+      null : this.image_.getImage();
 };
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'setProjection',
-    ol.Geolocation.prototype.setProjection);
 
 
 /**
- * Enable/disable tracking.
- * @param {boolean} tracking Whether to track the device's position.
- * @observable
- * @api stable
+ * @inheritDoc
  */
-ol.Geolocation.prototype.setTracking = function(tracking) {
-  this.set(ol.GeolocationProperty.TRACKING, tracking);
+ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() {
+  return this.imageTransform_;
 };
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'setTracking',
-    ol.Geolocation.prototype.setTracking);
 
 
 /**
- * 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 at
- *     {@link http://www.w3.org/TR/geolocation-API/#position_options_interface}
- * @observable
- * @api stable
+ * @inheritDoc
  */
-ol.Geolocation.prototype.setTrackingOptions = function(options) {
-  this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options);
-};
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'setTrackingOptions',
-    ol.Geolocation.prototype.setTrackingOptions);
+ol.renderer.canvas.ImageLayer.prototype.prepareFrame =
+    function(frameState, layerState) {
 
-goog.provide('ol.geom.flat.flip');
+  var pixelRatio = frameState.pixelRatio;
+  var viewState = frameState.viewState;
+  var viewCenter = viewState.center;
+  var viewResolution = viewState.resolution;
+  var viewRotation = viewState.rotation;
 
-goog.require('goog.asserts');
+  var image;
+  var imageLayer = this.getLayer();
+  goog.asserts.assertInstanceof(imageLayer, ol.layer.Image);
+  var imageSource = imageLayer.getSource();
 
+  var hints = frameState.viewHints;
 
-/**
- * @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 (goog.isDef(opt_dest)) {
-    dest = opt_dest;
-    destOffset = goog.isDef(opt_destOffset) ? opt_destOffset : 0;
-  } else {
-    goog.asserts.assert(!goog.isDef(opt_destOffset));
-    dest = [];
-    destOffset = 0;
+  var renderedExtent = frameState.extent;
+  if (goog.isDef(layerState.extent)) {
+    renderedExtent = ol.extent.getIntersection(
+        renderedExtent, layerState.extent);
   }
-  var j, k;
-  for (j = offset; j < end; ) {
-    var x = flatCoordinates[j++];
-    dest[destOffset++] = flatCoordinates[j++];
-    dest[destOffset++] = x;
-    for (k = 2; k < stride; ++k) {
-      dest[destOffset++] = flatCoordinates[j++];
+
+  if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
+      !ol.extent.isEmpty(renderedExtent)) {
+    var projection = viewState.projection;
+    var sourceProjection = imageSource.getProjection();
+    if (!goog.isNull(sourceProjection)) {
+      goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection));
+      projection = sourceProjection;
+    }
+    image = imageSource.getImage(
+        renderedExtent, viewResolution, pixelRatio, projection);
+    if (!goog.isNull(image)) {
+      var imageState = image.getState();
+      if (imageState == ol.ImageState.IDLE) {
+        goog.events.listenOnce(image, goog.events.EventType.CHANGE,
+            this.handleImageChange, false, this);
+        image.load();
+      } else if (imageState == ol.ImageState.LOADED) {
+        this.image_ = image;
+      }
     }
   }
-  dest.length = destOffset;
-  return dest;
+
+  if (!goog.isNull(this.image_)) {
+    image = this.image_;
+    var imageExtent = image.getExtent();
+    var imageResolution = image.getResolution();
+    var imagePixelRatio = image.getPixelRatio();
+    var scale = pixelRatio * imageResolution /
+        (viewResolution * imagePixelRatio);
+    ol.vec.Mat4.makeTransform2D(this.imageTransform_,
+        pixelRatio * frameState.size[0] / 2,
+        pixelRatio * frameState.size[1] / 2,
+        scale, scale,
+        viewRotation,
+        imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
+        imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
+    this.updateAttributions(frameState.attributions, image.getAttributions());
+    this.updateLogos(frameState, imageSource);
+  }
+
+  return true;
 };
 
-goog.provide('ol.geom.flat.geodesic');
+// FIXME find correct globalCompositeOperation
+// FIXME optimize :-)
+
+goog.provide('ol.renderer.canvas.TileLayer');
 
+goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.math');
 goog.require('goog.object');
-goog.require('ol.TransformFunction');
-goog.require('ol.math');
-goog.require('ol.proj');
+goog.require('goog.vec.Mat4');
+goog.require('ol.Size');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.layer.Tile');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.tilecoord');
+goog.require('ol.vec.Mat4');
+
 
 
 /**
- * @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.
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Tile} tileLayer Tile layer.
  */
-ol.geom.flat.geodesic.line_ =
-    function(interpolate, transform, squaredTolerance) {
-  // FIXME reduce garbage generation
-  // FIXME optimize stack operations
+ol.renderer.canvas.TileLayer = function(mapRenderer, tileLayer) {
 
-  /** @type {Array.<number>} */
-  var flatCoordinates = [];
+  goog.base(this, mapRenderer, tileLayer);
 
-  var geoA = interpolate(0);
-  var geoB = interpolate(1);
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = null;
 
-  var a = transform(geoA);
-  var b = transform(geoB);
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.canvasSize_ = null;
 
-  /** @type {Array.<ol.Coordinate>} */
-  var geoStack = [geoB, geoA];
-  /** @type {Array.<ol.Coordinate>} */
-  var stack = [b, a];
-  /** @type {Array.<number>} */
-  var fractionStack = [1, 0];
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.canvasTooBig_ = false;
 
-  /** @type {Object.<number, boolean>} */
-  var fractions = {};
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = null;
 
-  var maxIterations = 1e5;
-  var geoM, m, fracA, fracB, fracM, key;
+  /**
+   * @private
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.imageTransform_ = goog.vec.Mat4.createNumber();
 
-  while (--maxIterations > 0 && fractionStack.length > 0) {
-    // Pop the a coordinate off the stack
-    fracA = fractionStack.pop();
-    geoA = geoStack.pop();
-    a = stack.pop();
-    // Add the a coordinate if it has not been added yet
-    key = fracA.toString();
-    if (!goog.object.containsKey(fractions, key)) {
-      flatCoordinates.push(a[0], a[1]);
-      goog.object.set(fractions, key, true);
-    }
-    // Pop the b coordinate off the stack
-    fracB = fractionStack.pop();
-    geoB = geoStack.pop();
-    b = stack.pop();
-    // Find the m point between the a and b coordinates
-    fracM = (fracA + fracB) / 2;
-    geoM = interpolate(fracM);
-    m = transform(geoM);
-    if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1],
-        b[0], b[1]) < squaredTolerance) {
-      // If the m point is sufficiently close to the straight line, then we
-      // discard it.  Just use the b coordinate and move on to the next line
-      // segment.
-      flatCoordinates.push(b[0], b[1]);
-      key = fracB.toString();
-      goog.asserts.assert(!goog.object.containsKey(fractions, key));
-      goog.object.set(fractions, key, true);
-    } else {
-      // Otherwise, we need to subdivide the current line segment.  Split it
-      // into two and push the two line segments onto the stack.
-      fractionStack.push(fracB, fracM, fracM, fracA);
-      stack.push(b, m, m, a);
-      geoStack.push(geoB, geoM, geoM, geoA);
-    }
-  }
-  goog.asserts.assert(maxIterations > 0);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedCanvasZ_ = NaN;
+
+  /**
+   * @private
+   * @type {ol.TileRange}
+   */
+  this.renderedCanvasTileRange_ = null;
+
+  /**
+   * @private
+   * @type {Array.<ol.Tile|undefined>}
+   */
+  this.renderedTiles_ = null;
 
-  return flatCoordinates;
 };
+goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer);
 
 
 /**
-* 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');
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.getImage = function() {
+  return this.canvas_;
+};
 
-  var cosLat1 = Math.cos(goog.math.toRadians(lat1));
-  var sinLat1 = Math.sin(goog.math.toRadians(lat1));
-  var cosLat2 = Math.cos(goog.math.toRadians(lat2));
-  var sinLat2 = Math.sin(goog.math.toRadians(lat2));
-  var cosDeltaLon = Math.cos(goog.math.toRadians(lon2 - lon1));
-  var sinDeltaLon = Math.sin(goog.math.toRadians(lon2 - lon1));
-  var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon;
 
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @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 = goog.math.toRadians(lon1) +
-            Math.atan2(Math.sin(theta) * sinD * cosLat1,
-                       cosD - sinLat1 * Math.sin(lat));
-        return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)];
-      }, ol.proj.getTransform(geoProjection, projection), squaredTolerance);
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() {
+  return this.imageTransform_;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-ol.geom.flat.geodesic.meridian =
-    function(lon, lat1, lat2, projection, squaredTolerance) {
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
-       */
-      function(frac) {
-        return [lon, lat1 + ((lat2 - lat1) * frac)];
-      },
-      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
-};
+ol.renderer.canvas.TileLayer.prototype.prepareFrame =
+    function(frameState, layerState) {
 
+  //
+  // Warning! You're entering a dangerous zone!
+  //
+  // The canvas tile layer renderering is highly optimized, hence
+  // the complexity of this function. For best performance we try
+  // to minimize the number of pixels to update on the canvas. This
+  // includes:
+  //
+  // - Only drawing pixels that will be visible.
+  // - Not re-drawing pixels/tiles that are already correct.
+  // - Minimizing calls to clearRect.
+  // - Never shrink the canvas. Just make it bigger when necessary.
+  //   Re-sizing the canvas also clears it, which further means
+  //   re-creating it (expensive).
+  //
+  // The various steps performed by this functions:
+  //
+  // - Create a canvas element if none has been created yet.
+  //
+  // - Make the canvas bigger if it's too small. Note that we never shrink
+  //   the canvas, we just make it bigger when necessary, when rotating for
+  //   example. Note also that the canvas always contains a whole number
+  //   of tiles.
+  //
+  // - Invalidate the canvas tile range (renderedCanvasTileRange_ = null)
+  //   if (1) the canvas has been enlarged, or (2) the zoom level changes,
+  //   or (3) the canvas tile range doesn't contain the required tile
+  //   range. This canvas tile range invalidation thing is related to
+  //   an optimization where we attempt to redraw as few pixels as
+  //   possible on each prepareFrame call.
+  //
+  // - If the canvas tile range has been invalidated we reset
+  //   renderedCanvasTileRange_ and reset the renderedTiles_ array.
+  //   The renderedTiles_ array is the structure used to determine
+  //   the canvas pixels that need not be redrawn from one prepareFrame
+  //   call to another. It records while tile has been rendered at
+  //   which position in the canvas.
+  //
+  // - We then determine the tiles to draw on the canvas. Tiles for
+  //   the target resolution may not be loaded yet. In that case we
+  //   use low-resolution/interim tiles if loaded already. And, if
+  //   for a non-yet-loaded tile we haven't found a corresponding
+  //   low-resolution tile we indicate that the pixels for that
+  //   tile must be cleared on the canvas. Note: determining the
+  //   interim tiles is based on tile extents instead of tile
+  //   coords, this is to be able to handler irregular tile grids.
+  //
+  // - We're now ready to render. We start by calling clearRect
+  //   for the tiles that aren't loaded yet and are not fully covered
+  //   by a low-resolution tile (if they're loaded, we'll draw them;
+  //   if they're fully covered by a low-resolution tile then there's
+  //   no need to clear). We then render the tiles "back to front",
+  //   i.e. starting with the low-resolution tiles.
+  //
+  // - After rendering some bookkeeping is performed (updateUsedTiles,
+  //   etc.). manageTilePyramid is what enqueue tiles in the tile
+  //   queue for loading.
+  //
+  // - The last step involves updating the image transform matrix,
+  //   which will be used by the map renderer for the final
+  //   composition and positioning.
+  //
 
-/**
- * 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);
-};
+  var pixelRatio = frameState.pixelRatio;
+  var viewState = frameState.viewState;
+  var projection = viewState.projection;
 
-goog.provide('ol.Graticule');
+  var tileLayer = this.getLayer();
+  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
+  var tileSource = tileLayer.getSource();
+  var tileGrid = tileSource.getTileGridForProjection(projection);
+  var tileGutter = tileSource.getGutter();
+  var z = tileGrid.getZForResolution(viewState.resolution);
+  var tilePixelSize =
+      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
+  var tilePixelRatio = tilePixelSize / tileGrid.getTileSize(z);
+  var tileResolution = tileGrid.getResolution(z);
+  var tilePixelResolution = tileResolution / tilePixelRatio;
+  var center = viewState.center;
+  var extent;
+  if (tileResolution == viewState.resolution) {
+    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
+    extent = ol.extent.getForViewAndSize(
+        center, tileResolution, viewState.rotation, frameState.size);
+  } else {
+    extent = frameState.extent;
+  }
 
-goog.require('goog.asserts');
-goog.require('goog.math');
-goog.require('ol.extent');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.flat.geodesic');
-goog.require('ol.proj');
-goog.require('ol.render.EventType');
-goog.require('ol.style.Stroke');
+  if (goog.isDef(layerState.extent)) {
+    extent = ol.extent.getIntersection(extent, layerState.extent);
+  }
+  if (ol.extent.isEmpty(extent)) {
+    // Return false to prevent the rendering of the layer.
+    return false;
+  }
 
+  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
+      extent, tileResolution);
 
+  var canvasWidth = tilePixelSize * tileRange.getWidth();
+  var canvasHeight = tilePixelSize * tileRange.getHeight();
 
-/**
- * @constructor
- * @param {olx.GraticuleOptions=} opt_options Options.
- * @api
- */
-ol.Graticule = function(opt_options) {
+  var canvas, context;
+  if (goog.isNull(this.canvas_)) {
+    goog.asserts.assert(goog.isNull(this.canvasSize_));
+    goog.asserts.assert(goog.isNull(this.context_));
+    goog.asserts.assert(goog.isNull(this.renderedCanvasTileRange_));
+    context = ol.dom.createCanvasContext2D(canvasWidth, canvasHeight);
+    this.canvas_ = context.canvas;
+    this.canvasSize_ = [canvasWidth, canvasHeight];
+    this.context_ = context;
+    this.canvasTooBig_ =
+        !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_);
+  } else {
+    goog.asserts.assert(!goog.isNull(this.canvasSize_));
+    goog.asserts.assert(!goog.isNull(this.context_));
+    canvas = this.canvas_;
+    context = this.context_;
+    if (this.canvasSize_[0] < canvasWidth ||
+        this.canvasSize_[1] < canvasHeight ||
+        (this.canvasTooBig_ && (this.canvasSize_[0] > canvasWidth ||
+        this.canvasSize_[1] > canvasHeight))) {
+      // Canvas is too small, resize it. We never shrink the canvas, unless
+      // we know that the current canvas size exceeds the maximum size
+      canvas.width = canvasWidth;
+      canvas.height = canvasHeight;
+      this.canvasSize_ = [canvasWidth, canvasHeight];
+      this.canvasTooBig_ =
+          !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_);
+      this.renderedCanvasTileRange_ = null;
+    } else {
+      canvasWidth = this.canvasSize_[0];
+      canvasHeight = this.canvasSize_[1];
+      if (z != this.renderedCanvasZ_ ||
+          !this.renderedCanvasTileRange_.containsTileRange(tileRange)) {
+        this.renderedCanvasTileRange_ = null;
+      }
+    }
+  }
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  var canvasTileRange, canvasTileRangeWidth, minX, minY;
+  if (goog.isNull(this.renderedCanvasTileRange_)) {
+    canvasTileRangeWidth = canvasWidth / tilePixelSize;
+    var canvasTileRangeHeight = canvasHeight / tilePixelSize;
+    minX = tileRange.minX -
+        Math.floor((canvasTileRangeWidth - tileRange.getWidth()) / 2);
+    minY = tileRange.minY -
+        Math.floor((canvasTileRangeHeight - tileRange.getHeight()) / 2);
+    this.renderedCanvasZ_ = z;
+    this.renderedCanvasTileRange_ = new ol.TileRange(
+        minX, minX + canvasTileRangeWidth - 1,
+        minY, minY + canvasTileRangeHeight - 1);
+    this.renderedTiles_ =
+        new Array(canvasTileRangeWidth * canvasTileRangeHeight);
+    canvasTileRange = this.renderedCanvasTileRange_;
+  } else {
+    canvasTileRange = this.renderedCanvasTileRange_;
+    canvasTileRangeWidth = canvasTileRange.getWidth();
+  }
 
-  /**
-   * @type {ol.Map}
-   * @private
-   */
-  this.map_ = null;
+  goog.asserts.assert(canvasTileRange.containsTileRange(tileRange));
 
   /**
-   * @type {ol.proj.Projection}
-   * @private
+   * @type {Object.<number, Object.<string, ol.Tile>>}
    */
-  this.projection_ = null;
+  var tilesToDrawByZ = {};
+  tilesToDrawByZ[z] = {};
+  /** @type {Array.<ol.Tile>} */
+  var tilesToClear = [];
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLat_ = Infinity;
+  var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
+    return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED;
+  }, tileSource, pixelRatio, projection);
+  var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
+      tilesToDrawByZ, getTileIfLoaded);
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLon_ = Infinity;
+  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
+  if (!goog.isDef(useInterimTilesOnError)) {
+    useInterimTilesOnError = true;
+  }
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLat_ = -Infinity;
+  var tmpExtent = ol.extent.createEmpty();
+  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
+  var childTileRange, fullyLoaded, tile, tileState, x, y;
+  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
+    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLon_ = -Infinity;
+      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
+      tileState = tile.getState();
+      if (tileState == ol.TileState.LOADED ||
+          tileState == ol.TileState.EMPTY ||
+          (tileState == ol.TileState.ERROR && !useInterimTilesOnError)) {
+        tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
+        continue;
+      }
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.targetSize_ = goog.isDef(options.targetSize) ?
-      options.targetSize : 100;
+      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
+          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
+      if (!fullyLoaded) {
+        // FIXME we do not need to clear the tile if it is fully covered by its
+        //       children
+        tilesToClear.push(tile);
+        childTileRange = tileGrid.getTileCoordChildTileRange(
+            tile.tileCoord, tmpTileRange, tmpExtent);
+        if (!goog.isNull(childTileRange)) {
+          findLoadedTiles(z + 1, childTileRange);
+        }
+      }
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLines_ = goog.isDef(options.maxLines) ? options.maxLines : 100;
-  goog.asserts.assert(this.maxLines_ > 0);
+    }
+  }
 
-  /**
-   * @type {Array.<ol.geom.LineString>}
-   * @private
-   */
-  this.meridians_ = [];
+  var i, ii;
+  for (i = 0, ii = tilesToClear.length; i < ii; ++i) {
+    tile = tilesToClear[i];
+    x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX);
+    y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]);
+    context.clearRect(x, y, tilePixelSize, tilePixelSize);
+  }
 
-  /**
-   * @type {Array.<ol.geom.LineString>}
-   * @private
-   */
-  this.parallels_ = [];
+  /** @type {Array.<number>} */
+  var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
+  goog.array.sort(zs);
+  var opaque = tileSource.getOpaque();
+  var origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent(
+      [z, canvasTileRange.minX, canvasTileRange.maxY],
+      tmpExtent));
+  var currentZ, index, scale, tileCoordKey, tileExtent, tilesToDraw;
+  var ix, iy, interimTileExtent, interimTileRange, maxX, maxY;
+  var height, width;
+  for (i = 0, ii = zs.length; i < ii; ++i) {
+    currentZ = zs[i];
+    tilePixelSize =
+        tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
+    tilesToDraw = tilesToDrawByZ[currentZ];
+    if (currentZ == z) {
+      for (tileCoordKey in tilesToDraw) {
+        tile = tilesToDraw[tileCoordKey];
+        index =
+            (tile.tileCoord[2] - canvasTileRange.minY) * canvasTileRangeWidth +
+            (tile.tileCoord[1] - canvasTileRange.minX);
+        if (this.renderedTiles_[index] != tile) {
+          x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX);
+          y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]);
+          tileState = tile.getState();
+          if (tileState == ol.TileState.EMPTY ||
+              (tileState == ol.TileState.ERROR && !useInterimTilesOnError) ||
+              !opaque) {
+            context.clearRect(x, y, tilePixelSize, tilePixelSize);
+          }
+          if (tileState == ol.TileState.LOADED) {
+            context.drawImage(tile.getImage(),
+                tileGutter, tileGutter, tilePixelSize, tilePixelSize,
+                x, y, tilePixelSize, tilePixelSize);
+          }
+          this.renderedTiles_[index] = tile;
+        }
+      }
+    } else {
+      scale = tileGrid.getResolution(currentZ) / tileResolution;
+      for (tileCoordKey in tilesToDraw) {
+        tile = tilesToDraw[tileCoordKey];
+        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
+        x = (tileExtent[0] - origin[0]) / tilePixelResolution;
+        y = (origin[1] - tileExtent[3]) / tilePixelResolution;
+        width = scale * tilePixelSize;
+        height = scale * tilePixelSize;
+        tileState = tile.getState();
+        if (tileState == ol.TileState.EMPTY || !opaque) {
+          context.clearRect(x, y, width, height);
+        }
+        if (tileState == ol.TileState.LOADED) {
+          context.drawImage(tile.getImage(),
+              tileGutter, tileGutter, tilePixelSize, tilePixelSize,
+              x, y, width, height);
+        }
+        interimTileRange =
+            tileGrid.getTileRangeForExtentAndZ(tileExtent, z, tmpTileRange);
+        minX = Math.max(interimTileRange.minX, canvasTileRange.minX);
+        maxX = Math.min(interimTileRange.maxX, canvasTileRange.maxX);
+        minY = Math.max(interimTileRange.minY, canvasTileRange.minY);
+        maxY = Math.min(interimTileRange.maxY, canvasTileRange.maxY);
+        for (ix = minX; ix <= maxX; ++ix) {
+          for (iy = minY; iy <= maxY; ++iy) {
+            index = (iy - canvasTileRange.minY) * canvasTileRangeWidth +
+                    (ix - canvasTileRange.minX);
+            this.renderedTiles_[index] = undefined;
+          }
+        }
+      }
+    }
+  }
 
-  /**
-   * @type {ol.style.Stroke}
-   * @private
-   */
-  this.strokeStyle_ = goog.isDef(options.strokeStyle) ?
-      options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_;
+  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);
 
-  /**
-   * @type {ol.TransformFunction|undefined}
-   * @private
-   */
-  this.fromLonLatTransform_ = undefined;
+  ol.vec.Mat4.makeTransform2D(this.imageTransform_,
+      pixelRatio * frameState.size[0] / 2,
+      pixelRatio * frameState.size[1] / 2,
+      pixelRatio * tilePixelResolution / viewState.resolution,
+      pixelRatio * tilePixelResolution / viewState.resolution,
+      viewState.rotation,
+      (origin[0] - center[0]) / tilePixelResolution,
+      (center[1] - origin[1]) / tilePixelResolution);
 
-  /**
-   * @type {ol.TransformFunction|undefined}
-   * @private
-   */
-  this.toLonLatTransform_ = undefined;
+  return true;
+};
 
-  /**
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.projectionCenterLonLat_ = null;
+goog.provide('ol.geom.Circle');
+
+goog.require('goog.asserts');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.geom.flat.deflate');
+goog.require('ol.proj');
 
-  this.setMap(goog.isDef(options.map) ? options.map : null);
-};
 
 
 /**
- * @type {ol.style.Stroke}
- * @private
- * @const
+ * @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.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
-  color: 'rgba(0,0,0,0.2)'
-});
+ol.geom.Circle = function(center, opt_radius, opt_layout) {
+  goog.base(this);
+  var radius = goog.isDef(opt_radius) ? opt_radius : 0;
+  this.setCenterAndRadius(center, radius,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
+};
+goog.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);
 
 
 /**
- * TODO can be configurable
- * @type {Array.<number>}
- * @private
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Circle} Clone.
+ * @api
  */
-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];
+ol.geom.Circle.prototype.clone = function() {
+  var circle = new ol.geom.Circle(null);
+  circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return circle;
+};
 
 
 /**
- * @param {number} lon Longitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {ol.Extent} extent Extent.
- * @param {number} index Index.
- * @return {number} Index.
- * @private
+ * @inheritDoc
  */
-ol.Graticule.prototype.addMeridian_ =
-    function(lon, squaredTolerance, extent, index) {
-  var lineString = this.getMeridian_(lon, squaredTolerance, index);
-  if (ol.extent.intersects(lineString.getExtent(), extent)) {
-    this.meridians_[index++] = lineString;
+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;
   }
-  return index;
 };
 
 
 /**
- * @param {number} lat Latitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {ol.Extent} extent Extent.
- * @param {number} index Index.
- * @return {number} Index.
- * @private
+ * @inheritDoc
  */
-ol.Graticule.prototype.addParallel_ =
-    function(lat, squaredTolerance, extent, index) {
-  var lineString = this.getParallel_(lat, squaredTolerance, index);
-  if (ol.extent.intersects(lineString.getExtent(), extent)) {
-    this.parallels_[index++] = lineString;
-  }
-  return index;
+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_();
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} squaredTolerance Squared tolerance.
- * @private
+ * @return {ol.Coordinate} Center.
+ * @api
  */
-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;
-
-  // Create meridians
-
-  centerLon = Math.floor(centerLon / interval) * interval;
-  lon = goog.math.clamp(centerLon, this.minLon_, this.maxLon_);
-
-  idx = this.addMeridian_(lon, squaredTolerance, extent, 0);
-
-  cnt = 0;
-  while (lon != this.minLon_ && cnt++ < maxLines) {
-    lon = Math.max(lon - interval, this.minLon_);
-    idx = this.addMeridian_(lon, squaredTolerance, extent, idx);
-  }
-
-  lon = goog.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, squaredTolerance, extent, idx);
-  }
-
-  this.meridians_.length = idx;
-
-  // Create parallels
-
-  centerLat = Math.floor(centerLat / interval) * interval;
-  lat = goog.math.clamp(centerLat, this.minLat_, this.maxLat_);
-
-  idx = this.addParallel_(lat, squaredTolerance, extent, 0);
-
-  cnt = 0;
-  while (lat != this.minLat_ && cnt++ < maxLines) {
-    lat = Math.max(lat - interval, this.minLat_);
-    idx = this.addParallel_(lat, squaredTolerance, extent, idx);
-  }
-
-  lat = goog.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, squaredTolerance, extent, idx);
-  }
-
-  this.parallels_.length = idx;
-
+ol.geom.Circle.prototype.getCenter = function() {
+  return this.flatCoordinates.slice(0, this.stride);
 };
 
 
 /**
- * @param {number} resolution Resolution.
- * @return {number} The interval in degrees.
- * @private
+ * @inheritDoc
+ * @api
  */
-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];
+ol.geom.Circle.prototype.getExtent = function(opt_extent) {
+  if (this.extentRevision != this.getRevision()) {
+    var flatCoordinates = this.flatCoordinates;
+    var radius = flatCoordinates[this.stride] - flatCoordinates[0];
+    this.extent = ol.extent.createOrUpdate(
+        flatCoordinates[0] - radius, flatCoordinates[1] - radius,
+        flatCoordinates[0] + radius, flatCoordinates[1] + radius,
+        this.extent);
+    this.extentRevision = this.getRevision();
   }
-  return interval;
+  goog.asserts.assert(goog.isDef(this.extent));
+  return ol.extent.returnOrUpdate(this.extent, opt_extent);
 };
 
 
 /**
- * @return {ol.Map} The map.
+ * @return {number} Radius.
  * @api
  */
-ol.Graticule.prototype.getMap = function() {
-  return this.map_;
+ol.geom.Circle.prototype.getRadius = function() {
+  return Math.sqrt(this.getRadiusSquared_());
 };
 
 
 /**
- * @param {number} lon Longitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.LineString} The meridian line string.
- * @param {number} index Index.
  * @private
+ * @return {number} Radius squared.
  */
-ol.Graticule.prototype.getMeridian_ = function(lon, squaredTolerance, index) {
-  goog.asserts.assert(lon >= this.minLon_);
-  goog.asserts.assert(lon <= this.maxLon_);
-  var flatCoordinates = ol.geom.flat.geodesic.meridian(lon,
-      this.minLat_, this.maxLat_, this.projection_, squaredTolerance);
-  goog.asserts.assert(flatCoordinates.length > 0);
-  var lineString = goog.isDef(this.meridians_[index]) ?
-      this.meridians_[index] : new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
-  return lineString;
+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;
 };
 
 
 /**
- * @return {Array.<ol.geom.LineString>} The meridians.
+ * @inheritDoc
  * @api
  */
-ol.Graticule.prototype.getMeridians = function() {
-  return this.meridians_;
+ol.geom.Circle.prototype.getType = function() {
+  return ol.geom.GeometryType.CIRCLE;
 };
 
 
 /**
- * @param {number} lat Latitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.LineString} The parallel line string.
- * @param {number} index Index.
- * @private
+ * @param {ol.Coordinate} center Center.
+ * @api
  */
-ol.Graticule.prototype.getParallel_ = function(lat, squaredTolerance, index) {
-  goog.asserts.assert(lat >= this.minLat_);
-  goog.asserts.assert(lat <= this.maxLat_);
-  var flatCoordinates = ol.geom.flat.geodesic.parallel(lat,
-      this.minLon_, this.maxLon_, this.projection_, squaredTolerance);
-  goog.asserts.assert(flatCoordinates.length > 0);
-  var lineString = goog.isDef(this.parallels_[index]) ?
-      this.parallels_[index] : new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
-  return lineString;
+ol.geom.Circle.prototype.setCenter = function(center) {
+  var stride = this.stride;
+  goog.asserts.assert(center.length == 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);
 };
 
 
 /**
- * @return {Array.<ol.geom.LineString>} The parallels.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} radius Radius.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
  * @api
  */
-ol.Graticule.prototype.getParallels = function() {
-  return this.parallels_;
+ol.geom.Circle.prototype.setCenterAndRadius =
+    function(center, radius, opt_layout) {
+  if (goog.isNull(center)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+  } else {
+    this.setLayout(opt_layout, center, 0);
+    if (goog.isNull(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.render.Event} e Event.
- * @private
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-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 = goog.isNull(this.projection_) ||
-      !ol.proj.equivalent(this.projection_, projection);
-
-  if (updateProjectionInfo) {
-    this.updateProjectionInfo_(projection);
-  }
-
-  this.createGraticule_(extent, center, resolution, squaredTolerance);
-
-  // Draw the lines
-  vectorContext.setFillStrokeStyle(null, this.strokeStyle_);
-  var i, l, line;
-  for (i = 0, l = this.meridians_.length; i < l; ++i) {
-    line = this.meridians_[i];
-    vectorContext.drawLineStringGeometry(line, null);
-  }
-  for (i = 0, l = this.parallels_.length; i < l; ++i) {
-    line = this.parallels_[i];
-    vectorContext.drawLineStringGeometry(line, null);
-  }
+ol.geom.Circle.prototype.setFlatCoordinates =
+    function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
 
 /**
- * @param {ol.proj.Projection} projection Projection.
- * @private
+ * The radius is in the units of the projection.
+ * @param {number} radius Radius.
+ * @api
  */
-ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
-  goog.asserts.assert(!goog.isNull(projection));
-
-  var extent = projection.getExtent();
-  var worldExtent = projection.getWorldExtent();
-  var maxLat = worldExtent[3];
-  var maxLon = worldExtent[2];
-  var minLat = worldExtent[1];
-  var minLon = worldExtent[0];
-
-  goog.asserts.assert(!goog.isNull(extent));
-  goog.asserts.assert(goog.isDef(maxLat));
-  goog.asserts.assert(goog.isDef(maxLon));
-  goog.asserts.assert(goog.isDef(minLat));
-  goog.asserts.assert(goog.isDef(minLon));
-
-  this.maxLat_ = maxLat;
-  this.maxLon_ = maxLon;
-  this.minLat_ = minLat;
-  this.minLon_ = minLon;
-
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-
-  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;
+ol.geom.Circle.prototype.setRadius = function(radius) {
+  goog.asserts.assert(!goog.isNull(this.flatCoordinates));
+  this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
+  this.changed();
 };
 
 
 /**
- * @param {ol.Map} map Map.
- * @api
+ * Transform each coordinate of the circle from one coordinate reference system
+ * to another. The geometry is modified in place.
+ * If you do not want the geometry modified in place, first clone() it and
+ * then use this function on the clone.
+ *
+ * Internally a circle is currently represented by two points: the center of
+ * the circle `[cx, cy]`, and the point to the right of the circle
+ * `[cx + r, cy]`. This `transform` function just transforms these two points.
+ * So the resulting geometry is also a circle, and that circle does not
+ * correspond to the shape that would be obtained by transforming every point
+ * of the original circle.
+ *
+ * @param {ol.proj.ProjectionLike} source The current projection.  Can be a
+ *     string identifier or a {@link ol.proj.Projection} object.
+ * @param {ol.proj.ProjectionLike} destination The desired projection.  Can be a
+ *     string identifier or a {@link ol.proj.Projection} object.
+ * @return {ol.geom.Circle} This geometry.  Note that original geometry is
+ *     modified in place.
+ * @function
+ * @api stable
  */
-ol.Graticule.prototype.setMap = function(map) {
-  if (!goog.isNull(this.map_)) {
-    this.map_.un(ol.render.EventType.POSTCOMPOSE,
-        this.handlePostCompose_, this);
-    this.map_.render();
-  }
-  if (!goog.isNull(map)) {
-    map.on(ol.render.EventType.POSTCOMPOSE,
-        this.handlePostCompose_, this);
-    map.render();
-  }
-  this.map_ = map;
-};
+ol.geom.Circle.prototype.transform;
 
-goog.provide('ol.ImageBase');
-goog.provide('ol.ImageState');
+goog.provide('ol.geom.GeometryCollection');
 
-goog.require('goog.events.EventTarget');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
 goog.require('goog.events.EventType');
-goog.require('ol.Attribution');
-goog.require('ol.Extent');
-
-
-/**
- * @enum {number}
- */
-ol.ImageState = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3
-};
+goog.require('goog.object');
+goog.require('ol.extent');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryType');
 
 
 
 /**
+ * @classdesc
+ * An array of {@link ol.geom.Geometry} objects.
+ *
  * @constructor
- * @extends {goog.events.EventTarget}
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.ImageState} state State.
- * @param {Array.<ol.Attribution>} attributions Attributions.
+ * @extends {ol.geom.Geometry}
+ * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries.
+ * @api stable
  */
-ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) {
+ol.geom.GeometryCollection = function(opt_geometries) {
 
   goog.base(this);
 
   /**
    * @private
-   * @type {Array.<ol.Attribution>}
-   */
-  this.attributions_ = attributions;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = extent;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = pixelRatio;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.resolution_ = resolution;
-
-  /**
-   * @protected
-   * @type {ol.ImageState}
+   * @type {Array.<ol.geom.Geometry>}
    */
-  this.state = state;
+  this.geometries_ = goog.isDef(opt_geometries) ? opt_geometries : null;
 
+  this.listenGeometriesChange_();
 };
-goog.inherits(ol.ImageBase, goog.events.EventTarget);
+goog.inherits(ol.geom.GeometryCollection, ol.geom.Geometry);
 
 
 /**
- * @protected
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ * @private
+ * @return {Array.<ol.geom.Geometry>} Cloned geometries.
  */
-ol.ImageBase.prototype.dispatchChangeEvent = function() {
-  this.dispatchEvent(goog.events.EventType.CHANGE);
+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;
 };
 
 
 /**
- * @return {Array.<ol.Attribution>} Attributions.
+ * @private
  */
-ol.ImageBase.prototype.getAttributions = function() {
-  return this.attributions_;
+ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() {
+  var i, ii;
+  if (goog.isNull(this.geometries_)) {
+    return;
+  }
+  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
+    goog.events.unlisten(
+        this.geometries_[i], goog.events.EventType.CHANGE,
+        this.changed, false, this);
+  }
 };
 
 
 /**
- * @return {ol.Extent} Extent.
+ * @private
  */
-ol.ImageBase.prototype.getExtent = function() {
-  return this.extent_;
+ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() {
+  var i, ii;
+  if (goog.isNull(this.geometries_)) {
+    return;
+  }
+  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
+    goog.events.listen(
+        this.geometries_[i], goog.events.EventType.CHANGE,
+        this.changed, false, this);
+  }
 };
 
 
 /**
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.GeometryCollection} Clone.
+ * @api stable
  */
-ol.ImageBase.prototype.getImageElement = goog.abstractMethod;
+ol.geom.GeometryCollection.prototype.clone = function() {
+  var geometryCollection = new ol.geom.GeometryCollection(null);
+  geometryCollection.setGeometries(this.geometries_);
+  return geometryCollection;
+};
 
 
 /**
- * @return {number} PixelRatio.
+ * @inheritDoc
  */
-ol.ImageBase.prototype.getPixelRatio = function() {
-  return this.pixelRatio_;
+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;
 };
 
 
 /**
- * @return {number} Resolution.
+ * @inheritDoc
  */
-ol.ImageBase.prototype.getResolution = function() {
-  return this.resolution_;
+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;
 };
 
 
 /**
- * @return {ol.ImageState} State.
+ * @inheritDoc
+ * @api stable
  */
-ol.ImageBase.prototype.getState = function() {
-  return this.state;
+ol.geom.GeometryCollection.prototype.getExtent = function(opt_extent) {
+  if (this.extentRevision != this.getRevision()) {
+    var extent = ol.extent.createOrUpdateEmpty(this.extent);
+    var geometries = this.geometries_;
+    var i, ii;
+    for (i = 0, ii = geometries.length; i < ii; ++i) {
+      ol.extent.extend(extent, geometries[i].getExtent());
+    }
+    this.extent = extent;
+    this.extentRevision = this.getRevision();
+  }
+  goog.asserts.assert(goog.isDef(this.extent));
+  return ol.extent.returnOrUpdate(this.extent, opt_extent);
 };
 
 
 /**
- * Load not yet loaded URI.
+ * @return {Array.<ol.geom.Geometry>} Geometries.
+ * @api stable
  */
-ol.ImageBase.prototype.load = goog.abstractMethod;
-
-goog.provide('ol.Image');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
-
+ol.geom.GeometryCollection.prototype.getGeometries = function() {
+  return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_);
+};
 
 
 /**
- * @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 {string} src Image source URI.
- * @param {?string} crossOrigin Cross origin.
+ * @return {Array.<ol.geom.Geometry>} Geometries.
  */
-ol.Image =
-    function(extent, resolution, pixelRatio, attributions, src, crossOrigin) {
-
-  goog.base(this, extent, resolution, pixelRatio, ol.ImageState.IDLE,
-      attributions);
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.src_ = src;
-
-  /**
-   * @private
-   * @type {Image}
-   */
-  this.image_ = new Image();
-  if (!goog.isNull(crossOrigin)) {
-    this.image_.crossOrigin = crossOrigin;
-  }
-
-  /**
-   * @private
-   * @type {Object.<number, Image>}
-   */
-  this.imageByContext_ = {};
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.imageListenerKeys_ = null;
-
-  /**
-   * @protected
-   * @type {ol.ImageState}
-   */
-  this.state = ol.ImageState.IDLE;
-};
-goog.inherits(ol.Image, ol.ImageBase);
+ol.geom.GeometryCollection.prototype.getGeometriesArray = function() {
+  return this.geometries_;
+};
 
 
 /**
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
+ * @inheritDoc
  */
-ol.Image.prototype.getImageElement = function(opt_context) {
-  if (goog.isDef(opt_context)) {
-    var image;
-    var key = goog.getUid(opt_context);
-    if (key in this.imageByContext_) {
-      return this.imageByContext_[key];
-    } else if (goog.object.isEmpty(this.imageByContext_)) {
-      image = this.image_;
+ol.geom.GeometryCollection.prototype.getSimplifiedGeometry =
+    function(squaredTolerance) {
+  if (this.simplifiedGeometryRevision != this.getRevision()) {
+    goog.object.clear(this.simplifiedGeometryCache);
+    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
+    this.simplifiedGeometryRevision = this.getRevision();
+  }
+  if (squaredTolerance < 0 ||
+      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
+       squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) {
+    return this;
+  }
+  var key = squaredTolerance.toString();
+  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
+    return this.simplifiedGeometryCache[key];
+  } else {
+    var simplifiedGeometries = [];
+    var geometries = this.geometries_;
+    var simplified = false;
+    var i, ii;
+    for (i = 0, ii = geometries.length; i < ii; ++i) {
+      var geometry = geometries[i];
+      var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
+      simplifiedGeometries.push(simplifiedGeometry);
+      if (simplifiedGeometry !== geometry) {
+        simplified = true;
+      }
+    }
+    if (simplified) {
+      var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null);
+      simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries);
+      this.simplifiedGeometryCache[key] = simplifiedGeometryCollection;
+      return simplifiedGeometryCollection;
     } else {
-      image = /** @type {Image} */ (this.image_.cloneNode(false));
+      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
+      return this;
     }
-    this.imageByContext_[key] = image;
-    return image;
-  } else {
-    return this.image_;
   }
 };
 
 
 /**
- * Tracks loading or read errors.
- *
- * @private
- */
-ol.Image.prototype.handleImageError_ = function() {
-  this.state = ol.ImageState.ERROR;
-  this.unlistenImage_();
-  this.dispatchChangeEvent();
-};
-
-
-/**
- * Tracks successful image load.
- *
- * @private
+ * @inheritDoc
+ * @api stable
  */
-ol.Image.prototype.handleImageLoad_ = function() {
-  this.state = ol.ImageState.LOADED;
-  this.unlistenImage_();
-  this.dispatchChangeEvent();
+ol.geom.GeometryCollection.prototype.getType = function() {
+  return ol.geom.GeometryType.GEOMETRY_COLLECTION;
 };
 
 
 /**
- * Load not yet loaded URI.
+ * @inheritDoc
+ * @api
  */
-ol.Image.prototype.load = function() {
-  if (this.state == ol.ImageState.IDLE) {
-    this.state = ol.ImageState.LOADING;
-    goog.asserts.assert(goog.isNull(this.imageListenerKeys_));
-    this.imageListenerKeys_ = [
-      goog.events.listenOnce(this.image_, goog.events.EventType.ERROR,
-          this.handleImageError_, false, this),
-      goog.events.listenOnce(this.image_, goog.events.EventType.LOAD,
-          this.handleImageLoad_, false, this)
-    ];
-    this.image_.src = this.src_;
+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;
 };
 
 
 /**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
+ * @return {boolean} Is empty.
  */
-ol.Image.prototype.unlistenImage_ = function() {
-  goog.asserts.assert(!goog.isNull(this.imageListenerKeys_));
-  goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
+ol.geom.GeometryCollection.prototype.isEmpty = function() {
+  return goog.array.isEmpty(this.geometries_);
 };
 
-goog.provide('ol.ImageCanvas');
-
-goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
-
-
 
 /**
- * @constructor
- * @extends {ol.ImageBase}
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {Array.<ol.Attribution>} attributions Attributions.
- * @param {HTMLCanvasElement} canvas Canvas.
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
+ * @api stable
  */
-ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions,
-    canvas) {
-
-  goog.base(this, extent, resolution, pixelRatio, ol.ImageState.LOADED,
-      attributions);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = canvas;
-
+ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) {
+  this.setGeometriesArray(
+      ol.geom.GeometryCollection.cloneGeometries_(geometries));
 };
-goog.inherits(ol.ImageCanvas, ol.ImageBase);
 
 
 /**
- * @inheritDoc
+ * @param {Array.<ol.geom.Geometry>} geometries Geometries.
  */
-ol.ImageCanvas.prototype.getImageElement = function(opt_context) {
-  return this.canvas_;
+ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) {
+  this.unlistenGeometriesChange_();
+  this.geometries_ = geometries;
+  this.listenGeometriesChange_();
+  this.changed();
 };
 
-goog.provide('ol.Tile');
-goog.provide('ol.TileState');
-
-goog.require('goog.events');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
-goog.require('ol.TileCoord');
-
 
 /**
- * @enum {number}
+ * @inheritDoc
+ * @api stable
  */
-ol.TileState = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3,
-  EMPTY: 4
+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();
 };
 
 
-
 /**
- * @classdesc
- * Base class for tiles.
- *
- * @constructor
- * @extends {goog.events.EventTarget}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.TileState} state State.
+ * Translate the geometry.
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ * @api
  */
-ol.Tile = function(tileCoord, state) {
-
-  goog.base(this);
-
-  /**
-   * @type {ol.TileCoord}
-   */
-  this.tileCoord = tileCoord;
-
-  /**
-   * @protected
-   * @type {ol.TileState}
-   */
-  this.state = state;
-
+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();
 };
-goog.inherits(ol.Tile, goog.events.EventTarget);
 
 
 /**
- * @protected
+ * @inheritDoc
  */
-ol.Tile.prototype.dispatchChangeEvent = function() {
-  this.dispatchEvent(goog.events.EventType.CHANGE);
+ol.geom.GeometryCollection.prototype.disposeInternal = function() {
+  this.unlistenGeometriesChange_();
+  goog.base(this, 'disposeInternal');
 };
 
+goog.provide('ol.geom.flat.interpolate');
 
-/**
- * @function
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
- */
-ol.Tile.prototype.getImage = goog.abstractMethod;
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.math');
 
 
 /**
- * @return {string} Key.
+ * @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.Tile.prototype.getKey = function() {
-  return goog.getUid(this).toString();
+ol.geom.flat.interpolate.lineString =
+    function(flatCoordinates, offset, end, stride, fraction, opt_dest) {
+  // FIXME interpolate extra dimensions
+  goog.asserts.assert(0 <= fraction && fraction <= 1);
+  var pointX = NaN;
+  var pointY = NaN;
+  var n = (end - offset) / stride;
+  if (n === 0) {
+    goog.asserts.fail();
+  } else if (n == 1) {
+    pointX = flatCoordinates[offset];
+    pointY = flatCoordinates[offset + 1];
+  } else if (n == 2) {
+    pointX = (1 - fraction) * flatCoordinates[offset] +
+        fraction * flatCoordinates[offset + stride];
+    pointY = (1 - fraction) * flatCoordinates[offset + 1] +
+        fraction * flatCoordinates[offset + stride + 1];
+  } else {
+    var x1 = flatCoordinates[offset];
+    var y1 = flatCoordinates[offset + 1];
+    var length = 0;
+    var cumulativeLengths = [0];
+    var i;
+    for (i = offset + stride; i < end; i += stride) {
+      var x2 = flatCoordinates[i];
+      var y2 = flatCoordinates[i + 1];
+      length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
+      cumulativeLengths.push(length);
+      x1 = x2;
+      y1 = y2;
+    }
+    var target = fraction * length;
+    var index = goog.array.binarySearch(cumulativeLengths, target);
+    if (index < 0) {
+      var t = (target - cumulativeLengths[-index - 2]) /
+          (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]);
+      var o = offset + (-index - 2) * stride;
+      pointX = goog.math.lerp(
+          flatCoordinates[o], flatCoordinates[o + stride], t);
+      pointY = goog.math.lerp(
+          flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t);
+    } else {
+      pointX = flatCoordinates[offset + index * stride];
+      pointY = flatCoordinates[offset + index * stride + 1];
+    }
+  }
+  if (goog.isDefAndNotNull(opt_dest)) {
+    opt_dest[0] = pointX;
+    opt_dest[1] = pointY;
+    return opt_dest;
+  } else {
+    return [pointX, pointY];
+  }
 };
 
 
 /**
- * @return {ol.TileCoord}
- * @api
+ * @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.Tile.prototype.getTileCoord = function() {
-  return this.tileCoord;
+ol.geom.flat.lineStringCoordinateAtM =
+    function(flatCoordinates, offset, end, stride, m, extrapolate) {
+  if (end == offset) {
+    return null;
+  }
+  var coordinate;
+  if (m < flatCoordinates[offset + stride - 1]) {
+    if (extrapolate) {
+      coordinate = flatCoordinates.slice(offset, offset + stride);
+      coordinate[stride - 1] = m;
+      return coordinate;
+    } else {
+      return null;
+    }
+  } else if (flatCoordinates[end - 1] < m) {
+    if (extrapolate) {
+      coordinate = flatCoordinates.slice(end - stride, end);
+      coordinate[stride - 1] = m;
+      return coordinate;
+    } else {
+      return null;
+    }
+  }
+  // FIXME use O(1) search
+  if (m == flatCoordinates[offset + stride - 1]) {
+    return flatCoordinates.slice(offset, offset + stride);
+  }
+  var lo = offset / stride;
+  var hi = end / stride;
+  while (lo < hi) {
+    var mid = (lo + hi) >> 1;
+    if (m < flatCoordinates[(mid + 1) * stride - 1]) {
+      hi = mid;
+    } else {
+      lo = mid + 1;
+    }
+  }
+  var m0 = flatCoordinates[lo * stride - 1];
+  if (m == m0) {
+    return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride);
+  }
+  var m1 = flatCoordinates[(lo + 1) * stride - 1];
+  goog.asserts.assert(m0 < m);
+  goog.asserts.assert(m <= m1);
+  var t = (m - m0) / (m1 - m0);
+  coordinate = [];
+  var i;
+  for (i = 0; i < stride - 1; ++i) {
+    coordinate.push(goog.math.lerp(flatCoordinates[(lo - 1) * stride + i],
+        flatCoordinates[lo * stride + i], t));
+  }
+  coordinate.push(m);
+  goog.asserts.assert(coordinate.length == stride);
+  return coordinate;
 };
 
 
 /**
- * @return {ol.TileState} State.
+ * @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.Tile.prototype.getState = function() {
-  return this.state;
+ol.geom.flat.lineStringsCoordinateAtM = function(
+    flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) {
+  if (interpolate) {
+    return ol.geom.flat.lineStringCoordinateAtM(
+        flatCoordinates, offset, ends[ends.length - 1], stride, m, extrapolate);
+  }
+  var coordinate;
+  if (m < flatCoordinates[stride - 1]) {
+    if (extrapolate) {
+      coordinate = flatCoordinates.slice(0, stride);
+      coordinate[stride - 1] = m;
+      return coordinate;
+    } else {
+      return null;
+    }
+  }
+  if (flatCoordinates[flatCoordinates.length - 1] < m) {
+    if (extrapolate) {
+      coordinate = flatCoordinates.slice(flatCoordinates.length - stride);
+      coordinate[stride - 1] = m;
+      return coordinate;
+    } else {
+      return null;
+    }
+  }
+  var i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    var end = ends[i];
+    if (offset == end) {
+      continue;
+    }
+    if (m < flatCoordinates[offset + stride - 1]) {
+      return null;
+    } else if (m <= flatCoordinates[end - 1]) {
+      return ol.geom.flat.lineStringCoordinateAtM(
+          flatCoordinates, offset, end, stride, m, false);
+    }
+    offset = end;
+  }
+  goog.asserts.fail();
+  return null;
 };
 
+goog.provide('ol.geom.flat.length');
+
 
 /**
- * FIXME empty description for jsdoc
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Length.
  */
-ol.Tile.prototype.load = goog.abstractMethod;
-
-goog.provide('ol.TileLoadFunctionType');
+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;
+};
 
 
 /**
- * A function that takes an {@link ol.ImageTile} for the image tile and a
- * `{string}` for the src as arguments.
- *
- * @typedef {function(ol.ImageTile, string)}
- * @api
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Perimeter.
  */
-ol.TileLoadFunctionType;
+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.ImageTile');
+goog.provide('ol.geom.LineString');
 
 goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol.Tile');
-goog.require('ol.TileCoord');
-goog.require('ol.TileLoadFunctionType');
-goog.require('ol.TileState');
+goog.require('ol.extent');
+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.simplify');
 
 
 
 /**
+ * @classdesc
+ * Linestring geometry.
+ *
  * @constructor
- * @extends {ol.Tile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.TileState} state State.
- * @param {string} src Image source URI.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) {
+ol.geom.LineString = function(coordinates, opt_layout) {
 
-  goog.base(this, tileCoord, state);
+  goog.base(this);
 
   /**
-   * Image URI
-   *
    * @private
-   * @type {string}
+   * @type {ol.Coordinate}
    */
-  this.src_ = src;
+  this.flatMidpoint_ = null;
 
   /**
    * @private
-   * @type {Image}
+   * @type {number}
    */
-  this.image_ = new Image();
-  if (!goog.isNull(crossOrigin)) {
-    this.image_.crossOrigin = crossOrigin;
-  }
+  this.flatMidpointRevision_ = -1;
 
   /**
    * @private
-   * @type {Object.<number, Image>}
+   * @type {number}
    */
-  this.imageByContext_ = {};
+  this.maxDelta_ = -1;
 
   /**
    * @private
-   * @type {Array.<number>}
+   * @type {number}
    */
-  this.imageListenerKeys_ = null;
+  this.maxDeltaRevision_ = -1;
 
-  /**
-   * @private
-   * @type {ol.TileLoadFunctionType}
-   */
-  this.tileLoadFunction_ = tileLoadFunction;
+  this.setCoordinates(coordinates,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
 
 };
-goog.inherits(ol.ImageTile, ol.Tile);
+goog.inherits(ol.geom.LineString, ol.geom.SimpleGeometry);
 
 
 /**
- * @inheritDoc
- * @api
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @api stable
  */
-ol.ImageTile.prototype.getImage = function(opt_context) {
-  if (goog.isDef(opt_context)) {
-    var image;
-    var key = goog.getUid(opt_context);
-    if (key in this.imageByContext_) {
-      return this.imageByContext_[key];
-    } else if (goog.object.isEmpty(this.imageByContext_)) {
-      image = this.image_;
-    } else {
-      image = /** @type {Image} */ (this.image_.cloneNode(false));
-    }
-    this.imageByContext_[key] = image;
-    return image;
+ol.geom.LineString.prototype.appendCoordinate = function(coordinate) {
+  goog.asserts.assert(coordinate.length == this.stride);
+  if (goog.isNull(this.flatCoordinates)) {
+    this.flatCoordinates = coordinate.slice();
   } else {
-    return this.image_;
+    goog.array.extend(this.flatCoordinates, coordinate);
   }
+  this.changed();
 };
 
 
 /**
- * @inheritDoc
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.LineString} Clone.
+ * @api stable
  */
-ol.ImageTile.prototype.getKey = function() {
-  return this.src_;
+ol.geom.LineString.prototype.clone = function() {
+  var lineString = new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return lineString;
 };
 
 
 /**
- * Tracks loading or read errors.
- *
- * @private
+ * @inheritDoc
  */
-ol.ImageTile.prototype.handleImageError_ = function() {
-  this.state = ol.TileState.ERROR;
-  this.unlistenImage_();
-  this.dispatchChangeEvent();
+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);
 };
 
 
 /**
- * Tracks successful image load.
+ * Returns the coordinate at `m` using linear interpolation, or `null` if no
+ * such coordinate exists.
  *
- * @private
+ * `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.
+ * @return {ol.Coordinate} Coordinate.
+ * @api stable
  */
-ol.ImageTile.prototype.handleImageLoad_ = function() {
-  if (!goog.isDef(this.image_.naturalWidth)) {
-    this.image_.naturalWidth = this.image_.width;
-    this.image_.naturalHeight = this.image_.height;
+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 = goog.isDef(opt_extrapolate) ? opt_extrapolate : false;
+  return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0,
+      this.flatCoordinates.length, this.stride, m, extrapolate);
+};
 
-  if (this.image_.naturalWidth && this.image_.naturalHeight) {
-    this.state = ol.TileState.LOADED;
-  } else {
-    this.state = ol.TileState.EMPTY;
-  }
-  this.unlistenImage_();
-  this.dispatchChangeEvent();
+
+/**
+ * @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);
 };
 
 
 /**
- * Load not yet loaded URI.
+ * @return {number} Length (on projected plane).
+ * @api stable
  */
-ol.ImageTile.prototype.load = function() {
-  if (this.state == ol.TileState.IDLE) {
-    this.state = ol.TileState.LOADING;
-    goog.asserts.assert(goog.isNull(this.imageListenerKeys_));
-    this.imageListenerKeys_ = [
-      goog.events.listenOnce(this.image_, goog.events.EventType.ERROR,
-          this.handleImageError_, false, this),
-      goog.events.listenOnce(this.image_, goog.events.EventType.LOAD,
-          this.handleImageLoad_, false, this)
-    ];
-    this.tileLoadFunction_(this, this.src_);
-  }
+ol.geom.LineString.prototype.getLength = function() {
+  return ol.geom.flat.length.lineString(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
+ * @return {Array.<number>} Flat midpoint.
  */
-ol.ImageTile.prototype.unlistenImage_ = function() {
-  goog.asserts.assert(!goog.isNull(this.imageListenerKeys_));
-  goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
+ol.geom.LineString.prototype.getFlatMidpoint = function() {
+  if (this.flatMidpointRevision_ != this.getRevision()) {
+    this.flatMidpoint_ = ol.geom.flat.interpolate.lineString(
+        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+        0.5, this.flatMidpoint_);
+    this.flatMidpointRevision_ = this.getRevision();
+  }
+  return this.flatMidpoint_;
 };
 
-goog.provide('ol.ImageUrlFunction');
-goog.provide('ol.ImageUrlFunctionType');
 
-goog.require('ol.Size');
+/**
+ * @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;
+};
 
 
 /**
- * @typedef {function(this:ol.source.Image, ol.Extent, ol.Size,
- *     ol.proj.Projection): (string|undefined)}
+ * @inheritDoc
+ * @api stable
  */
-ol.ImageUrlFunctionType;
+ol.geom.LineString.prototype.getType = function() {
+  return ol.geom.GeometryType.LINE_STRING;
+};
 
 
 /**
- * @param {string} baseUrl Base URL (may have query data).
- * @param {Object.<string,*>} params to encode in the URL.
- * @param {function(string, Object.<string,*>, ol.Extent, ol.Size,
- *     ol.proj.Projection): (string|undefined)} paramsFunction params function.
- * @return {ol.ImageUrlFunctionType} Image URL function.
+ * @inheritDoc
+ * @api
  */
-ol.ImageUrlFunction.createFromParamsFunction =
-    function(baseUrl, params, paramsFunction) {
-  return (
-      /**
-       * @this {ol.source.Image}
-       * @param {ol.Extent} extent Extent.
-       * @param {ol.Size} size Size.
-       * @param {ol.proj.Projection} projection Projection.
-       * @return {string|undefined} URL.
-       */
-      function(extent, size, projection) {
-        return paramsFunction(baseUrl, params, extent, size, projection);
-      });
+ol.geom.LineString.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.lineString(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+      extent);
 };
 
 
 /**
- * @this {ol.source.Image}
- * @param {ol.Extent} extent Extent.
- * @param {ol.Size} size Size.
- * @return {string|undefined} Image URL.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-ol.ImageUrlFunction.nullImageUrlFunction =
-    function(extent, size) {
-  return undefined;
+ol.geom.LineString.prototype.setCoordinates =
+    function(coordinates, opt_layout) {
+  if (goog.isNull(coordinates)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+  } else {
+    this.setLayout(opt_layout, coordinates, 1);
+    if (goog.isNull(this.flatCoordinates)) {
+      this.flatCoordinates = [];
+    }
+    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
+        this.flatCoordinates, 0, coordinates, this.stride);
+    this.changed();
+  }
 };
 
-// FIXME factor out key precondition (shift et. al)
 
-goog.provide('ol.interaction.Interaction');
+/**
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ */
+ol.geom.LineString.prototype.setFlatCoordinates =
+    function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
+};
+
+goog.provide('ol.geom.MultiLineString');
 
+goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.Observable');
-goog.require('ol.animation');
-goog.require('ol.easing');
+goog.require('ol.extent');
+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
- * 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.
+ * Multi-linestring geometry.
  *
  * @constructor
- * @extends {ol.Observable}
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-ol.interaction.Interaction = function() {
+ol.geom.MultiLineString = function(coordinates, opt_layout) {
+
   goog.base(this);
 
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.ends_ = [];
+
   /**
    * @private
-   * @type {ol.Map}
+   * @type {number}
    */
-  this.map_ = null;
+  this.maxDelta_ = -1;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
+
+  this.setCoordinates(coordinates,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
 
 };
-goog.inherits(ol.interaction.Interaction, ol.Observable);
+goog.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry);
 
 
 /**
- * Get the map associated with this interaction.
- * @return {ol.Map} Map.
+ * @param {ol.geom.LineString} lineString LineString.
+ * @api stable
  */
-ol.interaction.Interaction.prototype.getMap = function() {
-  return this.map_;
+ol.geom.MultiLineString.prototype.appendLineString = function(lineString) {
+  goog.asserts.assert(lineString.getLayout() == this.layout);
+  if (goog.isNull(this.flatCoordinates)) {
+    this.flatCoordinates = lineString.getFlatCoordinates().slice();
+  } else {
+    goog.array.extend(
+        this.flatCoordinates, lineString.getFlatCoordinates().slice());
+  }
+  this.ends_.push(this.flatCoordinates.length);
+  this.changed();
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} Whether the map browser event should continue
- *     through the chain of interactions. false means stop, true
- *     means continue.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiLineString} Clone.
+ * @api stable
  */
-ol.interaction.Interaction.prototype.handleMapBrowserEvent =
-    goog.abstractMethod;
+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;
+};
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-ol.interaction.Interaction.prototype.setMap = function(map) {
-  this.map_ = map;
+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);
 };
 
 
 /**
- * @param {ol.Map} map Map.
- * @param {ol.View} view View.
- * @param {ol.Coordinate} delta Delta.
- * @param {number=} opt_duration Duration.
+ * 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.
+ * @param {boolean=} opt_interpolate Interpolate.
+ * @return {ol.Coordinate} Coordinate.
+ * @api stable
  */
-ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) {
-  var currentCenter = view.getCenter();
-  if (goog.isDef(currentCenter)) {
-    if (goog.isDef(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);
+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 = goog.isDef(opt_extrapolate) ? opt_extrapolate : false;
+  var interpolate = goog.isDef(opt_interpolate) ? opt_interpolate : false;
+  return ol.geom.flat.lineStringsCoordinateAtM(this.flatCoordinates, 0,
+      this.ends_, this.stride, m, extrapolate, interpolate);
 };
 
 
 /**
- * @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.
+ * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
+ * @api stable
  */
-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);
+ol.geom.MultiLineString.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinatess(
+      this.flatCoordinates, 0, this.ends_, this.stride);
 };
 
 
 /**
- * @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.
+ * @return {Array.<number>} Ends.
  */
-ol.interaction.Interaction.rotateWithoutConstraints =
-    function(map, view, rotation, opt_anchor, opt_duration) {
-  if (goog.isDefAndNotNull(rotation)) {
-    var currentRotation = view.getRotation();
-    var currentCenter = view.getCenter();
-    if (goog.isDef(currentRotation) && goog.isDef(currentCenter) &&
-        goog.isDef(opt_duration) && opt_duration > 0) {
-      map.beforeRender(ol.animation.rotate({
-        rotation: currentRotation,
-        duration: opt_duration,
-        easing: ol.easing.easeOut
-      }));
-      if (goog.isDef(opt_anchor)) {
-        map.beforeRender(ol.animation.pan({
-          source: currentCenter,
-          duration: opt_duration,
-          easing: ol.easing.easeOut
-        }));
-      }
-    }
-    view.rotate(rotation, opt_anchor);
-  }
+ol.geom.MultiLineString.prototype.getEnds = function() {
+  return this.ends_;
 };
 
 
 /**
- * @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.
+ * @param {number} index Index.
+ * @return {ol.geom.LineString} LineString.
+ * @api stable
  */
-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);
+ol.geom.MultiLineString.prototype.getLineString = function(index) {
+  goog.asserts.assert(0 <= index && index < this.ends_.length);
+  if (index < 0 || this.ends_.length <= index) {
+    return null;
+  }
+  var lineString = new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
+      index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]));
+  return lineString;
 };
 
 
 /**
- * @param {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.
+ * @return {Array.<ol.geom.LineString>} LineStrings.
+ * @api stable
  */
-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);
+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;
 };
 
 
 /**
- * @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.
+ * @return {Array.<number>} Flat midpoints.
  */
-ol.interaction.Interaction.zoomWithoutConstraints =
-    function(map, view, resolution, opt_anchor, opt_duration) {
-  if (goog.isDefAndNotNull(resolution)) {
-    var currentResolution = view.getResolution();
-    var currentCenter = view.getCenter();
-    if (goog.isDef(currentResolution) && goog.isDef(currentCenter) &&
-        goog.isDef(opt_duration) && opt_duration > 0) {
-      map.beforeRender(ol.animation.zoom({
-        resolution: currentResolution,
-        duration: opt_duration,
-        easing: ol.easing.easeOut
-      }));
-      if (goog.isDef(opt_anchor)) {
-        map.beforeRender(ol.animation.pan({
-          source: currentCenter,
-          duration: opt_duration,
-          easing: ol.easing.easeOut
-        }));
-      }
-    }
-    if (goog.isDefAndNotNull(opt_anchor)) {
-      var center = view.calculateCenterZoom(resolution, opt_anchor);
-      view.setCenter(center);
-    }
-    view.setResolution(resolution);
+ol.geom.MultiLineString.prototype.getFlatMidpoints = function() {
+  var midpoints = [];
+  var flatCoordinates = this.flatCoordinates;
+  var offset = 0;
+  var ends = this.ends_;
+  var stride = this.stride;
+  var i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    var end = ends[i];
+    var midpoint = ol.geom.flat.interpolate.lineString(
+        flatCoordinates, offset, end, stride, 0.5);
+    goog.array.extend(midpoints, midpoint);
+    offset = end;
   }
+  return midpoints;
 };
 
-goog.provide('ol.interaction.DoubleClickZoom');
-
-goog.require('goog.asserts');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.interaction.Interaction');
-
-
 
 /**
- * @classdesc
- * Allows the user to zoom by double-clicking on the map.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options.
- * @api stable
+ * @inheritDoc
  */
-ol.interaction.DoubleClickZoom = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = goog.isDef(options.delta) ? options.delta : 1;
-
-  goog.base(this);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
-
+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;
 };
-goog.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction);
 
 
 /**
  * @inheritDoc
+ * @api stable
  */
-ol.interaction.DoubleClickZoom.prototype.handleMapBrowserEvent =
-    function(mapBrowserEvent) {
-  var stopEvent = false;
-  var browserEvent = mapBrowserEvent.browserEvent;
-  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) {
-    var map = mapBrowserEvent.map;
-    var anchor = mapBrowserEvent.coordinate;
-    var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_;
-    var view = map.getView();
-    goog.asserts.assert(goog.isDef(view));
-    ol.interaction.Interaction.zoomByDelta(
-        map, view, delta, anchor, this.duration_);
-    mapBrowserEvent.preventDefault();
-    stopEvent = true;
-  }
-  return !stopEvent;
+ol.geom.MultiLineString.prototype.getType = function() {
+  return ol.geom.GeometryType.MULTI_LINE_STRING;
 };
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview Defines the collection interface.
- *
- * @author nnaze@google.com (Nathan Naze)
- */
-
-goog.provide('goog.structs.Collection');
-
-
 
 /**
- * An interface for a collection of values.
- * @interface
- * @template T
+ * @inheritDoc
+ * @api
  */
-goog.structs.Collection = function() {};
+ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.lineStrings(
+      this.flatCoordinates, 0, this.ends_, this.stride, extent);
+};
 
 
 /**
- * @param {T} value Value to add to the collection.
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-goog.structs.Collection.prototype.add;
+ol.geom.MultiLineString.prototype.setCoordinates =
+    function(coordinates, opt_layout) {
+  if (goog.isNull(coordinates)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
+  } else {
+    this.setLayout(opt_layout, coordinates, 2);
+    if (goog.isNull(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 {T} value Value to remove from the collection.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<number>} ends Ends.
  */
-goog.structs.Collection.prototype.remove;
+ol.geom.MultiLineString.prototype.setFlatCoordinates =
+    function(layout, flatCoordinates, ends) {
+  if (goog.isNull(flatCoordinates)) {
+    goog.asserts.assert(!goog.isNull(ends) && ends.length === 0);
+  } else if (ends.length === 0) {
+    goog.asserts.assert(flatCoordinates.length === 0);
+  } else {
+    goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]);
+  }
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.ends_ = ends;
+  this.changed();
+};
 
 
 /**
- * @param {T} value Value to find in the collection.
- * @return {boolean} Whether the collection contains the specified value.
+ * @param {Array.<ol.geom.LineString>} lineStrings LineStrings.
  */
-goog.structs.Collection.prototype.contains;
+ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) {
+  var layout = ol.geom.GeometryLayout.XY;
+  var flatCoordinates = [];
+  var ends = [];
+  var i, ii;
+  for (i = 0, ii = lineStrings.length; i < ii; ++i) {
+    var lineString = lineStrings[i];
+    if (i === 0) {
+      layout = lineString.getLayout();
+    } else {
+      // FIXME better handle the case of non-matching layouts
+      goog.asserts.assert(lineString.getLayout() == layout);
+    }
+    goog.array.extend(flatCoordinates, lineString.getFlatCoordinates());
+    ends.push(flatCoordinates.length);
+  }
+  this.setFlatCoordinates(layout, flatCoordinates, ends);
+};
 
+goog.provide('ol.geom.MultiPoint');
 
-/**
- * @return {number} The number of values stored in the collection.
- */
-goog.structs.Collection.prototype.getCount;
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('ol.extent');
+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');
 
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Datastructure: Set.
- *
- * @author arv@google.com (Erik Arvidsson)
- * @author pallosp@google.com (Peter Pallos)
+ * @classdesc
+ * Multi-point geometry.
  *
- * This class implements a set data structure. Adding and removing is O(1). It
- * supports both object and primitive values. Be careful because you can add
- * both 1 and new Number(1), because these are not the same. You can even add
- * multiple new Number(1) because these are not equal.
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-
-
-goog.provide('goog.structs.Set');
-
-goog.require('goog.structs');
-goog.require('goog.structs.Collection');
-goog.require('goog.structs.Map');
-
+ol.geom.MultiPoint = function(coordinates, opt_layout) {
+  goog.base(this);
+  this.setCoordinates(coordinates,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
+};
+goog.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry);
 
 
 /**
- * A set that can contain both primitives and objects.  Adding and removing
- * elements is O(1).  Primitives are treated as identical if they have the same
- * type and convert to the same string.  Objects are treated as identical only
- * if they are references to the same object.  WARNING: A goog.structs.Set can
- * contain both 1 and (new Number(1)), because they are not the same.  WARNING:
- * Adding (new Number(1)) twice will yield two distinct elements, because they
- * are two different objects.  WARNING: Any object that is added to a
- * goog.structs.Set will be modified!  Because goog.getUid() is used to
- * identify objects, every object in the set will be mutated.
- * @param {Array.<T>|Object.<?,T>=} opt_values Initial values to start with.
- * @constructor
- * @implements {goog.structs.Collection.<T>}
- * @final
- * @template T
+ * @param {ol.geom.Point} point Point.
+ * @api stable
  */
-goog.structs.Set = function(opt_values) {
-  this.map_ = new goog.structs.Map;
-  if (opt_values) {
-    this.addAll(opt_values);
+ol.geom.MultiPoint.prototype.appendPoint = function(point) {
+  goog.asserts.assert(point.getLayout() == this.layout);
+  if (goog.isNull(this.flatCoordinates)) {
+    this.flatCoordinates = point.getFlatCoordinates().slice();
+  } else {
+    goog.array.extend(this.flatCoordinates, point.getFlatCoordinates());
   }
+  this.changed();
 };
 
 
 /**
- * Obtains a unique key for an element of the set.  Primitives will yield the
- * same key if they have the same type and convert to the same string.  Object
- * references will yield the same key only if they refer to the same object.
- * @param {*} val Object or primitive value to get a key for.
- * @return {string} A unique key for this value/object.
- * @private
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiPoint} Clone.
+ * @api stable
  */
-goog.structs.Set.getKey_ = function(val) {
-  var type = typeof val;
-  if (type == 'object' && val || type == 'function') {
-    return 'o' + goog.getUid(/** @type {Object} */ (val));
-  } else {
-    return type.substr(0, 1) + val;
-  }
+ol.geom.MultiPoint.prototype.clone = function() {
+  var multiPoint = new ol.geom.MultiPoint(null);
+  multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return multiPoint;
 };
 
 
 /**
- * @return {number} The number of elements in the set.
- * @override
+ * @inheritDoc
  */
-goog.structs.Set.prototype.getCount = function() {
-  return this.map_.getCount();
+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;
 };
 
 
 /**
- * Add a primitive or an object to the set.
- * @param {T} element The primitive or object to add.
- * @override
+ * @return {Array.<ol.Coordinate>} Coordinates.
+ * @api stable
  */
-goog.structs.Set.prototype.add = function(element) {
-  this.map_.set(goog.structs.Set.getKey_(element), element);
+ol.geom.MultiPoint.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * Adds all the values in the given collection to this set.
- * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection
- *     containing the elements to add.
+ * @param {number} index Index.
+ * @return {ol.geom.Point} Point.
+ * @api stable
  */
-goog.structs.Set.prototype.addAll = function(col) {
-  var values = goog.structs.getValues(col);
-  var l = values.length;
-  for (var i = 0; i < l; i++) {
-    this.add(values[i]);
+ol.geom.MultiPoint.prototype.getPoint = function(index) {
+  var n = goog.isNull(this.flatCoordinates) ?
+      0 : this.flatCoordinates.length / this.stride;
+  goog.asserts.assert(0 <= index && index < 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;
 };
 
 
 /**
- * Removes all values in the given collection from this set.
- * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection
- *     containing the elements to remove.
+ * @return {Array.<ol.geom.Point>} Points.
+ * @api stable
  */
-goog.structs.Set.prototype.removeAll = function(col) {
-  var values = goog.structs.getValues(col);
-  var l = values.length;
-  for (var i = 0; i < l; i++) {
-    this.remove(values[i]);
+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;
 };
 
 
 /**
- * Removes the given element from this set.
- * @param {T} element The primitive or object to remove.
- * @return {boolean} Whether the element was found and removed.
- * @override
+ * @inheritDoc
+ * @api stable
  */
-goog.structs.Set.prototype.remove = function(element) {
-  return this.map_.remove(goog.structs.Set.getKey_(element));
+ol.geom.MultiPoint.prototype.getType = function() {
+  return ol.geom.GeometryType.MULTI_POINT;
 };
 
 
 /**
- * Removes all elements from this set.
+ * @inheritDoc
+ * @api
  */
-goog.structs.Set.prototype.clear = function() {
-  this.map_.clear();
+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;
 };
 
 
 /**
- * Tests whether this set is empty.
- * @return {boolean} True if there are no elements in this set.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-goog.structs.Set.prototype.isEmpty = function() {
-  return this.map_.isEmpty();
+ol.geom.MultiPoint.prototype.setCoordinates =
+    function(coordinates, opt_layout) {
+  if (goog.isNull(coordinates)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+  } else {
+    this.setLayout(opt_layout, coordinates, 1);
+    if (goog.isNull(this.flatCoordinates)) {
+      this.flatCoordinates = [];
+    }
+    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
+        this.flatCoordinates, 0, coordinates, this.stride);
+    this.changed();
+  }
 };
 
 
 /**
- * Tests whether this set contains the given element.
- * @param {T} element The primitive or object to test for.
- * @return {boolean} True if this set contains the given element.
- * @override
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-goog.structs.Set.prototype.contains = function(element) {
-  return this.map_.containsKey(goog.structs.Set.getKey_(element));
+ol.geom.MultiPoint.prototype.setFlatCoordinates =
+    function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
+goog.provide('ol.geom.flat.center');
+
+goog.require('ol.extent');
+
 
 /**
- * Tests whether this set contains all the values in a given collection.
- * Repeated elements in the collection are ignored, e.g.  (new
- * goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
- * @param {goog.structs.Collection.<T>|Object} col A collection-like object.
- * @return {boolean} True if the set contains all elements.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @return {Array.<number>} Flat centers.
  */
-goog.structs.Set.prototype.containsAll = function(col) {
-  return goog.structs.every(col, this.contains, this);
+ol.geom.flat.center.linearRingss =
+    function(flatCoordinates, offset, endss, stride) {
+  var flatCenters = [];
+  var i, ii;
+  var extent = ol.extent.createEmpty();
+  for (i = 0, ii = endss.length; i < ii; ++i) {
+    var ends = endss[i];
+    extent = ol.extent.createOrUpdateFromFlatCoordinates(
+        flatCoordinates, offset, ends[0], stride);
+    flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2);
+    offset = ends[ends.length - 1];
+  }
+  return flatCenters;
 };
 
+goog.provide('ol.geom.MultiPolygon');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('ol.extent');
+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');
+
+
 
 /**
- * Finds all values that are present in both this set and the given collection.
- * @param {Array.<S>|Object.<?,S>} col A collection.
- * @return {!goog.structs.Set.<T|S>} A new set containing all the values
- *     (primitives or objects) present in both this set and the given
- *     collection.
- * @template S
+ * @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
  */
-goog.structs.Set.prototype.intersection = function(col) {
-  var result = new goog.structs.Set();
+ol.geom.MultiPolygon = function(coordinates, opt_layout) {
 
-  var values = goog.structs.getValues(col);
-  for (var i = 0; i < values.length; i++) {
-    var value = values[i];
-    if (this.contains(value)) {
-      result.add(value);
-    }
-  }
+  goog.base(this);
 
-  return result;
-};
+  /**
+   * @type {Array.<Array.<number>>}
+   * @private
+   */
+  this.endss_ = [];
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.flatInteriorPointsRevision_ = -1;
 
-/**
- * Finds all values that are present in this set and not in the given
- * collection.
- * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection.
- * @return {!goog.structs.Set} A new set containing all the values
- *     (primitives or objects) present in this set but not in the given
- *     collection.
- */
-goog.structs.Set.prototype.difference = function(col) {
-  var result = this.clone();
-  result.removeAll(col);
-  return result;
-};
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.flatInteriorPoints_ = null;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
 
-/**
- * Returns an array containing all the elements in this set.
- * @return {!Array.<T>} An array containing all the elements in this set.
- */
-goog.structs.Set.prototype.getValues = function() {
-  return this.map_.getValues();
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.orientedRevision_ = -1;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.orientedFlatCoordinates_ = null;
+
+  this.setCoordinates(coordinates,
+      /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout));
 
-/**
- * Creates a shallow clone of this set.
- * @return {!goog.structs.Set.<T>} A new set containing all the same elements as
- *     this set.
- */
-goog.structs.Set.prototype.clone = function() {
-  return new goog.structs.Set(this);
 };
+goog.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry);
 
 
 /**
- * Tests whether the given collection consists of the same elements as this set,
- * regardless of order, without repetition.  Primitives are treated as equal if
- * they have the same type and convert to the same string; objects are treated
- * as equal if they are references to the same object.  This operation is O(n).
- * @param {goog.structs.Collection.<T>|Object} col A collection.
- * @return {boolean} True if the given collection consists of the same elements
- *     as this set, regardless of order, without repetition.
+ * @param {ol.geom.Polygon} polygon Polygon.
+ * @api stable
  */
-goog.structs.Set.prototype.equals = function(col) {
-  return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
+ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) {
+  goog.asserts.assert(polygon.getLayout() == this.layout);
+  /** @type {Array.<number>} */
+  var ends;
+  if (goog.isNull(this.flatCoordinates)) {
+    this.flatCoordinates = polygon.getFlatCoordinates().slice();
+    ends = polygon.getEnds().slice();
+    this.endss_.push();
+  } else {
+    var offset = this.flatCoordinates.length;
+    goog.array.extend(this.flatCoordinates, polygon.getFlatCoordinates());
+    ends = polygon.getEnds().slice();
+    var i, ii;
+    for (i = 0, ii = ends.length; i < ii; ++i) {
+      ends[i] += offset;
+    }
+  }
+  this.endss_.push(ends);
+  this.changed();
 };
 
 
 /**
- * Tests whether the given collection contains all the elements in this set.
- * Primitives are treated as equal if they have the same type and convert to the
- * same string; objects are treated as equal if they are references to the same
- * object.  This operation is O(n).
- * @param {goog.structs.Collection.<T>|Object} col A collection.
- * @return {boolean} True if this set is a subset of the given collection.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.MultiPolygon} Clone.
+ * @api stable
  */
-goog.structs.Set.prototype.isSubsetOf = function(col) {
-  var colCount = goog.structs.getCount(col);
-  if (this.getCount() > colCount) {
-    return false;
-  }
-  // TODO(user) Find the minimal collection size where the conversion makes
-  // the contains() method faster.
-  if (!(col instanceof goog.structs.Set) && colCount > 5) {
-    // Convert to a goog.structs.Set so that goog.structs.contains runs in
-    // O(1) time instead of O(n) time.
-    col = new goog.structs.Set(col);
-  }
-  return goog.structs.every(this, function(value) {
-    return goog.structs.contains(col, value);
-  });
+ol.geom.MultiPolygon.prototype.clone = function() {
+  var multiPolygon = new ol.geom.MultiPolygon(null);
+  multiPolygon.setFlatCoordinates(
+      this.layout, this.flatCoordinates.slice(), this.endss_.slice());
+  return multiPolygon;
 };
 
 
 /**
- * Returns an iterator that iterates over the elements in this set.
- * @param {boolean=} opt_keys This argument is ignored.
- * @return {!goog.iter.Iterator} An iterator over the elements in this set.
+ * @inheritDoc
  */
-goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
-  return this.map_.__iterator__(false);
+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);
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Logging and debugging utilities.
- *
- * @see ../demos/debug.html
+ * @inheritDoc
  */
-
-goog.provide('goog.debug');
-
-goog.require('goog.array');
-goog.require('goog.string');
-goog.require('goog.structs.Set');
-goog.require('goog.userAgent');
+ol.geom.MultiPolygon.prototype.containsXY = function(x, y) {
+  return ol.geom.flat.contains.linearRingssContainsXY(
+      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y);
+};
 
 
-/** @define {boolean} Whether logging should be enabled. */
-goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
+/**
+ * @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);
+};
 
 
 /**
- * Catches onerror events fired by windows and similar objects.
- * @param {function(Object)} logFunc The function to call with the error
- *    information.
- * @param {boolean=} opt_cancel Whether to stop the error from reaching the
- *    browser.
- * @param {Object=} opt_target Object that fires onerror events.
+ * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates.
+ * @api stable
  */
-goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
-  var target = opt_target || goog.global;
-  var oldErrorHandler = target.onerror;
-  var retVal = !!opt_cancel;
+ol.geom.MultiPolygon.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinatesss(
+      this.flatCoordinates, 0, this.endss_, this.stride);
+};
 
-  // Chrome interprets onerror return value backwards (http://crbug.com/92062)
-  // until it was fixed in webkit revision r94061 (Webkit 535.3). This
-  // workaround still needs to be skipped in Safari after the webkit change
-  // gets pushed out in Safari.
-  // See https://bugs.webkit.org/show_bug.cgi?id=67119
-  if (goog.userAgent.WEBKIT &&
-      !goog.userAgent.isVersionOrHigher('535.3')) {
-    retVal = !retVal;
-  }
 
-  /**
-   * New onerror handler for this target. This onerror handler follows the spec
-   * according to
-   * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
-   * The spec was changed in August 2013 to support receiving column information
-   * and an error object for all scripts on the same origin or cross origin
-   * scripts with the proper headers. See
-   * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
-   *
-   * @param {string} message The error message. For cross-origin errors, this
-   *     will be scrubbed to just "Script error.". For new browsers that have
-   *     updated to follow the latest spec, errors that come from origins that
-   *     have proper cross origin headers will not be scrubbed.
-   * @param {string} url The URL of the script that caused the error. The URL
-   *     will be scrubbed to "" for cross origin scripts unless the script has
-   *     proper cross origin headers and the browser has updated to the latest
-   *     spec.
-   * @param {number} line The line number in the script that the error
-   *     occurred on.
-   * @param {number=} opt_col The optional column number that the error
-   *     occurred on. Only browsers that have updated to the latest spec will
-   *     include this.
-   * @param {Error=} opt_error The optional actual error object for this
-   *     error that should include the stack. Only browsers that have updated
-   *     to the latest spec will inlude this parameter.
-   * @return {boolean} Whether to prevent the error from reaching the browser.
-   */
-  target.onerror = function(message, url, line, opt_col, opt_error) {
-    if (oldErrorHandler) {
-      oldErrorHandler(message, url, line, opt_col, opt_error);
-    }
-    logFunc({
-      message: message,
-      fileName: url,
-      line: line,
-      col: opt_col,
-      error: opt_error
-    });
-    return retVal;
-  };
+/**
+ * @return {Array.<Array.<number>>} Endss.
+ */
+ol.geom.MultiPolygon.prototype.getEndss = function() {
+  return this.endss_;
 };
 
 
 /**
- * Creates a string representing an object and all its properties.
- * @param {Object|null|undefined} obj Object to expose.
- * @param {boolean=} opt_showFn Show the functions as well as the properties,
- *     default is false.
- * @return {string} The string representation of {@code obj}.
+ * @return {Array.<number>} Flat interior points.
  */
-goog.debug.expose = function(obj, opt_showFn) {
-  if (typeof obj == 'undefined') {
-    return 'undefined';
-  }
-  if (obj == null) {
-    return 'NULL';
-  }
-  var str = [];
-
-  for (var x in obj) {
-    if (!opt_showFn && goog.isFunction(obj[x])) {
-      continue;
-    }
-    var s = x + ' = ';
-    /** @preserveTry */
-    try {
-      s += obj[x];
-    } catch (e) {
-      s += '*** ' + e + ' ***';
-    }
-    str.push(s);
+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 str.join('\n');
+  return this.flatInteriorPoints_;
 };
 
 
 /**
- * Creates a string representing a given primitive or object, and for an
- * object, all its properties and nested objects.  WARNING: If an object is
- * given, it and all its nested objects will be modified.  To detect reference
- * cycles, this method identifies objects using goog.getUid() which mutates the
- * object.
- * @param {*} obj Object to expose.
- * @param {boolean=} opt_showFn Also show properties that are functions (by
- *     default, functions are omitted).
- * @return {string} A string representation of {@code obj}.
+ * @return {ol.geom.MultiPoint} Interior points.
+ * @api stable
  */
-goog.debug.deepExpose = function(obj, opt_showFn) {
-  var str = [];
-
-  var helper = function(obj, space, parentSeen) {
-    var nestspace = space + '  ';
-    var seen = new goog.structs.Set(parentSeen);
-
-    var indentMultiline = function(str) {
-      return str.replace(/\n/g, '\n' + space);
-    };
-
-    /** @preserveTry */
-    try {
-      if (!goog.isDef(obj)) {
-        str.push('undefined');
-      } else if (goog.isNull(obj)) {
-        str.push('NULL');
-      } else if (goog.isString(obj)) {
-        str.push('"' + indentMultiline(obj) + '"');
-      } else if (goog.isFunction(obj)) {
-        str.push(indentMultiline(String(obj)));
-      } else if (goog.isObject(obj)) {
-        if (seen.contains(obj)) {
-          str.push('*** reference loop detected ***');
-        } else {
-          seen.add(obj);
-          str.push('{');
-          for (var x in obj) {
-            if (!opt_showFn && goog.isFunction(obj[x])) {
-              continue;
-            }
-            str.push('\n');
-            str.push(nestspace);
-            str.push(x + ' = ');
-            helper(obj[x], nestspace, seen);
-          }
-          str.push('\n' + space + '}');
-        }
-      } else {
-        str.push(obj);
-      }
-    } catch (e) {
-      str.push('*** ' + e + ' ***');
-    }
-  };
-
-  helper(obj, '', new goog.structs.Set());
-  return str.join('');
+ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
+  var interiorPoints = new ol.geom.MultiPoint(null);
+  interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY,
+      this.getFlatInteriorPoints().slice());
+  return interiorPoints;
 };
 
 
 /**
- * Recursively outputs a nested array as a string.
- * @param {Array} arr The array.
- * @return {string} String representing nested array.
+ * @return {Array.<number>} Oriented flat coordinates.
  */
-goog.debug.exposeArray = function(arr) {
-  var str = [];
-  for (var i = 0; i < arr.length; i++) {
-    if (goog.isArray(arr[i])) {
-      str.push(goog.debug.exposeArray(arr[i]));
+ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() {
+  if (this.orientedRevision_ != this.getRevision()) {
+    var flatCoordinates = this.flatCoordinates;
+    if (ol.geom.flat.linearRingssAreOriented(
+        flatCoordinates, 0, this.endss_, this.stride)) {
+      this.orientedFlatCoordinates_ = flatCoordinates;
     } else {
-      str.push(arr[i]);
+      this.orientedFlatCoordinates_ = flatCoordinates.slice();
+      this.orientedFlatCoordinates_.length =
+          ol.geom.flat.orient.orientLinearRingss(
+              this.orientedFlatCoordinates_, 0, this.endss_, this.stride);
     }
+    this.orientedRevision_ = this.getRevision();
   }
-  return '[ ' + str.join(', ') + ' ]';
+  return this.orientedFlatCoordinates_;
 };
 
 
 /**
- * Exposes an exception that has been caught by a try...catch and outputs the
- * error with a stack trace.
- * @param {Object} err Error object or string.
- * @param {Function=} opt_fn Optional function to start stack trace from.
- * @return {string} Details of exception.
+ * @inheritDoc
  */
-goog.debug.exposeException = function(err, opt_fn) {
-  /** @preserveTry */
-  try {
-    var e = goog.debug.normalizeErrorObject(err);
-
-    // Create the error message
-    var error = 'Message: ' + goog.string.htmlEscape(e.message) +
-        '\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' +
-        e.fileName + '</a>\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
-        goog.string.htmlEscape(e.stack + '-> ') +
-        '[end]\n\nJS stack traversal:\n' + goog.string.htmlEscape(
-            goog.debug.getStacktrace(opt_fn) + '-> ');
-    return error;
-  } catch (e2) {
-    return 'Exception trying to expose exception! You win, we lose. ' + e2;
-  }
+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;
 };
 
 
 /**
- * Normalizes the error/exception object between browsers.
- * @param {Object} err Raw error object.
- * @return {!Object} Normalized error object.
+ * @param {number} index Index.
+ * @return {ol.geom.Polygon} Polygon.
+ * @api stable
  */
-goog.debug.normalizeErrorObject = function(err) {
-  var href = goog.getObjectByName('window.location.href');
-  if (goog.isString(err)) {
-    return {
-      'message': err,
-      'name': 'Unknown error',
-      'lineNumber': 'Not available',
-      'fileName': href,
-      'stack': 'Not available'
-    };
-  }
-
-  var lineNumber, fileName;
-  var threwError = false;
-
-  try {
-    lineNumber = err.lineNumber || err.line || 'Not available';
-  } catch (e) {
-    // Firefox 2 sometimes throws an error when accessing 'lineNumber':
-    // Message: Permission denied to get property UnnamedClass.lineNumber
-    lineNumber = 'Not available';
-    threwError = true;
+ol.geom.MultiPolygon.prototype.getPolygon = function(index) {
+  goog.asserts.assert(0 <= index && index < this.endss_.length);
+  if (index < 0 || this.endss_.length <= index) {
+    return null;
   }
-
-  try {
-    fileName = err.fileName || err.filename || err.sourceURL ||
-        // $googDebugFname may be set before a call to eval to set the filename
-        // that the eval is supposed to present.
-        goog.global['$googDebugFname'] || href;
-  } catch (e) {
-    // Firefox 2 may also throw an error when accessing 'filename'.
-    fileName = 'Not available';
-    threwError = true;
+  var offset;
+  if (index === 0) {
+    offset = 0;
+  } else {
+    var prevEnds = this.endss_[index - 1];
+    offset = prevEnds[prevEnds.length - 1];
   }
-
-  // The IE Error object contains only the name and the message.
-  // The Safari Error object uses the line and sourceURL fields.
-  if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
-      !err.message || !err.name) {
-    return {
-      'message': err.message || 'Not available',
-      'name': err.name || 'UnknownError',
-      'lineNumber': lineNumber,
-      'fileName': fileName,
-      'stack': err.stack || 'Not available'
-    };
+  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;
+    }
   }
-
-  // Standards error object
-  return err;
+  var polygon = new ol.geom.Polygon(null);
+  polygon.setFlatCoordinates(
+      this.layout, this.flatCoordinates.slice(offset, end), ends);
+  return polygon;
 };
 
 
 /**
- * Converts an object to an Error if it's a String,
- * adds a stacktrace if there isn't one,
- * and optionally adds an extra message.
- * @param {Error|string} err  the original thrown object or string.
- * @param {string=} opt_message  optional additional message to add to the
- *     error.
- * @return {!Error} If err is a string, it is used to create a new Error,
- *     which is enhanced and returned.  Otherwise err itself is enhanced
- *     and returned.
+ * @return {Array.<ol.geom.Polygon>} Polygons.
+ * @api stable
  */
-goog.debug.enhanceError = function(err, opt_message) {
-  var error;
-  if (typeof err == 'string') {
-    error = Error(err);
-    if (Error.captureStackTrace) {
-      // Trim this function off the call stack, if we can.
-      Error.captureStackTrace(error, goog.debug.enhanceError);
-    }
-  } else {
-    error = err;
-  }
-
-  if (!error.stack) {
-    error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
-  }
-  if (opt_message) {
-    // find the first unoccupied 'messageX' property
-    var x = 0;
-    while (error['message' + x]) {
-      ++x;
+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;
+      }
     }
-    error['message' + x] = String(opt_message);
+    var polygon = new ol.geom.Polygon(null);
+    polygon.setFlatCoordinates(
+        layout, flatCoordinates.slice(offset, end), ends);
+    polygons.push(polygon);
+    offset = end;
   }
-  return error;
+  return polygons;
 };
 
 
 /**
- * Gets the current stack trace. Simple and iterative - doesn't worry about
- * catching circular references or getting the args.
- * @param {number=} opt_depth Optional maximum depth to trace back to.
- * @return {string} A string with the function names of all functions in the
- *     stack, separated by \n.
- * @suppress {es5Strict}
+ * @inheritDoc
+ * @api stable
  */
-goog.debug.getStacktraceSimple = function(opt_depth) {
-  if (goog.STRICT_MODE_COMPATIBLE) {
-    var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
-    if (stack) {
-      return stack;
-    }
-    // NOTE: browsers that have strict mode support also have native "stack"
-    // properties.  Fall-through for legacy browser support.
-  }
-
-  var sb = [];
-  var fn = arguments.callee.caller;
-  var depth = 0;
-
-  while (fn && (!opt_depth || depth < opt_depth)) {
-    sb.push(goog.debug.getFunctionName(fn));
-    sb.push('()\n');
-    /** @preserveTry */
-    try {
-      fn = fn.caller;
-    } catch (e) {
-      sb.push('[exception trying to get caller]\n');
-      break;
-    }
-    depth++;
-    if (depth >= goog.debug.MAX_STACK_DEPTH) {
-      sb.push('[...long stack...]');
-      break;
-    }
-  }
-  if (opt_depth && depth >= opt_depth) {
-    sb.push('[...reached max depth limit...]');
-  } else {
-    sb.push('[end]');
-  }
-
-  return sb.join('');
+ol.geom.MultiPolygon.prototype.getType = function() {
+  return ol.geom.GeometryType.MULTI_POLYGON;
 };
 
 
 /**
- * Max length of stack to try and output
- * @type {number}
+ * @inheritDoc
+ * @api
  */
-goog.debug.MAX_STACK_DEPTH = 50;
+ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.linearRingss(
+      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent);
+};
 
 
 /**
- * @param {Function} fn The function to start getting the trace from.
- * @return {?string}
- * @private
+ * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api stable
  */
-goog.debug.getNativeStackTrace_ = function(fn) {
-  var tempErr = new Error();
-  if (Error.captureStackTrace) {
-    Error.captureStackTrace(tempErr, fn);
-    return String(tempErr.stack);
+ol.geom.MultiPolygon.prototype.setCoordinates =
+    function(coordinates, opt_layout) {
+  if (goog.isNull(coordinates)) {
+    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_);
   } else {
-    // IE10, only adds stack traces when an exception is thrown.
-    try {
-      throw tempErr;
-    } catch (e) {
-      tempErr = e;
+    this.setLayout(opt_layout, coordinates, 3);
+    if (goog.isNull(this.flatCoordinates)) {
+      this.flatCoordinates = [];
     }
-    var stack = tempErr.stack;
-    if (stack) {
-      return String(stack);
+    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();
   }
-  return null;
 };
 
 
 /**
- * Gets the current stack trace, either starting from the caller or starting
- * from a specified function that's currently on the call stack.
- * @param {Function=} opt_fn Optional function to start getting the trace from.
- *     If not provided, defaults to the function that called this.
- * @return {string} Stack trace.
- * @suppress {es5Strict}
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<Array.<number>>} endss Endss.
  */
-goog.debug.getStacktrace = function(opt_fn) {
-  var stack;
-  if (goog.STRICT_MODE_COMPATIBLE) {
-    // Try to get the stack trace from the environment if it is available.
-    var contextFn = opt_fn || goog.debug.getStacktrace;
-    stack = goog.debug.getNativeStackTrace_(contextFn);
-  }
-  if (!stack) {
-    // NOTE: browsers that have strict mode support also have native "stack"
-    // properties. This function will throw in strict mode.
-    stack = goog.debug.getStacktraceHelper_(
-        opt_fn || arguments.callee.caller, []);
+ol.geom.MultiPolygon.prototype.setFlatCoordinates =
+    function(layout, flatCoordinates, endss) {
+  goog.asserts.assert(!goog.isNull(endss));
+  if (goog.isNull(flatCoordinates) || flatCoordinates.length === 0) {
+    goog.asserts.assert(endss.length === 0);
+  } else {
+    goog.asserts.assert(endss.length > 0);
+    var ends = endss[endss.length - 1];
+    goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]);
   }
-  return stack;
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.endss_ = endss;
+  this.changed();
 };
 
 
 /**
- * Private helper for getStacktrace().
- * @param {Function} fn Function to start getting the trace from.
- * @param {Array} visited List of functions visited so far.
- * @return {string} Stack trace starting from function fn.
- * @suppress {es5Strict}
- * @private
+ * @param {Array.<ol.geom.Polygon>} polygons Polygons.
  */
-goog.debug.getStacktraceHelper_ = function(fn, visited) {
-  var sb = [];
-
-  // Circular reference, certain functions like bind seem to cause a recursive
-  // loop so we need to catch circular references
-  if (goog.array.contains(visited, fn)) {
-    sb.push('[...circular reference...]');
-
-  // Traverse the call stack until function not found or max depth is reached
-  } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
-    sb.push(goog.debug.getFunctionName(fn) + '(');
-    var args = fn.arguments;
-    // Args may be null for some special functions such as host objects or eval.
-    for (var i = 0; args && i < args.length; i++) {
-      if (i > 0) {
-        sb.push(', ');
-      }
-      var argDesc;
-      var arg = args[i];
-      switch (typeof arg) {
-        case 'object':
-          argDesc = arg ? 'object' : 'null';
-          break;
-
-        case 'string':
-          argDesc = arg;
-          break;
-
-        case 'number':
-          argDesc = String(arg);
-          break;
-
-        case 'boolean':
-          argDesc = arg ? 'true' : 'false';
-          break;
-
-        case 'function':
-          argDesc = goog.debug.getFunctionName(arg);
-          argDesc = argDesc ? argDesc : '[fn]';
-          break;
-
-        case 'undefined':
-        default:
-          argDesc = typeof arg;
-          break;
-      }
-
-      if (argDesc.length > 40) {
-        argDesc = argDesc.substr(0, 40) + '...';
-      }
-      sb.push(argDesc);
+ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) {
+  var layout = ol.geom.GeometryLayout.XY;
+  var flatCoordinates = [];
+  var endss = [];
+  var i, ii, ends;
+  for (i = 0, ii = polygons.length; i < ii; ++i) {
+    var polygon = polygons[i];
+    if (i === 0) {
+      layout = polygon.getLayout();
+    } else {
+      // FIXME better handle the case of non-matching layouts
+      goog.asserts.assert(polygon.getLayout() == layout);
     }
-    visited.push(fn);
-    sb.push(')\n');
-    /** @preserveTry */
-    try {
-      sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
-    } catch (e) {
-      sb.push('[exception trying to get caller]\n');
+    var offset = flatCoordinates.length;
+    ends = polygon.getEnds();
+    var j, jj;
+    for (j = 0, jj = ends.length; j < jj; ++j) {
+      ends[j] += offset;
     }
-
-  } else if (fn) {
-    sb.push('[...long stack...]');
-  } else {
-    sb.push('[end]');
+    goog.array.extend(flatCoordinates, polygon.getFlatCoordinates());
+    endss.push(ends);
   }
-  return sb.join('');
+  this.setFlatCoordinates(layout, flatCoordinates, endss);
 };
 
+goog.provide('ol.renderer.vector');
 
-/**
- * Set a custom function name resolver.
- * @param {function(Function): string} resolver Resolves functions to their
- *     names.
- */
-goog.debug.setFunctionResolver = function(resolver) {
-  goog.debug.fnNameResolver_ = resolver;
-};
+goog.require('goog.asserts');
+goog.require('ol.geom.Circle');
+goog.require('ol.geom.GeometryCollection');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.render.IReplayGroup');
+goog.require('ol.style.ImageState');
+goog.require('ol.style.Style');
 
 
 /**
- * Gets a function name
- * @param {Function} fn Function to get name of.
- * @return {string} Function's name.
+ * @param {ol.Feature} feature1 Feature 1.
+ * @param {ol.Feature} feature2 Feature 2.
+ * @return {number} Order.
  */
-goog.debug.getFunctionName = function(fn) {
-  if (goog.debug.fnNameCache_[fn]) {
-    return goog.debug.fnNameCache_[fn];
-  }
-  if (goog.debug.fnNameResolver_) {
-    var name = goog.debug.fnNameResolver_(fn);
-    if (name) {
-      goog.debug.fnNameCache_[fn] = name;
-      return name;
-    }
-  }
-
-  // Heuristically determine function name based on code.
-  var functionSource = String(fn);
-  if (!goog.debug.fnNameCache_[functionSource]) {
-    var matches = /function ([^\(]+)/.exec(functionSource);
-    if (matches) {
-      var method = matches[1];
-      goog.debug.fnNameCache_[functionSource] = method;
-    } else {
-      goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
-    }
-  }
-
-  return goog.debug.fnNameCache_[functionSource];
+ol.renderer.vector.defaultOrder = function(feature1, feature2) {
+  return goog.getUid(feature1) - goog.getUid(feature2);
 };
 
 
 /**
- * Makes whitespace visible by replacing it with printable characters.
- * This is useful in finding diffrences between the expected and the actual
- * output strings of a testcase.
- * @param {string} string whose whitespace needs to be made visible.
- * @return {string} string whose whitespace is made visible.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {number} Squared pixel tolerance.
  */
-goog.debug.makeWhitespaceVisible = function(string) {
-  return string.replace(/ /g, '[_]')
-      .replace(/\f/g, '[f]')
-      .replace(/\n/g, '[n]\n')
-      .replace(/\r/g, '[r]')
-      .replace(/\t/g, '[t]');
+ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) {
+  var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
+  return tolerance * tolerance;
 };
 
 
 /**
- * Hash map for storing function names that have already been looked up.
- * @type {Object}
- * @private
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {number} Pixel tolerance.
  */
-goog.debug.fnNameCache_ = {};
+ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
+  return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
+};
 
 
 /**
- * Resolves functions to their names.  Resolved function names will be cached.
- * @type {function(Function):string}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-goog.debug.fnNameResolver_;
-
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview Definition of the LogRecord class. Please minimize
- * dependencies this file has on other closure classes as any dependency it
- * takes won't be able to use the logging infrastructure.
- *
- */
-
-goog.provide('goog.debug.LogRecord');
-
+ol.renderer.vector.renderCircleGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.Circle);
+  var fillStyle = style.getFill();
+  var strokeStyle = style.getStroke();
+  if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) {
+    var polygonReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.POLYGON);
+    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
+    polygonReplay.drawCircleGeometry(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (!goog.isNull(textStyle)) {
+    var textReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.TEXT);
+    textReplay.setTextStyle(textStyle);
+    textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature);
+  }
+};
 
 
 /**
- * LogRecord objects are used to pass logging requests between
- * the logging framework and individual log Handlers.
- * @constructor
- * @param {goog.debug.Logger.Level} level One of the level identifiers.
- * @param {string} msg The string message.
- * @param {string} loggerName The name of the source logger.
- * @param {number=} opt_time Time this log record was created if other than now.
- *     If 0, we use #goog.now.
- * @param {number=} opt_sequenceNumber Sequence number of this log record. This
- *     should only be passed in when restoring a log record from persistence.
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {function(this: T, goog.events.Event)} listener Listener function.
+ * @param {T} thisArg Value to use as `this` when executing `listener`.
+ * @return {boolean} `true` if style is loading.
+ * @template T
  */
-goog.debug.LogRecord = function(level, msg, loggerName,
-    opt_time, opt_sequenceNumber) {
-  this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
+ol.renderer.vector.renderFeature = function(
+    replayGroup, feature, style, squaredTolerance, listener, thisArg) {
+  var loading = false;
+  var imageStyle, imageState;
+  imageStyle = style.getImage();
+  if (goog.isNull(imageStyle)) {
+    ol.renderer.vector.renderFeature_(
+        replayGroup, feature, style, squaredTolerance);
+  } else {
+    imageState = imageStyle.getImageState();
+    if (imageState == ol.style.ImageState.LOADED ||
+        imageState == ol.style.ImageState.ERROR) {
+      imageStyle.unlistenImageChange(listener, thisArg);
+      if (imageState == ol.style.ImageState.LOADED) {
+        ol.renderer.vector.renderFeature_(
+            replayGroup, feature, style, squaredTolerance);
+      }
+    } else {
+      if (imageState == ol.style.ImageState.IDLE) {
+        imageStyle.load();
+      }
+      imageState = imageStyle.getImageState();
+      goog.asserts.assert(imageState == ol.style.ImageState.LOADING);
+      imageStyle.listenImageChange(listener, thisArg);
+      loading = true;
+    }
+  }
+  return loading;
 };
 
 
 /**
- * Time the LogRecord was created.
- * @type {number}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
+ * @param {number} squaredTolerance Squared tolerance.
  * @private
  */
-goog.debug.LogRecord.prototype.time_;
+ol.renderer.vector.renderFeature_ = function(
+    replayGroup, feature, style, squaredTolerance) {
+  var geometry = style.getGeometryFunction()(feature);
+  if (!goog.isDefAndNotNull(geometry)) {
+    return;
+  }
+  var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
+  var geometryRenderer =
+      ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
+  goog.asserts.assert(goog.isDef(geometryRenderer));
+  geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
+};
 
 
 /**
- * Level of the LogRecord
- * @type {goog.debug.Logger.Level}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-goog.debug.LogRecord.prototype.level_;
+ol.renderer.vector.renderGeometryCollectionGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection);
+  var geometries = geometry.getGeometriesArray();
+  var i, ii;
+  for (i = 0, ii = geometries.length; i < ii; ++i) {
+    var geometryRenderer =
+        ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()];
+    goog.asserts.assert(goog.isDef(geometryRenderer));
+    geometryRenderer(replayGroup, geometries[i], style, feature);
+  }
+};
 
 
 /**
- * Message associated with the record
- * @type {string}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-goog.debug.LogRecord.prototype.msg_;
+ol.renderer.vector.renderLineStringGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+  var strokeStyle = style.getStroke();
+  if (!goog.isNull(strokeStyle)) {
+    var lineStringReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
+    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
+    lineStringReplay.drawLineStringGeometry(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (!goog.isNull(textStyle)) {
+    var textReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.TEXT);
+    textReplay.setTextStyle(textStyle);
+    textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature);
+  }
+};
 
 
 /**
- * Name of the logger that created the record.
- * @type {string}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-goog.debug.LogRecord.prototype.loggerName_;
+ol.renderer.vector.renderMultiLineStringGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
+  var strokeStyle = style.getStroke();
+  if (!goog.isNull(strokeStyle)) {
+    var lineStringReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
+    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
+    lineStringReplay.drawMultiLineStringGeometry(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (!goog.isNull(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);
+  }
+};
 
 
 /**
- * Sequence number for the LogRecord. Each record has a unique sequence number
- * that is greater than all log records created before it.
- * @type {number}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
+ol.renderer.vector.renderMultiPolygonGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon);
+  var fillStyle = style.getFill();
+  var strokeStyle = style.getStroke();
+  if (!goog.isNull(strokeStyle) || !goog.isNull(fillStyle)) {
+    var polygonReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.POLYGON);
+    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
+    polygonReplay.drawMultiPolygonGeometry(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (!goog.isNull(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);
+  }
+};
 
 
 /**
- * Exception associated with the record
- * @type {Object}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-goog.debug.LogRecord.prototype.exception_ = null;
+ol.renderer.vector.renderPointGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.Point);
+  var imageStyle = style.getImage();
+  if (!goog.isNull(imageStyle)) {
+    var imageReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.IMAGE);
+    imageReplay.setImageStyle(imageStyle);
+    imageReplay.drawPointGeometry(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (!goog.isNull(textStyle)) {
+    var textReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.TEXT);
+    textReplay.setTextStyle(textStyle);
+    textReplay.drawText(geometry.getCoordinates(), 0, 2, 2, geometry, feature);
+  }
+};
 
 
 /**
- * Exception text associated with the record
- * @type {?string}
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-goog.debug.LogRecord.prototype.exceptionText_ = null;
+ol.renderer.vector.renderMultiPointGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint);
+  var imageStyle = style.getImage();
+  if (!goog.isNull(imageStyle)) {
+    var imageReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.IMAGE);
+    imageReplay.setImageStyle(imageStyle);
+    imageReplay.drawMultiPointGeometry(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (!goog.isNull(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);
+  }
+};
 
 
 /**
- * @define {boolean} Whether to enable log sequence numbers.
+ * @param {ol.render.IReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature} feature Feature.
+ * @private
  */
-goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
+ol.renderer.vector.renderPolygonGeometry_ =
+    function(replayGroup, geometry, style, feature) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
+  var fillStyle = style.getFill();
+  var strokeStyle = style.getStroke();
+  if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) {
+    var polygonReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.POLYGON);
+    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
+    polygonReplay.drawPolygonGeometry(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (!goog.isNull(textStyle)) {
+    var textReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.TEXT);
+    textReplay.setTextStyle(textStyle);
+    textReplay.drawText(
+        geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature);
+  }
+};
 
 
 /**
- * A sequence counter for assigning increasing sequence numbers to LogRecord
- * objects.
- * @type {number}
+ * @const
  * @private
+ * @type {Object.<ol.geom.GeometryType,
+ *                function(ol.render.IReplayGroup, ol.geom.Geometry,
+ *                         ol.style.Style, Object)>}
  */
-goog.debug.LogRecord.nextSequenceNumber_ = 0;
-
+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_
+};
 
-/**
- * Sets all fields of the log record.
- * @param {goog.debug.Logger.Level} level One of the level identifiers.
- * @param {string} msg The string message.
- * @param {string} loggerName The name of the source logger.
- * @param {number=} opt_time Time this log record was created if other than now.
- *     If 0, we use #goog.now.
- * @param {number=} opt_sequenceNumber Sequence number of this log record. This
- *     should only be passed in when restoring a log record from persistence.
- */
-goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
-    opt_time, opt_sequenceNumber) {
-  if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
-    this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
-        opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
-  }
+goog.provide('ol.renderer.canvas.VectorLayer');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.layer.Vector');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.renderer.vector');
 
-  this.time_ = opt_time || goog.now();
-  this.level_ = level;
-  this.msg_ = msg;
-  this.loggerName_ = loggerName;
-  delete this.exception_;
-  delete this.exceptionText_;
-};
 
 
 /**
- * Get the source Logger's name.
- *
- * @return {string} source logger name (may be null).
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
  */
-goog.debug.LogRecord.prototype.getLoggerName = function() {
-  return this.loggerName_;
-};
+ol.renderer.canvas.VectorLayer = function(mapRenderer, vectorLayer) {
 
+  goog.base(this, mapRenderer, vectorLayer);
 
-/**
- * Get the exception that is part of the log record.
- *
- * @return {Object} the exception.
- */
-goog.debug.LogRecord.prototype.getException = function() {
-  return this.exception_;
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.dirty_ = false;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = -1;
 
-/**
- * Set the exception that is part of the log record.
- *
- * @param {Object} exception the exception.
- */
-goog.debug.LogRecord.prototype.setException = function(exception) {
-  this.exception_ = exception;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedResolution_ = NaN;
 
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.renderedExtent_ = ol.extent.createEmpty();
 
-/**
- * Get the exception text that is part of the log record.
- *
- * @return {?string} Exception text.
- */
-goog.debug.LogRecord.prototype.getExceptionText = function() {
-  return this.exceptionText_;
-};
+  /**
+   * @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();
 
-/**
- * Set the exception text that is part of the log record.
- *
- * @param {string} text The exception text.
- */
-goog.debug.LogRecord.prototype.setExceptionText = function(text) {
-  this.exceptionText_ = text;
 };
+goog.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
 
 
 /**
- * Get the source Logger's name.
- *
- * @param {string} loggerName source logger name (may be null).
+ * @inheritDoc
  */
-goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
-  this.loggerName_ = loggerName;
-};
+ol.renderer.canvas.VectorLayer.prototype.composeFrame =
+    function(frameState, layerState, context) {
 
+  var transform = this.getTransform(frameState);
 
-/**
- * Get the logging message level, for example Level.SEVERE.
- * @return {goog.debug.Logger.Level} the logging message level.
- */
-goog.debug.LogRecord.prototype.getLevel = function() {
-  return this.level_;
-};
+  this.dispatchPreComposeEvent(context, frameState, transform);
 
+  var replayGroup = this.replayGroup_;
+  if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) {
+    var layer = this.getLayer();
+    var replayContext;
+    if (layer.hasListener(ol.render.EventType.RENDER)) {
+      // resize and clear
+      this.context_.canvas.width = context.canvas.width;
+      this.context_.canvas.height = context.canvas.height;
+      replayContext = this.context_;
+    } else {
+      replayContext = context;
+    }
+    // for performance reasons, context.save / context.restore is not used
+    // to save and restore the transformation matrix and the opacity.
+    // see http://jsperf.com/context-save-restore-versus-variable
+    var alpha = replayContext.globalAlpha;
+    replayContext.globalAlpha = layerState.opacity;
+    replayGroup.replay(
+        replayContext, frameState.pixelRatio, transform,
+        frameState.viewState.rotation, frameState.skippedFeatureUids);
 
-/**
- * Set the logging message level, for example Level.SEVERE.
- * @param {goog.debug.Logger.Level} level the logging message level.
- */
-goog.debug.LogRecord.prototype.setLevel = function(level) {
-  this.level_ = level;
-};
+    if (replayContext != context) {
+      this.dispatchRenderEvent(replayContext, frameState, transform);
+      context.drawImage(replayContext.canvas, 0, 0);
+    }
+    replayContext.globalAlpha = alpha;
+  }
 
+  this.dispatchPostComposeEvent(context, frameState, transform);
 
-/**
- * Get the "raw" log message, before localization or formatting.
- *
- * @return {string} the raw message string.
- */
-goog.debug.LogRecord.prototype.getMessage = function() {
-  return this.msg_;
 };
 
 
 /**
- * Set the "raw" log message, before localization or formatting.
- *
- * @param {string} msg the raw message string.
+ * @inheritDoc
  */
-goog.debug.LogRecord.prototype.setMessage = function(msg) {
-  this.msg_ = msg;
+ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel =
+    function(coordinate, frameState, callback, thisArg) {
+  if (goog.isNull(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_.forEachGeometryAtPixel(resolution,
+        rotation, coordinate, frameState.skippedFeatureUids,
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          goog.asserts.assert(goog.isDef(feature));
+          var key = goog.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback.call(thisArg, feature, layer);
+          }
+        });
+  }
 };
 
 
 /**
- * Get event time in milliseconds since 1970.
- *
- * @return {number} event time in millis since 1970.
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
  */
-goog.debug.LogRecord.prototype.getMillis = function() {
-  return this.time_;
+ol.renderer.canvas.VectorLayer.prototype.handleImageChange_ =
+    function(event) {
+  this.renderIfReadyAndVisible();
 };
 
 
 /**
- * Set event time in milliseconds since 1970.
- *
- * @param {number} time event time in millis since 1970.
+ * @inheritDoc
  */
-goog.debug.LogRecord.prototype.setMillis = function(time) {
-  this.time_ = time;
-};
+ol.renderer.canvas.VectorLayer.prototype.prepareFrame =
+    function(frameState, layerState) {
 
+  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+  goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
+  var vectorSource = vectorLayer.getSource();
 
-/**
- * Get the sequence number.
- * <p>
- * Sequence numbers are normally assigned in the LogRecord
- * constructor, which assigns unique sequence numbers to
- * each new LogRecord in increasing order.
- * @return {number} the sequence number.
- */
-goog.debug.LogRecord.prototype.getSequenceNumber = function() {
-  return this.sequenceNumber_;
-};
+  this.updateAttributions(
+      frameState.attributions, vectorSource.getAttributions());
+  this.updateLogos(frameState, vectorSource);
 
+  if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
+      frameState.viewHints[ol.ViewHint.INTERACTING])) {
+    return true;
+  }
 
-// Copyright 2010 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+  var 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();
 
-/**
- * @fileoverview A buffer for log records. The purpose of this is to improve
- * logging performance by re-using old objects when the buffer becomes full and
- * to eliminate the need for each app to implement their own log buffer. The
- * disadvantage to doing this is that log handlers cannot maintain references to
- * log records and expect that they are not overwriten at a later point.
- *
- * @author agrieve@google.com (Andrew Grieve)
- */
+  if (!goog.isDef(vectorLayerRenderOrder)) {
+    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
+  }
 
-goog.provide('goog.debug.LogBuffer');
+  var extent = ol.extent.buffer(frameStateExtent,
+      vectorLayerRenderBuffer * resolution);
 
-goog.require('goog.asserts');
-goog.require('goog.debug.LogRecord');
+  if (!this.dirty_ &&
+      this.renderedResolution_ == resolution &&
+      this.renderedRevision_ == vectorLayerRevision &&
+      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
+      ol.extent.containsExtent(this.renderedExtent_, extent)) {
+    return true;
+  }
 
+  // FIXME dispose of old replayGroup in post render
+  goog.dispose(this.replayGroup_);
+  this.replayGroup_ = null;
 
+  this.dirty_ = false;
 
-/**
- * Creates the log buffer.
- * @constructor
- * @final
- */
-goog.debug.LogBuffer = function() {
-  goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
-      'Cannot use goog.debug.LogBuffer without defining ' +
-      'goog.debug.LogBuffer.CAPACITY.');
-  this.clear();
+  var replayGroup =
+      new ol.render.canvas.ReplayGroup(
+          ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
+          resolution);
+  vectorSource.loadFeatures(extent, resolution, projection);
+  var renderFeature =
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @this {ol.renderer.canvas.VectorLayer}
+       */
+      function(feature) {
+    var styles;
+    if (goog.isDef(feature.getStyleFunction())) {
+      styles = feature.getStyleFunction().call(feature, resolution);
+    } else if (goog.isDef(vectorLayer.getStyleFunction())) {
+      styles = vectorLayer.getStyleFunction()(feature, resolution);
+    }
+    if (goog.isDefAndNotNull(styles)) {
+      var dirty = this.renderFeature(
+          feature, resolution, pixelRatio, styles, replayGroup);
+      this.dirty_ = this.dirty_ || dirty;
+    }
+  };
+  if (!goog.isNull(vectorLayerRenderOrder)) {
+    /** @type {Array.<ol.Feature>} */
+    var features = [];
+    vectorSource.forEachFeatureInExtentAtResolution(extent, resolution,
+        /**
+         * @param {ol.Feature} feature Feature.
+         */
+        function(feature) {
+          features.push(feature);
+        }, this);
+    goog.array.sort(features, vectorLayerRenderOrder);
+    goog.array.forEach(features, renderFeature, this);
+  } else {
+    vectorSource.forEachFeatureInExtentAtResolution(
+        extent, resolution, renderFeature, this);
+  }
+  replayGroup.finish();
+
+  this.renderedResolution_ = resolution;
+  this.renderedRevision_ = vectorLayerRevision;
+  this.renderedRenderOrder_ = vectorLayerRenderOrder;
+  this.renderedExtent_ = extent;
+  this.replayGroup_ = replayGroup;
+
+  return true;
 };
 
 
 /**
- * A static method that always returns the same instance of LogBuffer.
- * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
+ * @param {ol.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {Array.<ol.style.Style>} styles Array of styles
+ * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
+ * @return {boolean} `true` if an image is loading.
  */
-goog.debug.LogBuffer.getInstance = function() {
-  if (!goog.debug.LogBuffer.instance_) {
-    // This function is written with the return statement after the assignment
-    // to avoid the jscompiler StripCode bug described in http://b/2608064.
-    // After that bug is fixed this can be refactored.
-    goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
+ol.renderer.canvas.VectorLayer.prototype.renderFeature =
+    function(feature, resolution, pixelRatio, styles, replayGroup) {
+  if (!goog.isDefAndNotNull(styles)) {
+    return false;
   }
-  return goog.debug.LogBuffer.instance_;
+  var i, ii, loading = false;
+  for (i = 0, ii = styles.length; i < ii; ++i) {
+    loading = ol.renderer.vector.renderFeature(
+        replayGroup, feature, styles[i],
+        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+        this.handleImageChange_, this) || loading;
+  }
+  return loading;
 };
 
+// FIXME offset panning
 
-/**
- * @define {number} The number of log records to buffer. 0 means disable
- * buffering.
- */
-goog.define('goog.debug.LogBuffer.CAPACITY', 0);
+goog.provide('ol.renderer.canvas.Map');
 
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.style');
+goog.require('goog.vec.Mat4');
+goog.require('ol');
+goog.require('ol.RendererType');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.layer.Image');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas.Immediate');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.canvas.ImageLayer');
+goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.renderer.canvas.TileLayer');
+goog.require('ol.renderer.canvas.VectorLayer');
+goog.require('ol.renderer.vector');
+goog.require('ol.source.State');
+goog.require('ol.vec.Mat4');
 
-/**
- * The array to store the records.
- * @type {!Array.<!goog.debug.LogRecord|undefined>}
- * @private
- */
-goog.debug.LogBuffer.prototype.buffer_;
 
 
 /**
- * The index of the most recently added record or -1 if there are no records.
- * @type {number}
- * @private
+ * @constructor
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
  */
-goog.debug.LogBuffer.prototype.curIndex_;
-
+ol.renderer.canvas.Map = function(container, map) {
 
-/**
- * Whether the buffer is at capacity.
- * @type {boolean}
- * @private
- */
-goog.debug.LogBuffer.prototype.isFull_;
+  goog.base(this, container, map);
 
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = ol.dom.createCanvasContext2D();
 
-/**
- * Adds a log record to the buffer, possibly overwriting the oldest record.
- * @param {goog.debug.Logger.Level} level One of the level identifiers.
- * @param {string} msg The string message.
- * @param {string} loggerName The name of the source logger.
- * @return {!goog.debug.LogRecord} The log record.
- */
-goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
-  var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
-  this.curIndex_ = curIndex;
-  if (this.isFull_) {
-    var ret = this.buffer_[curIndex];
-    ret.reset(level, msg, loggerName);
-    return ret;
-  }
-  this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
-  return this.buffer_[curIndex] =
-      new goog.debug.LogRecord(level, msg, loggerName);
-};
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = this.context_.canvas;
 
+  this.canvas_.style.width = '100%';
+  this.canvas_.style.height = '100%';
+  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
+  goog.dom.insertChildAt(container, this.canvas_, 0);
 
-/**
- * @return {boolean} Whether the log buffer is enabled.
- */
-goog.debug.LogBuffer.isBufferingEnabled = function() {
-  return goog.debug.LogBuffer.CAPACITY > 0;
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.renderedVisible_ = true;
 
+  /**
+   * @private
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.transform_ = goog.vec.Mat4.createNumber();
 
-/**
- * Removes all buffered log records.
- */
-goog.debug.LogBuffer.prototype.clear = function() {
-  this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
-  this.curIndex_ = -1;
-  this.isFull_ = false;
 };
+goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
 
 
 /**
- * Calls the given function for each buffered log record, starting with the
- * oldest one.
- * @param {function(!goog.debug.LogRecord)} func The function to call.
+ * @inheritDoc
  */
-goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
-  var buffer = this.buffer_;
-  // Corner case: no records.
-  if (!buffer[0]) {
-    return;
+ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) {
+  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
+    return new ol.renderer.canvas.ImageLayer(this, layer);
+  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
+    return new ol.renderer.canvas.TileLayer(this, layer);
+  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
+    return new ol.renderer.canvas.VectorLayer(this, layer);
+  } else {
+    goog.asserts.fail();
+    return null;
   }
-  var curIndex = this.curIndex_;
-  var i = this.isFull_ ? curIndex : -1;
-  do {
-    i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
-    func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
-  } while (i != curIndex);
 };
 
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
 /**
- * @fileoverview Definition of the Logger class. Please minimize dependencies
- * this file has on other closure classes as any dependency it takes won't be
- * able to use the logging infrastructure.
- *
- * @see ../demos/debug.html
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
+ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
+    function(type, frameState) {
+  var map = this.getMap();
+  var context = this.context_;
+  if (map.hasListener(type)) {
+    var extent = frameState.extent;
+    var pixelRatio = frameState.pixelRatio;
+    var viewState = frameState.viewState;
+    var resolution = viewState.resolution;
+    var rotation = viewState.rotation;
 
-goog.provide('goog.debug.LogManager');
-goog.provide('goog.debug.Loggable');
-goog.provide('goog.debug.Logger');
-goog.provide('goog.debug.Logger.Level');
+    ol.vec.Mat4.makeTransform2D(this.transform_,
+        this.canvas_.width / 2, this.canvas_.height / 2,
+        pixelRatio / resolution, -pixelRatio / resolution,
+        -rotation,
+        -viewState.center[0], -viewState.center[1]);
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.debug');
-goog.require('goog.debug.LogBuffer');
-goog.require('goog.debug.LogRecord');
+    var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
+    var replayGroup = new ol.render.canvas.ReplayGroup(tolerance, extent,
+        resolution);
+
+    var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
+        extent, this.transform_, rotation);
+    var composeEvent = new ol.render.Event(type, map, vectorContext,
+        replayGroup, frameState, context, null);
+    map.dispatchEvent(composeEvent);
+
+    replayGroup.finish();
+    if (!replayGroup.isEmpty()) {
+      replayGroup.replay(context, pixelRatio, this.transform_,
+          rotation, {});
+    }
+    vectorContext.flush();
+    this.replayGroup = replayGroup;
+  }
+};
 
 
 /**
- * A message value that can be handled by a Logger.
- *
- * Functions are treated like callbacks, but are only called when the event's
- * log level is enabled. This is useful for logging messages that are expensive
- * to construct.
- *
- * @typedef {string|function(): string}
+ * @param {ol.layer.Layer} layer Layer.
+ * @return {ol.renderer.canvas.Layer} Canvas layer renderer.
  */
-goog.debug.Loggable;
-
+ol.renderer.canvas.Map.prototype.getCanvasLayerRenderer = function(layer) {
+  var layerRenderer = this.getLayerRenderer(layer);
+  goog.asserts.assertInstanceof(layerRenderer, ol.renderer.canvas.Layer);
+  return /** @type {ol.renderer.canvas.Layer} */ (layerRenderer);
+};
 
 
 /**
- * The Logger is an object used for logging debug messages. Loggers are
- * normally named, using a hierarchical dot-separated namespace. Logger names
- * can be arbitrary strings, but they should normally be based on the package
- * name or class name of the logged component, such as goog.net.BrowserChannel.
- *
- * The Logger object is loosely based on the java class
- * java.util.logging.Logger. It supports different levels of filtering for
- * different loggers.
- *
- * The logger object should never be instantiated by application code. It
- * should always use the goog.debug.Logger.getLogger function.
- *
- * @constructor
- * @param {string} name The name of the Logger.
- * @final
+ * @inheritDoc
  */
-goog.debug.Logger = function(name) {
-  /**
-   * Name of the Logger. Generally a dot-separated namespace
-   * @private {string}
-   */
-  this.name_ = name;
+ol.renderer.canvas.Map.prototype.getType = function() {
+  return ol.RendererType.CANVAS;
+};
 
-  /**
-   * Parent Logger.
-   * @private {goog.debug.Logger}
-   */
-  this.parent_ = null;
 
-  /**
-   * Level that this logger only filters above. Null indicates it should
-   * inherit from the parent.
-   * @private {goog.debug.Logger.Level}
-   */
-  this.level_ = null;
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
 
-  /**
-   * Map of children loggers. The keys are the leaf names of the children and
-   * the values are the child loggers.
-   * @private {Object}
-   */
-  this.children_ = null;
+  if (goog.isNull(frameState)) {
+    if (this.renderedVisible_) {
+      goog.style.setElementShown(this.canvas_, false);
+      this.renderedVisible_ = false;
+    }
+    return;
+  }
 
-  /**
-   * Handlers that are listening to this logger.
-   * @private {Array.<Function>}
-   */
-  this.handlers_ = null;
-};
+  var context = this.context_;
+  var width = frameState.size[0] * frameState.pixelRatio;
+  var height = frameState.size[1] * frameState.pixelRatio;
+  if (this.canvas_.width != width || this.canvas_.height != height) {
+    this.canvas_.width = width;
+    this.canvas_.height = height;
+  } else {
+    context.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
+  }
 
+  this.calculateMatrices2D(frameState);
 
-/** @const */
-goog.debug.Logger.ROOT_LOGGER_NAME = '';
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
 
+  var layerStatesArray = frameState.layerStatesArray;
+  var viewResolution = frameState.viewState.resolution;
+  var i, ii, layer, layerRenderer, layerState;
+  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
+    layerState = layerStatesArray[i];
+    layer = layerState.layer;
+    layerRenderer = this.getLayerRenderer(layer);
+    goog.asserts.assertInstanceof(layerRenderer, ol.renderer.canvas.Layer);
+    if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) ||
+        layerState.sourceState != ol.source.State.READY) {
+      continue;
+    }
+    if (layerRenderer.prepareFrame(frameState, layerState)) {
+      layerRenderer.composeFrame(frameState, layerState, context);
+    }
+  }
 
-/**
- * @define {boolean} Toggles whether loggers other than the root logger can have
- *     log handlers attached to them and whether they can have their log level
- *     set. Logging is a bit faster when this is set to false.
- */
-goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
+  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
 
+  if (!this.renderedVisible_) {
+    goog.style.setElementShown(this.canvas_, true);
+    this.renderedVisible_ = true;
+  }
 
-if (!goog.debug.Logger.ENABLE_HIERARCHY) {
-  /**
-   * @type {!Array.<Function>}
-   * @private
-   */
-  goog.debug.Logger.rootHandlers_ = [];
+  this.scheduleRemoveUnusedLayerRenderers(frameState);
+  this.scheduleExpireIconCache(frameState);
+};
 
+goog.provide('ol.renderer.dom.Layer');
 
-  /**
-   * @type {goog.debug.Logger.Level}
-   * @private
-   */
-  goog.debug.Logger.rootLevel_;
-}
+goog.require('goog.dom');
+goog.require('ol.layer.Layer');
+goog.require('ol.renderer.Layer');
 
 
 
 /**
- * The Level class defines a set of standard logging levels that
- * can be used to control logging output.  The logging Level objects
- * are ordered and are specified by ordered integers.  Enabling logging
- * at a given level also enables logging at all higher levels.
- * <p>
- * Clients should normally use the predefined Level constants such
- * as Level.SEVERE.
- * <p>
- * The levels in descending order are:
- * <ul>
- * <li>SEVERE (highest value)
- * <li>WARNING
- * <li>INFO
- * <li>CONFIG
- * <li>FINE
- * <li>FINER
- * <li>FINEST  (lowest value)
- * </ul>
- * In addition there is a level OFF that can be used to turn
- * off logging, and a level ALL that can be used to enable
- * logging of all messages.
- *
- * @param {string} name The name of the level.
- * @param {number} value The numeric value of the level.
  * @constructor
- * @final
+ * @extends {ol.renderer.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Layer} layer Layer.
+ * @param {!Element} target Target.
  */
-goog.debug.Logger.Level = function(name, value) {
-  /**
-   * The name of the level
-   * @type {string}
-   */
-  this.name = name;
+ol.renderer.dom.Layer = function(mapRenderer, layer, target) {
+
+  goog.base(this, mapRenderer, layer);
 
   /**
-   * The numeric value of the level
-   * @type {number}
+   * @type {!Element}
+   * @protected
    */
-  this.value = value;
+  this.target = target;
+
 };
+goog.inherits(ol.renderer.dom.Layer, ol.renderer.Layer);
 
 
 /**
- * @return {string} String representation of the logger level.
- * @override
+ * Clear rendered elements.
  */
-goog.debug.Logger.Level.prototype.toString = function() {
-  return this.name;
-};
+ol.renderer.dom.Layer.prototype.clearFrame = goog.nullFunction;
 
 
 /**
- * OFF is a special level that can be used to turn off logging.
- * This level is initialized to <CODE>Infinity</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
  */
-goog.debug.Logger.Level.OFF =
-    new goog.debug.Logger.Level('OFF', Infinity);
+ol.renderer.dom.Layer.prototype.composeFrame = goog.nullFunction;
 
 
 /**
- * SHOUT is a message level for extra debugging loudness.
- * This level is initialized to <CODE>1200</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @return {!Element} Target.
  */
-goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
+ol.renderer.dom.Layer.prototype.getTarget = function() {
+  return this.target;
+};
 
 
 /**
- * SEVERE is a message level indicating a serious failure.
- * This level is initialized to <CODE>1000</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @return {boolean} whether composeFrame should be called.
  */
-goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
+ol.renderer.dom.Layer.prototype.prepareFrame = goog.abstractMethod;
 
+goog.provide('ol.renderer.dom.ImageLayer');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.vec.Mat4');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.layer.Image');
+goog.require('ol.proj');
+goog.require('ol.renderer.dom.Layer');
+goog.require('ol.vec.Mat4');
 
-/**
- * WARNING is a message level indicating a potential problem.
- * This level is initialized to <CODE>900</CODE>.
- * @type {!goog.debug.Logger.Level}
- */
-goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
 
 
 /**
- * INFO is a message level for informational messages.
- * This level is initialized to <CODE>800</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @constructor
+ * @extends {ol.renderer.dom.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Image} imageLayer Image layer.
  */
-goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
+ol.renderer.dom.ImageLayer = function(mapRenderer, imageLayer) {
+  var target = goog.dom.createElement(goog.dom.TagName.DIV);
+  target.style.position = 'absolute';
 
+  goog.base(this, mapRenderer, imageLayer, target);
 
-/**
- * CONFIG is a message level for static configuration messages.
- * This level is initialized to <CODE>700</CODE>.
- * @type {!goog.debug.Logger.Level}
- */
-goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
+  /**
+   * The last rendered image.
+   * @private
+   * @type {?ol.ImageBase}
+   */
+  this.image_ = null;
+
+  /**
+   * @private
+   * @type {goog.vec.Mat4.Number}
+   */
+  this.transform_ = goog.vec.Mat4.createNumberIdentity();
+
+};
+goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer);
 
 
 /**
- * FINE is a message level providing tracing information.
- * This level is initialized to <CODE>500</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @inheritDoc
  */
-goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
+ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtPixel =
+    function(coordinate, frameState, callback, thisArg) {
+  var layer = this.getLayer();
+  var source = layer.getSource();
+  var resolution = frameState.viewState.resolution;
+  var rotation = frameState.viewState.rotation;
+  var skippedFeatureUids = frameState.skippedFeatureUids;
+  return source.forEachFeatureAtPixel(
+      resolution, rotation, coordinate, skippedFeatureUids,
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {?} Callback result.
+       */
+      function(feature) {
+        return callback.call(thisArg, feature, layer);
+      });
+};
 
 
 /**
- * FINER indicates a fairly detailed tracing message.
- * This level is initialized to <CODE>400</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @inheritDoc
  */
-goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
+ol.renderer.dom.ImageLayer.prototype.clearFrame = function() {
+  goog.dom.removeChildren(this.target);
+  this.image_ = null;
+};
+
 
 /**
- * FINEST indicates a highly detailed tracing message.
- * This level is initialized to <CODE>300</CODE>.
- * @type {!goog.debug.Logger.Level}
+ * @inheritDoc
  */
+ol.renderer.dom.ImageLayer.prototype.prepareFrame =
+    function(frameState, layerState) {
 
-goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
+  var viewState = frameState.viewState;
+  var viewCenter = viewState.center;
+  var viewResolution = viewState.resolution;
+  var viewRotation = viewState.rotation;
 
+  var image = this.image_;
+  var imageLayer = this.getLayer();
+  goog.asserts.assertInstanceof(imageLayer, ol.layer.Image);
+  var imageSource = imageLayer.getSource();
 
-/**
- * ALL indicates that all messages should be logged.
- * This level is initialized to <CODE>0</CODE>.
- * @type {!goog.debug.Logger.Level}
- */
-goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
+  var hints = frameState.viewHints;
 
+  var renderedExtent = frameState.extent;
+  if (goog.isDef(layerState.extent)) {
+    renderedExtent = ol.extent.getIntersection(
+        renderedExtent, layerState.extent);
+  }
 
-/**
- * The predefined levels.
- * @type {!Array.<!goog.debug.Logger.Level>}
- * @final
- */
-goog.debug.Logger.Level.PREDEFINED_LEVELS = [
-  goog.debug.Logger.Level.OFF,
-  goog.debug.Logger.Level.SHOUT,
-  goog.debug.Logger.Level.SEVERE,
-  goog.debug.Logger.Level.WARNING,
-  goog.debug.Logger.Level.INFO,
-  goog.debug.Logger.Level.CONFIG,
-  goog.debug.Logger.Level.FINE,
-  goog.debug.Logger.Level.FINER,
-  goog.debug.Logger.Level.FINEST,
-  goog.debug.Logger.Level.ALL];
+  if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
+      !ol.extent.isEmpty(renderedExtent)) {
+    var projection = viewState.projection;
+    var sourceProjection = imageSource.getProjection();
+    if (!goog.isNull(sourceProjection)) {
+      goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection));
+      projection = sourceProjection;
+    }
+    var image_ = imageSource.getImage(renderedExtent, viewResolution,
+        frameState.pixelRatio, projection);
+    if (!goog.isNull(image_)) {
+      var imageState = image_.getState();
+      if (imageState == ol.ImageState.IDLE) {
+        goog.events.listenOnce(image_, goog.events.EventType.CHANGE,
+            this.handleImageChange, false, this);
+        image_.load();
+      } else if (imageState == ol.ImageState.LOADED) {
+        image = image_;
+      }
+    }
+  }
 
+  if (!goog.isNull(image)) {
+    var imageExtent = image.getExtent();
+    var imageResolution = image.getResolution();
+    var transform = goog.vec.Mat4.createNumber();
+    ol.vec.Mat4.makeTransform2D(transform,
+        frameState.size[0] / 2, frameState.size[1] / 2,
+        imageResolution / viewResolution, imageResolution / viewResolution,
+        viewRotation,
+        (imageExtent[0] - viewCenter[0]) / imageResolution,
+        (viewCenter[1] - imageExtent[3]) / imageResolution);
+    if (image != this.image_) {
+      var imageElement = image.getImage(this);
+      // Bootstrap sets the style max-width: 100% for all images, which breaks
+      // prevents the image from being displayed in FireFox.  Workaround by
+      // overriding the max-width style.
+      imageElement.style.maxWidth = 'none';
+      imageElement.style.position = 'absolute';
+      goog.dom.removeChildren(this.target);
+      goog.dom.appendChild(this.target, imageElement);
+      this.image_ = image;
+    }
+    this.setTransform_(transform);
+    this.updateAttributions(frameState.attributions, image.getAttributions());
+    this.updateLogos(frameState, imageSource);
+  }
 
-/**
- * A lookup map used to find the level object based on the name or value of
- * the level object.
- * @type {Object}
- * @private
- */
-goog.debug.Logger.Level.predefinedLevelsCache_ = null;
+  return true;
+};
 
 
 /**
- * Creates the predefined levels cache and populates it.
+ * @param {goog.vec.Mat4.Number} transform Transform.
  * @private
  */
-goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
-  goog.debug.Logger.Level.predefinedLevelsCache_ = {};
-  for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
-       i++) {
-    goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
-    goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
+ol.renderer.dom.ImageLayer.prototype.setTransform_ = function(transform) {
+  if (!ol.vec.Mat4.equals2D(transform, this.transform_)) {
+    ol.dom.transformElement2D(this.target, transform, 6);
+    goog.vec.Mat4.setFromArray(this.transform_, transform);
   }
 };
 
+// FIXME probably need to reset TileLayerZ if offsets get too large
+// FIXME when zooming out, preserve higher Z divs to avoid white flash
 
-/**
- * Gets the predefined level with the given name.
- * @param {string} name The name of the level.
- * @return {goog.debug.Logger.Level} The level, or null if none found.
- */
-goog.debug.Logger.Level.getPredefinedLevel = function(name) {
-  if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
-    goog.debug.Logger.Level.createPredefinedLevelsCache_();
-  }
+goog.provide('ol.renderer.dom.TileLayer');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('goog.vec.Mat4');
+goog.require('ol');
+goog.require('ol.Coordinate');
+goog.require('ol.TileCoord');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.dom.BrowserFeature');
+goog.require('ol.extent');
+goog.require('ol.layer.Tile');
+goog.require('ol.renderer.dom.Layer');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.TileGrid');
+goog.require('ol.vec.Mat4');
 
-  return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
-};
 
 
 /**
- * Gets the highest predefined level <= #value.
- * @param {number} value Level value.
- * @return {goog.debug.Logger.Level} The level, or null if none found.
+ * @constructor
+ * @extends {ol.renderer.dom.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Tile} tileLayer Tile layer.
  */
-goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
-  if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
-    goog.debug.Logger.Level.createPredefinedLevelsCache_();
-  }
+ol.renderer.dom.TileLayer = function(mapRenderer, tileLayer) {
 
-  if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
-    return goog.debug.Logger.Level.predefinedLevelsCache_[value];
-  }
+  var target = goog.dom.createElement(goog.dom.TagName.DIV);
+  target.style.position = 'absolute';
 
-  for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
-    var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
-    if (level.value <= value) {
-      return level;
-    }
+  // Needed for IE7-8 to render a transformed element correctly
+  if (ol.dom.BrowserFeature.USE_MS_MATRIX_TRANSFORM) {
+    target.style.width = '100%';
+    target.style.height = '100%';
   }
-  return null;
-};
 
+  goog.base(this, mapRenderer, tileLayer, target);
 
-/**
- * Finds or creates a logger for a named subsystem. If a logger has already been
- * created with the given name it is returned. Otherwise a new logger is
- * created. If a new logger is created its log level will be configured based
- * on the LogManager configuration and it will configured to also send logging
- * output to its parent's handlers. It will be registered in the LogManager
- * global namespace.
- *
- * @param {string} name A name for the logger. This should be a dot-separated
- * name and should normally be based on the package name or class name of the
- * subsystem, such as goog.net.BrowserChannel.
- * @return {!goog.debug.Logger} The named logger.
- * @deprecated use goog.log instead. http://go/goog-debug-logger-deprecated
- */
-goog.debug.Logger.getLogger = function(name) {
-  return goog.debug.LogManager.getLogger(name);
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.renderedVisible_ = true;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedOpacity_ = 1;
 
-/**
- * Logs a message to profiling tools, if available.
- * {@see https://developers.google.com/web-toolkit/speedtracer/logging-api}
- * {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
- * @param {string} msg The message to log.
- */
-goog.debug.Logger.logToProfilers = function(msg) {
-  // Using goog.global, as loggers might be used in window-less contexts.
-  if (goog.global['console']) {
-    if (goog.global['console']['timeStamp']) {
-      // Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
-      goog.global['console']['timeStamp'](msg);
-    } else if (goog.global['console']['markTimeline']) {
-      // TODO(user): markTimeline is deprecated. Drop this else clause entirely
-      // after Chrome M14 hits stable.
-      goog.global['console']['markTimeline'](msg);
-    }
-  }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = 0;
+
+  /**
+   * @private
+   * @type {Object.<number, ol.renderer.dom.TileLayerZ_>}
+   */
+  this.tileLayerZs_ = {};
 
-  if (goog.global['msWriteProfilerMark']) {
-    // Logs a message to the Microsoft profiler
-    goog.global['msWriteProfilerMark'](msg);
-  }
 };
+goog.inherits(ol.renderer.dom.TileLayer, ol.renderer.dom.Layer);
 
 
 /**
- * Gets the name of this logger.
- * @return {string} The name of this logger.
+ * @inheritDoc
  */
-goog.debug.Logger.prototype.getName = function() {
-  return this.name_;
+ol.renderer.dom.TileLayer.prototype.clearFrame = function() {
+  goog.dom.removeChildren(this.target);
+  this.renderedRevision_ = 0;
 };
 
 
 /**
- * Adds a handler to the logger. This doesn't use the event system because
- * we want to be able to add logging to the event system.
- * @param {Function} handler Handler function to add.
+ * @inheritDoc
  */
-goog.debug.Logger.prototype.addHandler = function(handler) {
-  if (goog.debug.LOGGING_ENABLED) {
-    if (goog.debug.Logger.ENABLE_HIERARCHY) {
-      if (!this.handlers_) {
-        this.handlers_ = [];
-      }
-      this.handlers_.push(handler);
-    } else {
-      goog.asserts.assert(!this.name_,
-          'Cannot call addHandler on a non-root logger when ' +
-          'goog.debug.Logger.ENABLE_HIERARCHY is false.');
-      goog.debug.Logger.rootHandlers_.push(handler);
+ol.renderer.dom.TileLayer.prototype.prepareFrame =
+    function(frameState, layerState) {
+
+  if (!layerState.visible) {
+    if (this.renderedVisible_) {
+      goog.style.setElementShown(this.target, false);
+      this.renderedVisible_ = false;
     }
+    return true;
   }
-};
 
+  var pixelRatio = frameState.pixelRatio;
+  var viewState = frameState.viewState;
+  var projection = viewState.projection;
 
-/**
- * Removes a handler from the logger. This doesn't use the event system because
- * we want to be able to add logging to the event system.
- * @param {Function} handler Handler function to remove.
- * @return {boolean} Whether the handler was removed.
- */
-goog.debug.Logger.prototype.removeHandler = function(handler) {
-  if (goog.debug.LOGGING_ENABLED) {
-    var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
-        goog.debug.Logger.rootHandlers_;
-    return !!handlers && goog.array.remove(handlers, handler);
+  var tileLayer = this.getLayer();
+  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
+  var tileSource = tileLayer.getSource();
+  var tileGrid = tileSource.getTileGridForProjection(projection);
+  var tileGutter = tileSource.getGutter();
+  var z = tileGrid.getZForResolution(viewState.resolution);
+  var tileResolution = tileGrid.getResolution(z);
+  var center = viewState.center;
+  var extent;
+  if (tileResolution == viewState.resolution) {
+    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
+    extent = ol.extent.getForViewAndSize(
+        center, tileResolution, viewState.rotation, frameState.size);
   } else {
-    return false;
+    extent = frameState.extent;
   }
-};
 
+  if (goog.isDef(layerState.extent)) {
+    extent = ol.extent.getIntersection(extent, layerState.extent);
+  }
 
-/**
- * Returns the parent of this logger.
- * @return {goog.debug.Logger} The parent logger or null if this is the root.
- */
-goog.debug.Logger.prototype.getParent = function() {
-  return this.parent_;
-};
+  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
+      extent, tileResolution);
 
+  /** @type {Object.<number, Object.<string, ol.Tile>>} */
+  var tilesToDrawByZ = {};
+  tilesToDrawByZ[z] = {};
 
-/**
- * Returns the children of this logger as a map of the child name to the logger.
- * @return {!Object} The map where the keys are the child leaf names and the
- *     values are the Logger objects.
- */
-goog.debug.Logger.prototype.getChildren = function() {
-  if (!this.children_) {
-    this.children_ = {};
+  var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
+    return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED;
+  }, tileSource, pixelRatio, projection);
+  var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
+      tilesToDrawByZ, getTileIfLoaded);
+
+  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
+  if (!goog.isDef(useInterimTilesOnError)) {
+    useInterimTilesOnError = true;
   }
-  return this.children_;
-};
 
+  var tmpExtent = ol.extent.createEmpty();
+  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
+  var childTileRange, fullyLoaded, tile, tileState, x, y;
+  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
+    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+
+      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
+      tileState = tile.getState();
+      if (tileState == ol.TileState.LOADED) {
+        tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
+        continue;
+      } else if (tileState == ol.TileState.EMPTY ||
+                 (tileState == ol.TileState.ERROR &&
+                  !useInterimTilesOnError)) {
+        continue;
+      }
+
+      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
+          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
+      if (!fullyLoaded) {
+        childTileRange = tileGrid.getTileCoordChildTileRange(
+            tile.tileCoord, tmpTileRange, tmpExtent);
+        if (!goog.isNull(childTileRange)) {
+          findLoadedTiles(z + 1, childTileRange);
+        }
+      }
 
-/**
- * Set the log level specifying which message levels will be logged by this
- * logger. Message levels lower than this value will be discarded.
- * The level value Level.OFF can be used to turn off logging. If the new level
- * is null, it means that this node should inherit its level from its nearest
- * ancestor with a specific (non-null) level value.
- *
- * @param {goog.debug.Logger.Level} level The new level.
- */
-goog.debug.Logger.prototype.setLevel = function(level) {
-  if (goog.debug.LOGGING_ENABLED) {
-    if (goog.debug.Logger.ENABLE_HIERARCHY) {
-      this.level_ = level;
-    } else {
-      goog.asserts.assert(!this.name_,
-          'Cannot call setLevel() on a non-root logger when ' +
-          'goog.debug.Logger.ENABLE_HIERARCHY is false.');
-      goog.debug.Logger.rootLevel_ = level;
     }
+
   }
-};
 
+  // If the tile source revision changes, we destroy the existing DOM structure
+  // so that a new one will be created.  It would be more efficient to modify
+  // the existing structure.
+  var tileLayerZ, tileLayerZKey;
+  if (this.renderedRevision_ != tileSource.getRevision()) {
+    for (tileLayerZKey in this.tileLayerZs_) {
+      tileLayerZ = this.tileLayerZs_[+tileLayerZKey];
+      goog.dom.removeNode(tileLayerZ.target);
+    }
+    this.tileLayerZs_ = {};
+    this.renderedRevision_ = tileSource.getRevision();
+  }
 
-/**
- * Gets the log level specifying which message levels will be logged by this
- * logger. Message levels lower than this value will be discarded.
- * The level value Level.OFF can be used to turn off logging. If the level
- * is null, it means that this node should inherit its level from its nearest
- * ancestor with a specific (non-null) level value.
- *
- * @return {goog.debug.Logger.Level} The level.
- */
-goog.debug.Logger.prototype.getLevel = function() {
-  return goog.debug.LOGGING_ENABLED ?
-      this.level_ : goog.debug.Logger.Level.OFF;
-};
+  /** @type {Array.<number>} */
+  var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
+  goog.array.sort(zs);
 
+  /** @type {Object.<number, boolean>} */
+  var newTileLayerZKeys = {};
 
-/**
- * Returns the effective level of the logger based on its ancestors' levels.
- * @return {goog.debug.Logger.Level} The level.
- */
-goog.debug.Logger.prototype.getEffectiveLevel = function() {
-  if (!goog.debug.LOGGING_ENABLED) {
-    return goog.debug.Logger.Level.OFF;
+  var iz, iziz, tileCoordKey, tileCoordOrigin, tilesToDraw;
+  for (iz = 0, iziz = zs.length; iz < iziz; ++iz) {
+    tileLayerZKey = zs[iz];
+    if (tileLayerZKey in this.tileLayerZs_) {
+      tileLayerZ = this.tileLayerZs_[tileLayerZKey];
+    } else {
+      tileCoordOrigin =
+          tileGrid.getTileCoordForCoordAndZ(center, tileLayerZKey);
+      tileLayerZ = new ol.renderer.dom.TileLayerZ_(tileGrid, tileCoordOrigin);
+      newTileLayerZKeys[tileLayerZKey] = true;
+      this.tileLayerZs_[tileLayerZKey] = tileLayerZ;
+    }
+    tilesToDraw = tilesToDrawByZ[tileLayerZKey];
+    for (tileCoordKey in tilesToDraw) {
+      tileLayerZ.addTile(tilesToDraw[tileCoordKey], tileGutter);
+    }
+    tileLayerZ.finalizeAddTiles();
   }
 
-  if (!goog.debug.Logger.ENABLE_HIERARCHY) {
-    return goog.debug.Logger.rootLevel_;
+  /** @type {Array.<number>} */
+  var tileLayerZKeys =
+      goog.array.map(goog.object.getKeys(this.tileLayerZs_), Number);
+  goog.array.sort(tileLayerZKeys);
+
+  var i, ii, j, origin, resolution;
+  var transform = goog.vec.Mat4.createNumber();
+  for (i = 0, ii = tileLayerZKeys.length; i < ii; ++i) {
+    tileLayerZKey = tileLayerZKeys[i];
+    tileLayerZ = this.tileLayerZs_[tileLayerZKey];
+    if (!(tileLayerZKey in tilesToDrawByZ)) {
+      goog.dom.removeNode(tileLayerZ.target);
+      delete this.tileLayerZs_[tileLayerZKey];
+      continue;
+    }
+    resolution = tileLayerZ.getResolution();
+    origin = tileLayerZ.getOrigin();
+    ol.vec.Mat4.makeTransform2D(transform,
+        frameState.size[0] / 2, frameState.size[1] / 2,
+        resolution / viewState.resolution,
+        resolution / viewState.resolution,
+        viewState.rotation,
+        (origin[0] - center[0]) / resolution,
+        (center[1] - origin[1]) / resolution);
+    tileLayerZ.setTransform(transform);
+    if (tileLayerZKey in newTileLayerZKeys) {
+      for (j = tileLayerZKey - 1; j >= 0; --j) {
+        if (j in this.tileLayerZs_) {
+          goog.dom.insertSiblingAfter(
+              tileLayerZ.target, this.tileLayerZs_[j].target);
+          break;
+        }
+      }
+      if (j < 0) {
+        goog.dom.insertChildAt(this.target, tileLayerZ.target, 0);
+      }
+    } else {
+      if (!frameState.viewHints[ol.ViewHint.ANIMATING] &&
+          !frameState.viewHints[ol.ViewHint.INTERACTING]) {
+        tileLayerZ.removeTilesOutsideExtent(extent, tmpTileRange);
+      }
+    }
   }
-  if (this.level_) {
-    return this.level_;
+
+  if (layerState.opacity != this.renderedOpacity_) {
+    ol.dom.setOpacity(this.target, layerState.opacity);
+    this.renderedOpacity_ = layerState.opacity;
   }
-  if (this.parent_) {
-    return this.parent_.getEffectiveLevel();
+
+  if (layerState.visible && !this.renderedVisible_) {
+    goog.style.setElementShown(this.target, true);
+    this.renderedVisible_ = true;
   }
-  goog.asserts.fail('Root logger has no level set.');
-  return null;
-};
 
+  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);
 
-/**
- * Checks if a message of the given level would actually be logged by this
- * logger. This check is based on the Loggers effective level, which may be
- * inherited from its parent.
- * @param {goog.debug.Logger.Level} level The level to check.
- * @return {boolean} Whether the message would be logged.
- */
-goog.debug.Logger.prototype.isLoggable = function(level) {
-  return goog.debug.LOGGING_ENABLED &&
-      level.value >= this.getEffectiveLevel().value;
+  return true;
 };
 
 
+
 /**
- * Logs a message. If the logger is currently enabled for the
- * given message level then the given message is forwarded to all the
- * registered output Handler objects.
- * @param {goog.debug.Logger.Level} level One of the level identifiers.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error|Object=} opt_exception An exception associated with the
- *     message.
+ * @constructor
+ * @private
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @param {ol.TileCoord} tileCoordOrigin Tile coord origin.
  */
-goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
-  // java caches the effective level, not sure it's necessary here
-  if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
-    // Message callbacks can be useful when a log message is expensive to build.
-    if (goog.isFunction(msg)) {
-      msg = msg();
-    }
+ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) {
 
-    this.doLogRecord_(this.getLogRecord(
-        level, msg, opt_exception, goog.debug.Logger.prototype.log));
+  /**
+   * @type {!Element}
+   */
+  this.target = goog.dom.createElement(goog.dom.TagName.DIV);
+  this.target.style.position = 'absolute';
+  this.target.style.width = '100%';
+  this.target.style.height = '100%';
+
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+    /**
+     * Needed due to issues with IE7-8 clipping of transformed elements
+     * Solution is to translate separately from the scaled/rotated elements
+     * @private
+     * @type {!Element}
+     */
+    this.translateTarget_ = goog.dom.createElement(goog.dom.TagName.DIV);
+    this.translateTarget_.style.position = 'absolute';
+    this.translateTarget_.style.width = '100%';
+    this.translateTarget_.style.height = '100%';
+
+    goog.dom.appendChild(this.target, this.translateTarget_);
   }
+
+  /**
+   * @private
+   * @type {ol.tilegrid.TileGrid}
+   */
+  this.tileGrid_ = tileGrid;
+
+  /**
+   * @private
+   * @type {ol.TileCoord}
+   */
+  this.tileCoordOrigin_ = tileCoordOrigin;
+
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.origin_ =
+      ol.extent.getTopLeft(tileGrid.getTileCoordExtent(tileCoordOrigin));
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.resolution_ = tileGrid.getResolution(tileCoordOrigin[0]);
+
+  /**
+   * @private
+   * @type {Object.<string, ol.Tile>}
+   */
+  this.tiles_ = {};
+
+  /**
+   * @private
+   * @type {DocumentFragment}
+   */
+  this.documentFragment_ = null;
+
+  /**
+   * @private
+   * @type {goog.vec.Mat4.Number}
+   */
+  this.transform_ = goog.vec.Mat4.createNumberIdentity();
+
 };
 
 
 /**
- * Creates a new log record and adds the exception (if present) to it.
- * @param {goog.debug.Logger.Level} level One of the level identifiers.
- * @param {string} msg The string message.
- * @param {Error|Object=} opt_exception An exception associated with the
- *     message.
- * @param {Function=} opt_fnStackContext A function to use as the base
- *     of the stack trace used in the log record.
- * @return {!goog.debug.LogRecord} A log record.
- * @suppress {es5Strict}
+ * @param {ol.Tile} tile Tile.
+ * @param {number} tileGutter Tile gutter.
  */
-goog.debug.Logger.prototype.getLogRecord = function(
-    level, msg, opt_exception, opt_fnStackContext) {
-  if (goog.debug.LogBuffer.isBufferingEnabled()) {
-    var logRecord =
-        goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
+ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile, tileGutter) {
+  var tileCoord = tile.tileCoord;
+  var tileCoordZ = tileCoord[0];
+  var tileCoordX = tileCoord[1];
+  var tileCoordY = tileCoord[2];
+  goog.asserts.assert(tileCoordZ == this.tileCoordOrigin_[0]);
+  var tileCoordKey = ol.tilecoord.toString(tileCoord);
+  if (tileCoordKey in this.tiles_) {
+    return;
+  }
+  var tileSize = this.tileGrid_.getTileSize(tileCoordZ);
+  var image = tile.getImage(this);
+  var imageStyle = image.style;
+  // Bootstrap sets the style max-width: 100% for all images, which
+  // prevents the tile from being displayed in FireFox.  Workaround
+  // by overriding the max-width style.
+  imageStyle.maxWidth = 'none';
+  var tileElement;
+  var tileElementStyle;
+  if (tileGutter > 0) {
+    tileElement = goog.dom.createElement(goog.dom.TagName.DIV);
+    tileElementStyle = tileElement.style;
+    tileElementStyle.overflow = 'hidden';
+    tileElementStyle.width = tileSize + 'px';
+    tileElementStyle.height = tileSize + 'px';
+    imageStyle.position = 'absolute';
+    imageStyle.left = -tileGutter + 'px';
+    imageStyle.top = -tileGutter + 'px';
+    imageStyle.width = (tileSize + 2 * tileGutter) + 'px';
+    imageStyle.height = (tileSize + 2 * tileGutter) + 'px';
+    goog.dom.appendChild(tileElement, image);
   } else {
-    logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
+    imageStyle.width = tileSize + 'px';
+    imageStyle.height = tileSize + 'px';
+    tileElement = image;
+    tileElementStyle = imageStyle;
   }
-  if (opt_exception) {
-    var context;
-    if (goog.STRICT_MODE_COMPATIBLE) {
-      context = opt_fnStackContext || goog.debug.Logger.prototype.getLogRecord;
-    } else {
-      context = opt_fnStackContext || arguments.callee.caller;
-    }
-
-    logRecord.setException(opt_exception);
-    logRecord.setExceptionText(
-        goog.debug.exposeException(opt_exception,
-            opt_fnStackContext || goog.debug.Logger.prototype.getLogRecord));
+  tileElementStyle.position = 'absolute';
+  tileElementStyle.left =
+      ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize) + 'px';
+  tileElementStyle.top =
+      ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize) + 'px';
+  if (goog.isNull(this.documentFragment_)) {
+    this.documentFragment_ = document.createDocumentFragment();
   }
-  return logRecord;
+  goog.dom.appendChild(this.documentFragment_, tileElement);
+  this.tiles_[tileCoordKey] = tile;
 };
 
 
 /**
- * Logs a message at the Logger.Level.SHOUT level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * FIXME empty description for jsdoc
  */
-goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
+ol.renderer.dom.TileLayerZ_.prototype.finalizeAddTiles = function() {
+  if (!goog.isNull(this.documentFragment_)) {
+    if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+      goog.dom.appendChild(this.translateTarget_, this.documentFragment_);
+    } else {
+      goog.dom.appendChild(this.target, this.documentFragment_);
+    }
+    this.documentFragment_ = null;
   }
 };
 
 
 /**
- * Logs a message at the Logger.Level.SEVERE level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * @return {ol.Coordinate} Origin.
  */
-goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
-  }
+ol.renderer.dom.TileLayerZ_.prototype.getOrigin = function() {
+  return this.origin_;
 };
 
 
 /**
- * Logs a message at the Logger.Level.WARNING level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * @return {number} Resolution.
  */
-goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
-  }
+ol.renderer.dom.TileLayerZ_.prototype.getResolution = function() {
+  return this.resolution_;
 };
 
 
 /**
- * Logs a message at the Logger.Level.INFO level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
  */
-goog.debug.Logger.prototype.info = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
+ol.renderer.dom.TileLayerZ_.prototype.removeTilesOutsideExtent =
+    function(extent, opt_tileRange) {
+  var tileRange = this.tileGrid_.getTileRangeForExtentAndZ(
+      extent, this.tileCoordOrigin_[0], opt_tileRange);
+  /** @type {Array.<ol.Tile>} */
+  var tilesToRemove = [];
+  var tile, tileCoordKey;
+  for (tileCoordKey in this.tiles_) {
+    tile = this.tiles_[tileCoordKey];
+    if (!tileRange.contains(tile.tileCoord)) {
+      tilesToRemove.push(tile);
+    }
+  }
+  var i, ii;
+  for (i = 0, ii = tilesToRemove.length; i < ii; ++i) {
+    tile = tilesToRemove[i];
+    tileCoordKey = ol.tilecoord.toString(tile.tileCoord);
+    goog.dom.removeNode(tile.getImage(this));
+    delete this.tiles_[tileCoordKey];
   }
 };
 
 
 /**
- * Logs a message at the Logger.Level.CONFIG level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * @param {goog.vec.Mat4.Number} transform Transform.
  */
-goog.debug.Logger.prototype.config = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
+ol.renderer.dom.TileLayerZ_.prototype.setTransform = function(transform) {
+  if (!ol.vec.Mat4.equals2D(transform, this.transform_)) {
+    if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+      ol.dom.transformElement2D(this.target, transform, 6,
+          this.translateTarget_);
+    } else {
+      ol.dom.transformElement2D(this.target, transform, 6);
+    }
+    goog.vec.Mat4.setFromArray(this.transform_, transform);
   }
 };
 
+goog.provide('ol.renderer.dom.VectorLayer');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.vec.Mat4');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.layer.Vector');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas.Immediate');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.dom.Layer');
+goog.require('ol.renderer.vector');
+goog.require('ol.vec.Mat4');
 
-/**
- * Logs a message at the Logger.Level.FINE level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
- */
-goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
-  }
-};
 
 
 /**
- * Logs a message at the Logger.Level.FINER level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * @constructor
+ * @extends {ol.renderer.dom.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
  */
-goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
-  }
-};
+ol.renderer.dom.VectorLayer = function(mapRenderer, vectorLayer) {
 
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = ol.dom.createCanvasContext2D();
 
-/**
- * Logs a message at the Logger.Level.FINEST level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
- */
-goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
-  if (goog.debug.LOGGING_ENABLED) {
-    this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
-  }
-};
+  var target = this.context_.canvas;
+  // Bootstrap sets the style max-width: 100% for all images, which breaks
+  // prevents the image from being displayed in FireFox.  Workaround by
+  // overriding the max-width style.
+  target.style.maxWidth = 'none';
+  target.style.position = 'absolute';
 
+  goog.base(this, mapRenderer, vectorLayer, target);
 
-/**
- * Logs a LogRecord. If the logger is currently enabled for the
- * given message level then the given message is forwarded to all the
- * registered output Handler objects.
- * @param {goog.debug.LogRecord} logRecord A log record to log.
- */
-goog.debug.Logger.prototype.logRecord = function(logRecord) {
-  if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
-    this.doLogRecord_(logRecord);
-  }
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.dirty_ = false;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = -1;
 
-/**
- * Logs a LogRecord.
- * @param {goog.debug.LogRecord} logRecord A log record to log.
- * @private
- */
-goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
-  goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
-  if (goog.debug.Logger.ENABLE_HIERARCHY) {
-    var target = this;
-    while (target) {
-      target.callPublish_(logRecord);
-      target = target.getParent();
-    }
-  } else {
-    for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
-      handler(logRecord);
-    }
-  }
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedResolution_ = NaN;
 
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.renderedExtent_ = ol.extent.createEmpty();
 
-/**
- * Calls the handlers for publish.
- * @param {goog.debug.LogRecord} logRecord The log record to publish.
- * @private
- */
-goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
-  if (this.handlers_) {
-    for (var i = 0, handler; handler = this.handlers_[i]; i++) {
-      handler(logRecord);
-    }
-  }
-};
+  /**
+   * @private
+   * @type {function(ol.Feature, ol.Feature): number|null}
+   */
+  this.renderedRenderOrder_ = null;
 
+  /**
+   * @private
+   * @type {ol.render.canvas.ReplayGroup}
+   */
+  this.replayGroup_ = null;
+
+  /**
+   * @private
+   * @type {goog.vec.Mat4.Number}
+   */
+  this.transform_ = goog.vec.Mat4.createNumber();
 
-/**
- * Sets the parent of this logger. This is used for setting up the logger tree.
- * @param {goog.debug.Logger} parent The parent logger.
- * @private
- */
-goog.debug.Logger.prototype.setParent_ = function(parent) {
-  this.parent_ = parent;
 };
+goog.inherits(ol.renderer.dom.VectorLayer, ol.renderer.dom.Layer);
 
 
 /**
- * Adds a child to this logger. This is used for setting up the logger tree.
- * @param {string} name The leaf name of the child.
- * @param {goog.debug.Logger} logger The child logger.
- * @private
+ * @inheritDoc
  */
-goog.debug.Logger.prototype.addChild_ = function(name, logger) {
-  this.getChildren()[name] = logger;
-};
+ol.renderer.dom.VectorLayer.prototype.composeFrame =
+    function(frameState, layerState) {
 
+  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+  goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
 
-/**
- * There is a single global LogManager object that is used to maintain a set of
- * shared state about Loggers and log services. This is loosely based on the
- * java class java.util.logging.LogManager.
- */
-goog.debug.LogManager = {};
+  var viewState = frameState.viewState;
+  var viewRotation = viewState.rotation;
+  var pixelRatio = frameState.pixelRatio;
+
+  var transform = ol.vec.Mat4.makeTransform2D(this.transform_,
+      pixelRatio * frameState.size[0] / 2,
+      pixelRatio * frameState.size[1] / 2,
+      pixelRatio / viewState.resolution,
+      -pixelRatio / viewState.resolution,
+      -viewState.rotation,
+      -viewState.center[0], -viewState.center[1]);
 
+  var context = this.context_;
 
-/**
- * Map of logger names to logger objects.
- *
- * @type {!Object.<string, !goog.debug.Logger>}
- * @private
- */
-goog.debug.LogManager.loggers_ = {};
+  // Clear the canvas and set the correct size
+  context.canvas.width = frameState.size[0];
+  context.canvas.height = frameState.size[1];
 
+  this.dispatchEvent_(ol.render.EventType.PRECOMPOSE, frameState, transform);
 
-/**
- * The root logger which is the root of the logger tree.
- * @type {goog.debug.Logger}
- * @private
- */
-goog.debug.LogManager.rootLogger_ = null;
+  var replayGroup = this.replayGroup_;
 
+  if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) {
 
-/**
- * Initializes the LogManager if not already initialized.
- */
-goog.debug.LogManager.initialize = function() {
-  if (!goog.debug.LogManager.rootLogger_) {
-    goog.debug.LogManager.rootLogger_ = new goog.debug.Logger(
-        goog.debug.Logger.ROOT_LOGGER_NAME);
-    goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] =
-        goog.debug.LogManager.rootLogger_;
-    goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
+    context.globalAlpha = layerState.opacity;
+    replayGroup.replay(context, pixelRatio, transform, viewRotation,
+        frameState.skippedFeatureUids);
+
+    this.dispatchEvent_(ol.render.EventType.RENDER, frameState, transform);
   }
+
+  this.dispatchEvent_(ol.render.EventType.POSTCOMPOSE, frameState, transform);
 };
 
 
 /**
- * Returns all the loggers.
- * @return {!Object.<string, !goog.debug.Logger>} Map of logger names to logger
- *     objects.
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {goog.vec.Mat4.Number} transform Transform.
+ * @private
  */
-goog.debug.LogManager.getLoggers = function() {
-  return goog.debug.LogManager.loggers_;
+ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ =
+    function(type, frameState, transform) {
+  var context = this.context_;
+  var layer = this.getLayer();
+  if (layer.hasListener(type)) {
+    var render = new ol.render.canvas.Immediate(
+        context, frameState.pixelRatio, frameState.extent, transform,
+        frameState.viewState.rotation);
+    var event = new ol.render.Event(type, layer, render, null,
+        frameState, context, null);
+    layer.dispatchEvent(event);
+    render.flush();
+  }
 };
 
 
 /**
- * Returns the root of the logger tree namespace, the logger with the empty
- * string as its name.
- *
- * @return {!goog.debug.Logger} The root logger.
+ * @inheritDoc
  */
-goog.debug.LogManager.getRoot = function() {
-  goog.debug.LogManager.initialize();
-  return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
+ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel =
+    function(coordinate, frameState, callback, thisArg) {
+  if (goog.isNull(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_.forEachGeometryAtPixel(resolution,
+        rotation, coordinate, frameState.skippedFeatureUids,
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          goog.asserts.assert(goog.isDef(feature));
+          var key = goog.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback.call(thisArg, feature, layer);
+          }
+        });
+  }
 };
 
 
 /**
- * Finds a named logger.
- *
- * @param {string} name A name for the logger. This should be a dot-separated
- * name and should normally be based on the package name or class name of the
- * subsystem, such as goog.net.BrowserChannel.
- * @return {!goog.debug.Logger} The named logger.
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
  */
-goog.debug.LogManager.getLogger = function(name) {
-  goog.debug.LogManager.initialize();
-  var ret = goog.debug.LogManager.loggers_[name];
-  return ret || goog.debug.LogManager.createLogger_(name);
+ol.renderer.dom.VectorLayer.prototype.handleImageChange_ =
+    function(event) {
+  this.renderIfReadyAndVisible();
 };
 
 
 /**
- * Creates a function that can be passed to goog.debug.catchErrors. The function
- * will log all reported errors using the given logger.
- * @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
- *     Defaults to the root logger.
- * @return {function(Object)} The created function.
+ * @inheritDoc
  */
-goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
-  return function(info) {
-    var logger = opt_logger || goog.debug.LogManager.getRoot();
-    logger.severe('Error: ' + info.message + ' (' + info.fileName +
-                  ' @ Line: ' + info.line + ')');
-  };
-};
+ol.renderer.dom.VectorLayer.prototype.prepareFrame =
+    function(frameState, layerState) {
 
+  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+  goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
+  var vectorSource = vectorLayer.getSource();
 
-/**
- * Creates the named logger. Will also create the parents of the named logger
- * if they don't yet exist.
- * @param {string} name The name of the logger.
- * @return {!goog.debug.Logger} The named logger.
- * @private
- */
-goog.debug.LogManager.createLogger_ = function(name) {
-  // find parent logger
-  var logger = new goog.debug.Logger(name);
-  if (goog.debug.Logger.ENABLE_HIERARCHY) {
-    var lastDotIndex = name.lastIndexOf('.');
-    var parentName = name.substr(0, lastDotIndex);
-    var leafName = name.substr(lastDotIndex + 1);
-    var parentLogger = goog.debug.LogManager.getLogger(parentName);
+  this.updateAttributions(
+      frameState.attributions, vectorSource.getAttributions());
+  this.updateLogos(frameState, vectorSource);
 
-    // tell the parent about the child and the child about the parent
-    parentLogger.addChild_(leafName, logger);
-    logger.setParent_(parentLogger);
+  if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
+      frameState.viewHints[ol.ViewHint.INTERACTING])) {
+    return true;
   }
 
-  goog.debug.LogManager.loggers_[name] = logger;
-  return logger;
-};
-
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+  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();
 
-/**
- * @fileoverview Basic strippable logging definitions.
- * @see http://go/closurelogging
- *
- * @author johnlenz@google.com (John Lenz)
- */
+  if (!goog.isDef(vectorLayerRenderOrder)) {
+    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
+  }
 
-goog.provide('goog.log');
-goog.provide('goog.log.Level');
-goog.provide('goog.log.LogRecord');
-goog.provide('goog.log.Logger');
+  var extent = ol.extent.buffer(frameStateExtent,
+      vectorLayerRenderBuffer * resolution);
 
-goog.require('goog.debug');
-goog.require('goog.debug.LogManager');
-goog.require('goog.debug.LogRecord');
-goog.require('goog.debug.Logger');
+  if (!this.dirty_ &&
+      this.renderedResolution_ == resolution &&
+      this.renderedRevision_ == vectorLayerRevision &&
+      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
+      ol.extent.containsExtent(this.renderedExtent_, extent)) {
+    return true;
+  }
 
+  // FIXME dispose of old replayGroup in post render
+  goog.dispose(this.replayGroup_);
+  this.replayGroup_ = null;
 
-/** @define {boolean} Whether logging is enabled. */
-goog.define('goog.log.ENABLED', goog.debug.LOGGING_ENABLED);
+  this.dirty_ = false;
 
+  var replayGroup =
+      new ol.render.canvas.ReplayGroup(
+          ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
+          resolution);
+  vectorSource.loadFeatures(extent, resolution, projection);
+  var renderFeature =
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @this {ol.renderer.dom.VectorLayer}
+       */
+      function(feature) {
+    var styles;
+    if (goog.isDef(feature.getStyleFunction())) {
+      styles = feature.getStyleFunction().call(feature, resolution);
+    } else if (goog.isDef(vectorLayer.getStyleFunction())) {
+      styles = vectorLayer.getStyleFunction()(feature, resolution);
+    }
+    if (goog.isDefAndNotNull(styles)) {
+      var dirty = this.renderFeature(
+          feature, resolution, pixelRatio, styles, replayGroup);
+      this.dirty_ = this.dirty_ || dirty;
+    }
+  };
+  if (!goog.isNull(vectorLayerRenderOrder)) {
+    /** @type {Array.<ol.Feature>} */
+    var features = [];
+    vectorSource.forEachFeatureInExtentAtResolution(extent, resolution,
+        /**
+         * @param {ol.Feature} feature Feature.
+         */
+        function(feature) {
+          features.push(feature);
+        }, this);
+    goog.array.sort(features, vectorLayerRenderOrder);
+    goog.array.forEach(features, renderFeature, this);
+  } else {
+    vectorSource.forEachFeatureInExtentAtResolution(
+        extent, resolution, renderFeature, this);
+  }
+  replayGroup.finish();
 
-/** @const */
-goog.log.ROOT_LOGGER_NAME = goog.debug.Logger.ROOT_LOGGER_NAME;
+  this.renderedResolution_ = resolution;
+  this.renderedRevision_ = vectorLayerRevision;
+  this.renderedRenderOrder_ = vectorLayerRenderOrder;
+  this.renderedExtent_ = extent;
+  this.replayGroup_ = replayGroup;
 
+  return true;
+};
 
 
 /**
- * @constructor
- * @final
+ * @param {ol.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {Array.<ol.style.Style>} styles Array of styles
+ * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
+ * @return {boolean} `true` if an image is loading.
  */
-goog.log.Logger = goog.debug.Logger;
+ol.renderer.dom.VectorLayer.prototype.renderFeature =
+    function(feature, resolution, pixelRatio, styles, replayGroup) {
+  if (!goog.isDefAndNotNull(styles)) {
+    return false;
+  }
+  var i, ii, loading = false;
+  for (i = 0, ii = styles.length; i < ii; ++i) {
+    loading = ol.renderer.vector.renderFeature(
+        replayGroup, feature, styles[i],
+        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+        this.handleImageChange_, this) || loading;
+  }
+  return loading;
+};
+
+goog.provide('ol.renderer.dom.Map');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+goog.require('goog.functions');
+goog.require('goog.style');
+goog.require('goog.vec.Mat4');
+goog.require('ol');
+goog.require('ol.RendererType');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.layer.Image');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas.Immediate');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.dom.ImageLayer');
+goog.require('ol.renderer.dom.Layer');
+goog.require('ol.renderer.dom.TileLayer');
+goog.require('ol.renderer.dom.VectorLayer');
+goog.require('ol.renderer.vector');
+goog.require('ol.source.State');
+goog.require('ol.vec.Mat4');
 
 
 
 /**
  * @constructor
- * @final
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
  */
-goog.log.Level = goog.debug.Logger.Level;
+ol.renderer.dom.Map = function(container, map) {
 
+  goog.base(this, container, map);
 
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = null;
+  if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE)) {
+    this.context_ = ol.dom.createCanvasContext2D();
+    var canvas = this.context_.canvas;
+    canvas.style.position = 'absolute';
+    canvas.style.width = '100%';
+    canvas.style.height = '100%';
+    canvas.className = ol.css.CLASS_UNSELECTABLE;
+    goog.dom.insertChildAt(container, canvas, 0);
+  }
 
-/**
- * @constructor
- * @final
- */
-goog.log.LogRecord = goog.debug.LogRecord;
+  /**
+   * @private
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.transform_ = goog.vec.Mat4.createNumber();
 
+  /**
+   * @type {!Element}
+   * @private
+   */
+  this.layersPane_ = goog.dom.createElement(goog.dom.TagName.DIV);
+  this.layersPane_.className = ol.css.CLASS_UNSELECTABLE;
+  var style = this.layersPane_.style;
+  style.position = 'absolute';
+  style.width = '100%';
+  style.height = '100%';
 
-/**
- * Finds or creates a logger for a named subsystem. If a logger has already been
- * created with the given name it is returned. Otherwise a new logger is
- * created. If a new logger is created its log level will be configured based
- * on the goog.debug.LogManager configuration and it will configured to also
- * send logging output to its parent's handlers.
- * @see goog.debug.LogManager
- *
- * @param {string} name A name for the logger. This should be a dot-separated
- *     name and should normally be based on the package name or class name of
- *     the subsystem, such as goog.net.BrowserChannel.
- * @param {goog.log.Level=} opt_level If provided, override the
- *     default logging level with the provided level.
- * @return {goog.log.Logger} The named logger or null if logging is disabled.
- */
-goog.log.getLogger = function(name, opt_level) {
-  if (goog.log.ENABLED) {
-    var logger = goog.debug.LogManager.getLogger(name);
-    if (opt_level && logger) {
-      logger.setLevel(opt_level);
-    }
-    return logger;
-  } else {
-    return null;
+  // in IE < 9, we need to return false from ondragstart to cancel the default
+  // behavior of dragging images, which is interfering with the custom handler
+  // in the Drag interaction subclasses
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+    this.layersPane_.ondragstart = goog.functions.FALSE;
+    this.layersPane_.onselectstart = goog.functions.FALSE;
   }
+
+  // prevent the img context menu on mobile devices
+  goog.events.listen(this.layersPane_, goog.events.EventType.TOUCHSTART,
+      goog.events.Event.preventDefault);
+
+  goog.dom.insertChildAt(container, this.layersPane_, 0);
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.renderedVisible_ = true;
+
 };
+goog.inherits(ol.renderer.dom.Map, ol.renderer.Map);
 
 
-// TODO(johnlenz): try to tighten the types to these functions.
 /**
- * Adds a handler to the logger. This doesn't use the event system because
- * we want to be able to add logging to the event system.
- * @param {goog.log.Logger} logger
- * @param {Function} handler Handler function to add.
+ * @inheritDoc
  */
-goog.log.addHandler = function(logger, handler) {
-  if (goog.log.ENABLED && logger) {
-    logger.addHandler(handler);
-  }
+ol.renderer.dom.Map.prototype.disposeInternal = function() {
+  goog.dom.removeNode(this.layersPane_);
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * Removes a handler from the logger. This doesn't use the event system because
- * we want to be able to add logging to the event system.
- * @param {goog.log.Logger} logger
- * @param {Function} handler Handler function to remove.
- * @return {boolean} Whether the handler was removed.
+ * @inheritDoc
  */
-goog.log.removeHandler = function(logger, handler) {
-  if (goog.log.ENABLED && logger) {
-    return logger.removeHandler(handler);
+ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) {
+  var layerRenderer;
+  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
+    layerRenderer = new ol.renderer.dom.ImageLayer(this, layer);
+  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
+    layerRenderer = new ol.renderer.dom.TileLayer(this, layer);
+  } else if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) &&
+      ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
+    layerRenderer = new ol.renderer.dom.VectorLayer(this, layer);
   } else {
-    return false;
+    goog.asserts.fail();
+    return null;
   }
+  return layerRenderer;
 };
 
 
 /**
- * Logs a message. If the logger is currently enabled for the
- * given message level then the given message is forwarded to all the
- * registered output Handler objects.
- * @param {goog.log.Logger} logger
- * @param {goog.log.Level} level One of the level identifiers.
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error|Object=} opt_exception An exception associated with the
- *     message.
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-goog.log.log = function(logger, level, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.log(level, msg, opt_exception);
+ol.renderer.dom.Map.prototype.dispatchComposeEvent_ =
+    function(type, frameState) {
+  var map = this.getMap();
+  if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && map.hasListener(type)) {
+    var extent = frameState.extent;
+    var pixelRatio = frameState.pixelRatio;
+    var viewState = frameState.viewState;
+    var resolution = viewState.resolution;
+    var rotation = viewState.rotation;
+    var context = this.context_;
+    var canvas = context.canvas;
+
+    ol.vec.Mat4.makeTransform2D(this.transform_,
+        canvas.width / 2,
+        canvas.height / 2,
+        pixelRatio / viewState.resolution,
+        -pixelRatio / viewState.resolution,
+        -viewState.rotation,
+        -viewState.center[0], -viewState.center[1]);
+    var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
+        extent, this.transform_, rotation);
+    var replayGroup = new ol.render.canvas.ReplayGroup(
+        ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
+        resolution);
+    var composeEvent = new ol.render.Event(type, map, vectorContext,
+        replayGroup, frameState, context, null);
+    map.dispatchEvent(composeEvent);
+    replayGroup.finish();
+    if (!replayGroup.isEmpty()) {
+      replayGroup.replay(context, pixelRatio, this.transform_, rotation, {});
+    }
+    vectorContext.flush();
+    this.replayGroup = replayGroup;
   }
 };
 
 
 /**
- * Logs a message at the Level.SEVERE level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.log.Logger} logger
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * @inheritDoc
  */
-goog.log.error = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.severe(msg, opt_exception);
-  }
+ol.renderer.dom.Map.prototype.getType = function() {
+  return ol.RendererType.DOM;
 };
 
 
 /**
- * Logs a message at the Level.WARNING level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.log.Logger} logger
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
+ * @inheritDoc
  */
-goog.log.warning = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.warning(msg, opt_exception);
+ol.renderer.dom.Map.prototype.renderFrame = function(frameState) {
+
+  if (goog.isNull(frameState)) {
+    if (this.renderedVisible_) {
+      goog.style.setElementShown(this.layersPane_, false);
+      this.renderedVisible_ = false;
+    }
+    return;
   }
-};
 
+  /**
+   * @this {ol.renderer.dom.Map}
+   * @param {Element} elem
+   * @param {number} i
+   */
+  var addChild;
 
-/**
- * Logs a message at the Level.INFO level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.log.Logger} logger
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
- */
-goog.log.info = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.info(msg, opt_exception);
+  // appendChild is actually more performant than insertBefore
+  // in IE 7 and 8. http://jsperf.com/reattaching-dom-nodes
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+    addChild =
+        /**
+         * @this {ol.renderer.dom.Map}
+         * @param {Element} elem
+         */ (
+        function(elem) {
+          goog.dom.appendChild(this.layersPane_, elem);
+        });
+  } else {
+    addChild =
+        /**
+         * @this {ol.renderer.dom.Map}
+         * @param {Element} elem
+         * @param {number} i
+         */ (
+        function(elem, i) {
+          goog.dom.insertChildAt(this.layersPane_, elem, i);
+        });
   }
-};
 
+  var map = this.getMap();
+  if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) &&
+      (map.hasListener(ol.render.EventType.PRECOMPOSE) ||
+      map.hasListener(ol.render.EventType.POSTCOMPOSE))) {
+    var canvas = this.context_.canvas;
+    canvas.width = frameState.size[0];
+    canvas.height = frameState.size[1];
+  }
 
-/**
- * Logs a message at the Level.Fine level.
- * If the logger is currently enabled for the given message level then the
- * given message is forwarded to all the registered output Handler objects.
- * @param {goog.log.Logger} logger
- * @param {goog.debug.Loggable} msg The message to log.
- * @param {Error=} opt_exception An exception associated with the message.
- */
-goog.log.fine = function(logger, msg, opt_exception) {
-  if (goog.log.ENABLED && logger) {
-    logger.fine(msg, opt_exception);
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+
+  var layerStatesArray = frameState.layerStatesArray;
+  var i, ii, layer, layerRenderer, layerState;
+  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
+    layerState = layerStatesArray[i];
+    layer = layerState.layer;
+    layerRenderer = /** @type {ol.renderer.dom.Layer} */ (
+        this.getLayerRenderer(layer));
+    goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer);
+    addChild.call(this, layerRenderer.getTarget(), i);
+    if (layerState.sourceState == ol.source.State.READY) {
+      if (layerRenderer.prepareFrame(frameState, layerState)) {
+        layerRenderer.composeFrame(frameState, layerState);
+      }
+    } else {
+      layerRenderer.clearFrame();
+    }
+  }
+
+  var layerStates = frameState.layerStates;
+  var layerKey;
+  for (layerKey in this.getLayerRenderers()) {
+    if (!(layerKey in layerStates)) {
+      layerRenderer = this.getLayerRendererByKey(layerKey);
+      goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer);
+      goog.dom.removeNode(layerRenderer.getTarget());
+    }
   }
+
+  if (!this.renderedVisible_) {
+    goog.style.setElementShown(this.layersPane_, true);
+    this.renderedVisible_ = true;
+  }
+
+  this.calculateMatrices2D(frameState);
+  this.scheduleRemoveUnusedLayerRenderers(frameState);
+  this.scheduleExpireIconCache(frameState);
+
+  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
 };
 
-// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -65115,24589 +67401,35775 @@ goog.log.fine = function(logger, msg, opt_exception) {
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+
 /**
- * @fileoverview Provides a files drag and drop event detector. It works on
- * HTML5 browsers.
+ * @fileoverview Constants used by the WebGL rendering, including all of the
+ * constants used from the WebGL context.  For example, instead of using
+ * context.ARRAY_BUFFER, your code can use
+ * goog.webgl.ARRAY_BUFFER. The benefits for doing this include allowing
+ * the compiler to optimize your code so that the compiled code does not have to
+ * contain large strings to reference these properties, and reducing runtime
+ * property access.
  *
- * @see ../demos/filedrophandler.html
+ * Values are taken from the WebGL Spec:
+ * https://www.khronos.org/registry/webgl/specs/1.0/#WEBGLRENDERINGCONTEXT
  */
 
-goog.provide('goog.events.FileDropHandler');
-goog.provide('goog.events.FileDropHandler.EventType');
-
-goog.require('goog.array');
-goog.require('goog.dom');
-goog.require('goog.events');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.EventHandler');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
-goog.require('goog.log');
-
+goog.provide('goog.webgl');
 
 
 /**
- * A files drag and drop event detector. Gets an {@code element} as parameter
- * and fires {@code goog.events.FileDropHandler.EventType.DROP} event when files
- * are dropped in the {@code element}.
- *
- * @param {Element|Document} element The element or document to listen on.
- * @param {boolean=} opt_preventDropOutside Whether to prevent a drop on the
- *     area outside the {@code element}. Default false.
- * @constructor
- * @extends {goog.events.EventTarget}
- * @final
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler = function(element, opt_preventDropOutside) {
-  goog.events.EventTarget.call(this);
-
-  /**
-   * Handler for drag/drop events.
-   * @type {!goog.events.EventHandler.<!goog.events.FileDropHandler>}
-   * @private
-   */
-  this.eventHandler_ = new goog.events.EventHandler(this);
+goog.webgl.DEPTH_BUFFER_BIT = 0x00000100;
 
-  var doc = element;
-  if (opt_preventDropOutside) {
-    doc = goog.dom.getOwnerDocument(element);
-  }
 
-  // Add dragenter listener to the owner document of the element.
-  this.eventHandler_.listen(doc,
-                            goog.events.EventType.DRAGENTER,
-                            this.onDocDragEnter_);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BUFFER_BIT = 0x00000400;
 
-  // Add dragover listener to the owner document of the element only if the
-  // document is not the element itself.
-  if (doc != element) {
-    this.eventHandler_.listen(doc,
-                              goog.events.EventType.DRAGOVER,
-                              this.onDocDragOver_);
-  }
 
-  // Add dragover and drop listeners to the element.
-  this.eventHandler_.listen(element,
-                            goog.events.EventType.DRAGOVER,
-                            this.onElemDragOver_);
-  this.eventHandler_.listen(element,
-                            goog.events.EventType.DROP,
-                            this.onElemDrop_);
-};
-goog.inherits(goog.events.FileDropHandler, goog.events.EventTarget);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.COLOR_BUFFER_BIT = 0x00004000;
 
 
 /**
- * Whether the drag event contains files. It is initialized only in the
- * dragenter event. It is used in all the drag events to prevent default actions
- * only if the drag contains files. Preventing default actions is necessary to
- * go from dragenter to dragover and from dragover to drop. However we do not
- * always want to prevent default actions, e.g. when the user drags text or
- * links on a text area we should not prevent the browser default action that
- * inserts the text in the text area. It is also necessary to stop propagation
- * when handling drag events on the element to prevent them from propagating
- * to the document.
- * @private
- * @type {boolean}
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.prototype.dndContainsFiles_ = false;
+goog.webgl.POINTS = 0x0000;
 
 
 /**
- * A logger, used to help us debug the algorithm.
- * @type {goog.log.Logger}
- * @private
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.prototype.logger_ =
-    goog.log.getLogger('goog.events.FileDropHandler');
+goog.webgl.LINES = 0x0001;
 
 
 /**
- * The types of events fired by this class.
- * @enum {string}
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.EventType = {
-  DROP: goog.events.EventType.DROP
-};
+goog.webgl.LINE_LOOP = 0x0002;
 
 
-/** @override */
-goog.events.FileDropHandler.prototype.disposeInternal = function() {
-  goog.events.FileDropHandler.superClass_.disposeInternal.call(this);
-  this.eventHandler_.dispose();
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINE_STRIP = 0x0003;
 
 
 /**
- * Dispatches the DROP event.
- * @param {goog.events.BrowserEvent} e The underlying browser event.
- * @private
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.prototype.dispatch_ = function(e) {
-  goog.log.fine(this.logger_, 'Firing DROP event...');
-  var event = new goog.events.BrowserEvent(e.getBrowserEvent());
-  event.type = goog.events.FileDropHandler.EventType.DROP;
-  this.dispatchEvent(event);
-};
+goog.webgl.TRIANGLES = 0x0004;
 
 
 /**
- * Handles dragenter on the document.
- * @param {goog.events.BrowserEvent} e The dragenter event.
- * @private
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.prototype.onDocDragEnter_ = function(e) {
-  goog.log.log(this.logger_, goog.log.Level.FINER,
-      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
-  var dt = e.getBrowserEvent().dataTransfer;
-  // Check whether the drag event contains files.
-  this.dndContainsFiles_ = !!(dt &&
-      ((dt.types &&
-          (goog.array.contains(dt.types, 'Files') ||
-          goog.array.contains(dt.types, 'public.file-url'))) ||
-      (dt.files && dt.files.length > 0)));
-  // If it does
-  if (this.dndContainsFiles_) {
-    // Prevent default actions.
-    e.preventDefault();
-  }
-  goog.log.log(this.logger_, goog.log.Level.FINER,
-      'dndContainsFiles_: ' + this.dndContainsFiles_);
-};
+goog.webgl.TRIANGLE_STRIP = 0x0005;
 
 
 /**
- * Handles dragging something over the document.
- * @param {goog.events.BrowserEvent} e The dragover event.
- * @private
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.prototype.onDocDragOver_ = function(e) {
-  goog.log.log(this.logger_, goog.log.Level.FINEST,
-      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
-  if (this.dndContainsFiles_) {
-    // Prevent default actions.
-    e.preventDefault();
-    // Disable the drop on the document outside the drop zone.
-    var dt = e.getBrowserEvent().dataTransfer;
-    dt.dropEffect = 'none';
-  }
-};
+goog.webgl.TRIANGLE_FAN = 0x0006;
 
 
 /**
- * Handles dragging something over the element (drop zone).
- * @param {goog.events.BrowserEvent} e The dragover event.
- * @private
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.prototype.onElemDragOver_ = function(e) {
-  goog.log.log(this.logger_, goog.log.Level.FINEST,
-      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
-  if (this.dndContainsFiles_) {
-    // Prevent default actions and stop the event from propagating further to
-    // the document. Both lines are needed! (See comment above).
-    e.preventDefault();
-    e.stopPropagation();
-    // Allow the drop on the drop zone.
-    var dt = e.getBrowserEvent().dataTransfer;
-    dt.effectAllowed = 'all';
-    dt.dropEffect = 'copy';
-  }
-};
+goog.webgl.ZERO = 0;
 
 
 /**
- * Handles dropping something onto the element (drop zone).
- * @param {goog.events.BrowserEvent} e The drop event.
- * @private
+ * @const
+ * @type {number}
  */
-goog.events.FileDropHandler.prototype.onElemDrop_ = function(e) {
-  goog.log.log(this.logger_, goog.log.Level.FINER,
-      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
-  // If the drag and drop event contains files.
-  if (this.dndContainsFiles_) {
-    // Prevent default actions and stop the event from propagating further to
-    // the document. Both lines are needed! (See comment above).
-    e.preventDefault();
-    e.stopPropagation();
-    // Dispatch DROP event.
-    this.dispatch_(e);
-  }
-};
-
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+goog.webgl.ONE = 1;
 
-goog.provide('goog.Thenable');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SRC_COLOR = 0x0300;
 
 
 /**
- * Provides a more strict interface for Thenables in terms of
- * http://promisesaplus.com for interop with {@see goog.Promise}.
- *
- * @interface
- * @extends {IThenable.<TYPE>}
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.Thenable = function() {};
+goog.webgl.ONE_MINUS_SRC_COLOR = 0x0301;
 
 
 /**
- * Adds callbacks that will operate on the result of the Thenable, returning a
- * new child Promise.
- *
- * If the Thenable is fulfilled, the {@code onFulfilled} callback will be
- * invoked with the fulfillment value as argument, and the child Promise will
- * be fulfilled with the return value of the callback. If the callback throws
- * an exception, the child Promise will be rejected with the thrown value
- * instead.
- *
- * If the Thenable is rejected, the {@code onRejected} callback will be invoked
- * with the rejection reason as argument, and the child Promise will be rejected
- * with the return value of the callback or thrown value.
- *
- * @param {?(function(this:THIS, TYPE):
- *             (RESULT|IThenable.<RESULT>|Thenable))=} opt_onFulfilled A
- *     function that will be invoked with the fulfillment value if the Promise
- *     is fullfilled.
- * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will
- *     be invoked with the rejection reason if the Promise is rejected.
- * @param {THIS=} opt_context An optional context object that will be the
- *     execution context for the callbacks. By default, functions are executed
- *     with the default this.
- * @return {!goog.Promise.<RESULT>} A new Promise that will receive the result
- *     of the fulfillment or rejection callback.
- * @template RESULT,THIS
+ * @const
+ * @type {number}
  */
-goog.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected,
-    opt_context) {};
+goog.webgl.SRC_ALPHA = 0x0302;
 
 
 /**
- * An expando property to indicate that an object implements
- * {@code goog.Thenable}.
- *
- * {@see addImplementation}.
- *
  * @const
+ * @type {number}
  */
-goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable';
+goog.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;
 
 
 /**
- * Marks a given class (constructor) as an implementation of Thenable, so
- * that we can query that fact at runtime. The class must have already
- * implemented the interface.
- * Exports a 'then' method on the constructor prototype, so that the objects
- * also implement the extern {@see goog.Thenable} interface for interop with
- * other Promise implementations.
- * @param {function(new:goog.Thenable,...[?])} ctor The class constructor. The
- *     corresponding class must have already implemented the interface.
+ * @const
+ * @type {number}
  */
-goog.Thenable.addImplementation = function(ctor) {
-  goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then);
-  if (COMPILED) {
-    ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true;
-  } else {
-    // Avoids dictionary access in uncompiled mode.
-    ctor.prototype.$goog_Thenable = true;
-  }
-};
+goog.webgl.DST_ALPHA = 0x0304;
 
 
 /**
- * @param {*} object
- * @return {boolean} Whether a given instance implements {@code goog.Thenable}.
- *     The class/superclass of the instance must call {@code addImplementation}.
+ * @const
+ * @type {number}
  */
-goog.Thenable.isImplementedBy = function(object) {
-  if (!object) {
-    return false;
-  }
-  try {
-    if (COMPILED) {
-      return !!object[goog.Thenable.IMPLEMENTED_BY_PROP];
-    }
-    return !!object.$goog_Thenable;
-  } catch (e) {
-    // Property access seems to be forbidden.
-    return false;
-  }
-};
+goog.webgl.ONE_MINUS_DST_ALPHA = 0x0305;
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Provides a function to schedule running a function as soon
- * as possible after the current JS execution stops and yields to the event
- * loop.
- *
+ * @const
+ * @type {number}
  */
-
-goog.provide('goog.async.nextTick');
-goog.provide('goog.async.throwException');
-
-goog.require('goog.debug.entryPointRegistry');
-goog.require('goog.functions');
+goog.webgl.DST_COLOR = 0x0306;
 
 
 /**
- * Throw an item without interrupting the current execution context.  For
- * example, if processing a group of items in a loop, sometimes it is useful
- * to report an error while still allowing the rest of the batch to be
- * processed.
- * @param {*} exception
+ * @const
+ * @type {number}
  */
-goog.async.throwException = function(exception) {
-  // Each throw needs to be in its own context.
-  goog.global.setTimeout(function() { throw exception; }, 0);
-};
+goog.webgl.ONE_MINUS_DST_COLOR = 0x0307;
 
 
 /**
- * Fires the provided callbacks as soon as possible after the current JS
- * execution context. setTimeout(…, 0) always takes at least 5ms for legacy
- * reasons.
- *
- * This will not schedule the callback as a microtask (i.e. a task that can
- * preempt user input or networking callbacks). It is meant to emulate what
- * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
- * behavior, use {@see goog.Promise} instead.
- *
- * @param {function(this:SCOPE)} callback Callback function to fire as soon as
- *     possible.
- * @param {SCOPE=} opt_context Object in whose scope to call the listener.
- * @template SCOPE
+ * @const
+ * @type {number}
  */
-goog.async.nextTick = function(callback, opt_context) {
-  var cb = callback;
-  if (opt_context) {
-    cb = goog.bind(callback, opt_context);
-  }
-  cb = goog.async.nextTick.wrapCallback_(cb);
-  // Introduced and currently only supported by IE10.
-  if (goog.isFunction(goog.global.setImmediate)) {
-    goog.global.setImmediate(cb);
-    return;
-  }
-  // Look for and cache the custom fallback version of setImmediate.
-  if (!goog.async.nextTick.setImmediate_) {
-    goog.async.nextTick.setImmediate_ =
-        goog.async.nextTick.getSetImmediateEmulator_();
-  }
-  goog.async.nextTick.setImmediate_(cb);
-};
+goog.webgl.SRC_ALPHA_SATURATE = 0x0308;
 
 
 /**
- * Cache for the setImmediate implementation.
- * @type {function(function())}
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.nextTick.setImmediate_;
+goog.webgl.FUNC_ADD = 0x8006;
 
 
 /**
- * Determines the best possible implementation to run a function as soon as
- * the JS event loop is idle.
- * @return {function(function())} The "setImmediate" implementation.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.nextTick.getSetImmediateEmulator_ = function() {
-  // Create a private message channel and use it to postMessage empty messages
-  // to ourselves.
-  var Channel = goog.global['MessageChannel'];
-  // If MessageChannel is not available and we are in a browser, implement
-  // an iframe based polyfill in browsers that have postMessage and
-  // document.addEventListener. The latter excludes IE8 because it has a
-  // synchronous postMessage implementation.
-  if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
-      window.postMessage && window.addEventListener) {
-    /** @constructor */
-    Channel = function() {
-      // Make an empty, invisible iframe.
-      var iframe = document.createElement('iframe');
-      iframe.style.display = 'none';
-      iframe.src = '';
-      document.documentElement.appendChild(iframe);
-      var win = iframe.contentWindow;
-      var doc = win.document;
-      doc.open();
-      doc.write('');
-      doc.close();
-      // Do not post anything sensitive over this channel, as the workaround for
-      // pages with file: origin could allow that information to be modified or
-      // intercepted.
-      var message = 'callImmediate' + Math.random();
-      // The same origin policy rejects attempts to postMessage from file: urls
-      // unless the origin is '*'.
-      // TODO(b/16335441): Use '*' origin for data: and other similar protocols.
-      var origin = win.location.protocol == 'file:' ?
-          '*' : win.location.protocol + '//' + win.location.host;
-      var onmessage = goog.bind(function(e) {
-        // Validate origin and message to make sure that this message was
-        // intended for us.
-        if (e.origin != origin && e.data != message) {
-          return;
-        }
-        this['port1'].onmessage();
-      }, this);
-      win.addEventListener('message', onmessage, false);
-      this['port1'] = {};
-      this['port2'] = {
-        postMessage: function() {
-          win.postMessage(message, origin);
-        }
-      };
-    };
-  }
-  if (typeof Channel !== 'undefined') {
-    var channel = new Channel();
-    // Use a fifo linked list to call callbacks in the right order.
-    var head = {};
-    var tail = head;
-    channel['port1'].onmessage = function() {
-      head = head.next;
-      var cb = head.cb;
-      head.cb = null;
-      cb();
-    };
-    return function(cb) {
-      tail.next = {
-        cb: cb
-      };
-      tail = tail.next;
-      channel['port2'].postMessage(0);
-    };
-  }
-  // Implementation for IE6-8: Script elements fire an asynchronous
-  // onreadystatechange event when inserted into the DOM.
-  if (typeof document !== 'undefined' && 'onreadystatechange' in
-      document.createElement('script')) {
-    return function(cb) {
-      var script = document.createElement('script');
-      script.onreadystatechange = function() {
-        // Clean up and call the callback.
-        script.onreadystatechange = null;
-        script.parentNode.removeChild(script);
-        script = null;
-        cb();
-        cb = null;
-      };
-      document.documentElement.appendChild(script);
-    };
-  }
-  // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
-  // or more.
-  return function(cb) {
-    goog.global.setTimeout(cb, 0);
-  };
-};
+goog.webgl.BLEND_EQUATION = 0x8009;
 
 
 /**
- * Helper function that is overrided to protect callbacks with entry point
- * monitor if the application monitors entry points.
- * @param {function()} callback Callback function to fire as soon as possible.
- * @return {function()} The wrapped callback.
- * @private
+ * Same as BLEND_EQUATION
+ * @const
+ * @type {number}
  */
-goog.async.nextTick.wrapCallback_ = goog.functions.identity;
-
-
-// Register the callback function as an entry point, so that it can be
-// monitored for exception handling, etc. This has to be done in this file
-// since it requires special code to handle all browsers.
-goog.debug.entryPointRegistry.register(
-    /**
-     * @param {function(!Function): !Function} transformer The transforming
-     *     function.
-     */
-    function(transformer) {
-      goog.async.nextTick.wrapCallback_ = transformer;
-    });
+goog.webgl.BLEND_EQUATION_RGB = 0x8009;
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Simple notifiers for the Closure testing framework.
- *
- * @author johnlenz@google.com (John Lenz)
+ * @const
+ * @type {number}
  */
-
-goog.provide('goog.testing.watchers');
-
-
-/** @private {!Array.<function()>} */
-goog.testing.watchers.resetWatchers_ = [];
+goog.webgl.BLEND_EQUATION_ALPHA = 0x883D;
 
 
 /**
- * Fires clock reset watching functions.
+ * @const
+ * @type {number}
  */
-goog.testing.watchers.signalClockReset = function() {
-  var watchers = goog.testing.watchers.resetWatchers_;
-  for (var i = 0; i < watchers.length; i++) {
-    goog.testing.watchers.resetWatchers_[i]();
-  }
-};
+goog.webgl.FUNC_SUBTRACT = 0x800A;
 
 
 /**
- * Enqueues a function to be called when the clock used for setTimeout is reset.
- * @param {function()} fn
+ * @const
+ * @type {number}
  */
-goog.testing.watchers.watchClockReset = function(fn) {
-  goog.testing.watchers.resetWatchers_.push(fn);
-};
+goog.webgl.FUNC_REVERSE_SUBTRACT = 0x800B;
 
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_DST_RGB = 0x80C8;
 
-goog.provide('goog.async.run');
 
-goog.require('goog.async.nextTick');
-goog.require('goog.async.throwException');
-goog.require('goog.testing.watchers');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_SRC_RGB = 0x80C9;
 
 
 /**
- * Fires the provided callback just before the current callstack unwinds, or as
- * soon as possible after the current JS execution context.
- * @param {function(this:THIS)} callback
- * @param {THIS=} opt_context Object to use as the "this value" when calling
- *     the provided function.
- * @template THIS
+ * @const
+ * @type {number}
  */
-goog.async.run = function(callback, opt_context) {
-  if (!goog.async.run.schedule_) {
-    goog.async.run.initializeRunner_();
-  }
-  if (!goog.async.run.workQueueScheduled_) {
-    // Nothing is currently scheduled, schedule it now.
-    goog.async.run.schedule_();
-    goog.async.run.workQueueScheduled_ = true;
-  }
-
-  goog.async.run.workQueue_.push(
-      new goog.async.run.WorkItem_(callback, opt_context));
-};
+goog.webgl.BLEND_DST_ALPHA = 0x80CA;
 
 
 /**
- * Initializes the function to use to process the work queue.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.run.initializeRunner_ = function() {
-  // If native Promises are available in the browser, just schedule the callback
-  // on a fulfilled promise, which is specified to be async, but as fast as
-  // possible.
-  if (goog.global.Promise && goog.global.Promise.resolve) {
-    var promise = goog.global.Promise.resolve();
-    goog.async.run.schedule_ = function() {
-      promise.then(goog.async.run.processWorkQueue);
-    };
-  } else {
-    goog.async.run.schedule_ = function() {
-      goog.async.nextTick(goog.async.run.processWorkQueue);
-    };
-  }
-};
+goog.webgl.BLEND_SRC_ALPHA = 0x80CB;
 
 
 /**
- * Forces goog.async.run to use nextTick instead of Promise.
- *
- * This should only be done in unit tests. It's useful because MockClock
- * replaces nextTick, but not the browser Promise implementation, so it allows
- * Promise-based code to be tested with MockClock.
+ * @const
+ * @type {number}
  */
-goog.async.run.forceNextTick = function() {
-  goog.async.run.schedule_ = function() {
-    goog.async.nextTick(goog.async.run.processWorkQueue);
-  };
-};
+goog.webgl.CONSTANT_COLOR = 0x8001;
 
 
 /**
- * The function used to schedule work asynchronousely.
- * @private {function()}
+ * @const
+ * @type {number}
  */
-goog.async.run.schedule_;
-
+goog.webgl.ONE_MINUS_CONSTANT_COLOR = 0x8002;
 
-/** @private {boolean} */
-goog.async.run.workQueueScheduled_ = false;
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CONSTANT_ALPHA = 0x8003;
 
-/** @private {!Array.<!goog.async.run.WorkItem_>} */
-goog.async.run.workQueue_ = [];
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ONE_MINUS_CONSTANT_ALPHA = 0x8004;
 
-if (goog.DEBUG) {
-  /**
-   * Reset the event queue.
-   * @private
-   */
-  goog.async.run.resetQueue_ = function() {
-    goog.async.run.workQueueScheduled_ = false;
-    goog.async.run.workQueue_ = [];
-  };
 
-  // If there is a clock implemenation in use for testing
-  // and it is reset, reset the queue.
-  goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_);
-}
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND_COLOR = 0x8005;
 
 
 /**
- * Run any pending goog.async.run work items. This function is not intended
- * for general use, but for use by entry point handlers to run items ahead of
- * goog.async.nextTick.
+ * @const
+ * @type {number}
  */
-goog.async.run.processWorkQueue = function() {
-  // NOTE: additional work queue items may be pushed while processing.
-  while (goog.async.run.workQueue_.length) {
-    // Don't let the work queue grow indefinitely.
-    var workItems = goog.async.run.workQueue_;
-    goog.async.run.workQueue_ = [];
-    for (var i = 0; i < workItems.length; i++) {
-      var workItem = workItems[i];
-      try {
-        workItem.fn.call(workItem.scope);
-      } catch (e) {
-        goog.async.throwException(e);
-      }
-    }
-  }
+goog.webgl.ARRAY_BUFFER = 0x8892;
 
-  // There are no more work items, reset the work queue.
-  goog.async.run.workQueueScheduled_ = false;
-};
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;
 
 
 /**
- * @constructor
- * @final
- * @struct
- * @private
- *
- * @param {function()} fn
- * @param {Object|null|undefined} scope
+ * @const
+ * @type {number}
  */
-goog.async.run.WorkItem_ = function(fn, scope) {
-  /** @const */ this.fn = fn;
-  /** @const */ this.scope = scope;
-};
-
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+goog.webgl.ARRAY_BUFFER_BINDING = 0x8894;
 
-goog.provide('goog.promise.Resolver');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ELEMENT_ARRAY_BUFFER_BINDING = 0x8895;
 
 
 /**
- * Resolver interface for promises. The resolver is a convenience interface that
- * bundles the promise and its associated resolve and reject functions together,
- * for cases where the resolver needs to be persisted internally.
- *
- * @interface
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.promise.Resolver = function() {};
+goog.webgl.STREAM_DRAW = 0x88E0;
 
 
 /**
- * The promise that created this resolver.
- * @const {!goog.Promise.<TYPE>}
+ * @const
+ * @type {number}
  */
-goog.promise.Resolver.prototype.promise;
+goog.webgl.STATIC_DRAW = 0x88E4;
 
 
 /**
- * Resolves this resolver with the specified value.
- * @const {function((TYPE|goog.Promise.<TYPE>|Thenable)=)}
+ * @const
+ * @type {number}
  */
-goog.promise.Resolver.prototype.resolve;
+goog.webgl.DYNAMIC_DRAW = 0x88E8;
 
 
 /**
- * Rejects this resolver with the specified reason.
- * @const {function(*): void}
+ * @const
+ * @type {number}
  */
-goog.promise.Resolver.prototype.reject;
+goog.webgl.BUFFER_SIZE = 0x8764;
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
-goog.provide('goog.Promise');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BUFFER_USAGE = 0x8765;
 
-goog.require('goog.Thenable');
-goog.require('goog.asserts');
-goog.require('goog.async.run');
-goog.require('goog.async.throwException');
-goog.require('goog.debug.Error');
-goog.require('goog.promise.Resolver');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CURRENT_VERTEX_ATTRIB = 0x8626;
 
 
 /**
- * Promises provide a result that may be resolved asynchronously. A Promise may
- * be resolved by being fulfilled or rejected with a value, which will be known
- * as the fulfillment value or the rejection reason. Whether fulfilled or
- * rejected, the Promise result is immutable once it is set.
- *
- * Promises may represent results of any type, including undefined. Rejection
- * reasons are typically Errors, but may also be of any type. Closure Promises
- * allow for optional type annotations that enforce that fulfillment values are
- * of the appropriate types at compile time.
- *
- * The result of a Promise is accessible by calling {@code then} and registering
- * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise
- * resolves, the relevant callbacks are invoked with the fulfillment value or
- * rejection reason as argument. Callbacks are always invoked in the order they
- * were registered, even when additional {@code then} calls are made from inside
- * another callback. A callback is always run asynchronously sometime after the
- * scope containing the registering {@code then} invocation has returned.
- *
- * If a Promise is resolved with another Promise, the first Promise will block
- * until the second is resolved, and then assumes the same result as the second
- * Promise. This allows Promises to depend on the results of other Promises,
- * linking together multiple asynchronous operations.
- *
- * This implementation is compatible with the Promises/A+ specification and
- * passes that specification's conformance test suite. A Closure Promise may be
- * resolved with a Promise instance (or sufficiently compatible Promise-like
- * object) created by other Promise implementations. From the specification,
- * Promise-like objects are known as "Thenables".
- *
- * @see http://promisesaplus.com/
- *
- * @param {function(
- *             this:RESOLVER_CONTEXT,
- *             function((TYPE|IThenable.<TYPE>|Thenable)=),
- *             function(*)): void} resolver
- *     Initialization function that is invoked immediately with {@code resolve}
- *     and {@code reject} functions as arguments. The Promise is resolved or
- *     rejected with the first argument passed to either function.
- * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the
- *     resolver function. If unspecified, the resolver function will be executed
- *     in the default scope.
- * @constructor
- * @struct
- * @final
- * @implements {goog.Thenable.<TYPE>}
- * @template TYPE,RESOLVER_CONTEXT
+ * @const
+ * @type {number}
  */
-goog.Promise = function(resolver, opt_context) {
-  /**
-   * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or
-   * BLOCKED.
-   * @private {goog.Promise.State_}
-   */
-  this.state_ = goog.Promise.State_.PENDING;
+goog.webgl.FRONT = 0x0404;
 
-  /**
-   * The resolved result of the Promise. Immutable once set with either a
-   * fulfillment value or rejection reason.
-   * @private {*}
-   */
-  this.result_ = undefined;
 
-  /**
-   * For Promises created by calling {@code then()}, the originating parent.
-   * @private {goog.Promise}
-   */
-  this.parent_ = null;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BACK = 0x0405;
 
-  /**
-   * The list of {@code onFulfilled} and {@code onRejected} callbacks added to
-   * this Promise by calls to {@code then()}.
-   * @private {Array.<goog.Promise.CallbackEntry_>}
-   */
-  this.callbackEntries_ = null;
 
-  /**
-   * Whether the Promise is in the queue of Promises to execute.
-   * @private {boolean}
-   */
-  this.executing_ = false;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRONT_AND_BACK = 0x0408;
 
-  if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
-    /**
-     * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater
-     * than 0 milliseconds. The ID is set when the Promise is rejected, and
-     * cleared only if an {@code onRejected} callback is invoked for the
-     * Promise (or one of its descendants) before the delay is exceeded.
-     *
-     * If the rejection is not handled before the timeout completes, the
-     * rejection reason is passed to the unhandled rejection handler.
-     * @private {number}
-     */
-    this.unhandledRejectionId_ = 0;
-  } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
-    /**
-     * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a
-     * boolean that is set if the Promise is rejected, and reset to false if an
-     * {@code onRejected} callback is invoked for the Promise (or one of its
-     * descendants). If the rejection is not handled before the next timestep,
-     * the rejection reason is passed to the unhandled rejection handler.
-     * @private {boolean}
-     */
-    this.hadUnhandledRejection_ = false;
-  }
 
-  if (goog.Promise.LONG_STACK_TRACES) {
-    /**
-     * A list of stack trace frames pointing to the locations where this Promise
-     * was created or had callbacks added to it. Saved to add additional context
-     * to stack traces when an exception is thrown.
-     * @private {!Array.<string>}
-     */
-    this.stack_ = [];
-    this.addStackTrace_(new Error('created'));
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CULL_FACE = 0x0B44;
 
-    /**
-     * Index of the most recently executed stack frame entry.
-     * @private {number}
-     */
-    this.currentStep_ = 0;
-  }
 
-  try {
-    var self = this;
-    resolver.call(
-        opt_context,
-        function(value) {
-          self.resolve_(goog.Promise.State_.FULFILLED, value);
-        },
-        function(reason) {
-          self.resolve_(goog.Promise.State_.REJECTED, reason);
-        });
-  } catch (e) {
-    this.resolve_(goog.Promise.State_.REJECTED, e);
-  }
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.BLEND = 0x0BE2;
 
 
 /**
- * @define {boolean} Whether traces of {@code then} calls should be included in
- * exceptions thrown
+ * @const
+ * @type {number}
  */
-goog.define('goog.Promise.LONG_STACK_TRACES', false);
+goog.webgl.DITHER = 0x0BD0;
 
 
 /**
- * @define {number} The delay in milliseconds before a rejected Promise's reason
- * is passed to the rejection handler. By default, the rejection handler
- * rethrows the rejection reason so that it appears in the developer console or
- * {@code window.onerror} handler.
- *
- * Rejections are rethrown as quickly as possible by default. A negative value
- * disables rejection handling entirely.
+ * @const
+ * @type {number}
  */
-goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0);
+goog.webgl.STENCIL_TEST = 0x0B90;
 
 
 /**
- * The possible internal states for a Promise. These states are not directly
- * observable to external callers.
- * @enum {number}
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.State_ = {
-  /** The Promise is waiting for resolution. */
-  PENDING: 0,
-
-  /** The Promise is blocked waiting for the result of another Thenable. */
-  BLOCKED: 1,
+goog.webgl.DEPTH_TEST = 0x0B71;
 
-  /** The Promise has been resolved with a fulfillment value. */
-  FULFILLED: 2,
 
-  /** The Promise has been resolved with a rejection reason. */
-  REJECTED: 3
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SCISSOR_TEST = 0x0C11;
 
 
 /**
- * Typedef for entries in the callback chain. Each call to {@code then},
- * {@code thenCatch}, or {@code thenAlways} creates an entry containing the
- * functions that may be invoked once the Promise is resolved.
- *
- * @typedef {{
- *   child: goog.Promise,
- *   onFulfilled: function(*),
- *   onRejected: function(*)
- * }}
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.CallbackEntry_;
+goog.webgl.POLYGON_OFFSET_FILL = 0x8037;
 
 
 /**
- * @param {(TYPE|goog.Thenable.<TYPE>|Thenable)=} opt_value
- * @return {!goog.Promise.<TYPE>} A new Promise that is immediately resolved
- *     with the given value.
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.Promise.resolve = function(opt_value) {
-  return new goog.Promise(function(resolve, reject) {
-    resolve(opt_value);
-  });
-};
+goog.webgl.SAMPLE_ALPHA_TO_COVERAGE = 0x809E;
 
 
 /**
- * @param {*=} opt_reason
- * @return {!goog.Promise} A new Promise that is immediately rejected with the
- *     given reason.
+ * @const
+ * @type {number}
  */
-goog.Promise.reject = function(opt_reason) {
-  return new goog.Promise(function(resolve, reject) {
-    reject(opt_reason);
-  });
-};
+goog.webgl.SAMPLE_COVERAGE = 0x80A0;
 
 
 /**
- * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises
- * @return {!goog.Promise.<TYPE>} A Promise that receives the result of the
- *     first Promise (or Promise-like) input to complete.
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.Promise.race = function(promises) {
-  return new goog.Promise(function(resolve, reject) {
-    if (!promises.length) {
-      resolve(undefined);
-    }
-    for (var i = 0, promise; promise = promises[i]; i++) {
-      promise.then(resolve, reject);
-    }
-  });
-};
+goog.webgl.NO_ERROR = 0;
 
 
 /**
- * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises
- * @return {!goog.Promise.<!Array.<TYPE>>} A Promise that receives a list of
- *     every fulfilled value once every input Promise (or Promise-like) is
- *     successfully fulfilled, or is rejected by the first rejection result.
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.Promise.all = function(promises) {
-  return new goog.Promise(function(resolve, reject) {
-    var toFulfill = promises.length;
-    var values = [];
+goog.webgl.INVALID_ENUM = 0x0500;
 
-    if (!toFulfill) {
-      resolve(values);
-      return;
-    }
 
-    var onFulfill = function(index, value) {
-      toFulfill--;
-      values[index] = value;
-      if (toFulfill == 0) {
-        resolve(values);
-      }
-    };
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVALID_VALUE = 0x0501;
 
-    var onReject = function(reason) {
-      reject(reason);
-    };
 
-    for (var i = 0, promise; promise = promises[i]; i++) {
-      promise.then(goog.partial(onFulfill, i), onReject);
-    }
-  });
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVALID_OPERATION = 0x0502;
 
 
 /**
- * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises
- * @return {!goog.Promise.<TYPE>} A Promise that receives the value of the first
- *     input to be fulfilled, or is rejected with a list of every rejection
- *     reason if all inputs are rejected.
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.Promise.firstFulfilled = function(promises) {
-  return new goog.Promise(function(resolve, reject) {
-    var toReject = promises.length;
-    var reasons = [];
+goog.webgl.OUT_OF_MEMORY = 0x0505;
 
-    if (!toReject) {
-      resolve(undefined);
-      return;
-    }
 
-    var onFulfill = function(value) {
-      resolve(value);
-    };
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CW = 0x0900;
 
-    var onReject = function(index, reason) {
-      toReject--;
-      reasons[index] = reason;
-      if (toReject == 0) {
-        reject(reasons);
-      }
-    };
 
-    for (var i = 0, promise; promise = promises[i]; i++) {
-      promise.then(onFulfill, goog.partial(onReject, i));
-    }
-  });
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CCW = 0x0901;
 
 
 /**
- * @return {!goog.promise.Resolver.<TYPE>} Resolver wrapping the promise and its
- *     resolve / reject functions. Resolving or rejecting the resolver
- *     resolves or rejects the promise.
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.Promise.withResolver = function() {
-  var resolve, reject;
-  var promise = new goog.Promise(function(rs, rj) {
-    resolve = rs;
-    reject = rj;
-  });
-  return new goog.Promise.Resolver_(promise, resolve, reject);
-};
+goog.webgl.LINE_WIDTH = 0x0B21;
 
 
 /**
- * Adds callbacks that will operate on the result of the Promise, returning a
- * new child Promise.
- *
- * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
- * with the fulfillment value as argument, and the child Promise will be
- * fulfilled with the return value of the callback. If the callback throws an
- * exception, the child Promise will be rejected with the thrown value instead.
- *
- * If the Promise is rejected, the {@code onRejected} callback will be invoked
- * with the rejection reason as argument, and the child Promise will be resolved
- * with the return value or rejected with the thrown value of the callback.
- *
- * @override
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.then = function(
-    opt_onFulfilled, opt_onRejected, opt_context) {
+goog.webgl.ALIASED_POINT_SIZE_RANGE = 0x846D;
 
-  if (opt_onFulfilled != null) {
-    goog.asserts.assertFunction(opt_onFulfilled,
-        'opt_onFulfilled should be a function.');
-  }
-  if (opt_onRejected != null) {
-    goog.asserts.assertFunction(opt_onRejected,
-        'opt_onRejected should be a function. Did you pass opt_context ' +
-        'as the second argument instead of the third?');
-  }
 
-  if (goog.Promise.LONG_STACK_TRACES) {
-    this.addStackTrace_(new Error('then'));
-  }
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ALIASED_LINE_WIDTH_RANGE = 0x846E;
 
-  return this.addChildPromise_(
-      goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null,
-      goog.isFunction(opt_onRejected) ? opt_onRejected : null,
-      opt_context);
-};
-goog.Thenable.addImplementation(goog.Promise);
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CULL_FACE_MODE = 0x0B45;
 
 
 /**
- * Adds a callback that will be invoked whether the Promise is fulfilled or
- * rejected. The callback receives no argument, and no new child Promise is
- * created. This is useful for ensuring that cleanup takes place after certain
- * asynchronous operations. Callbacks added with {@code thenAlways} will be
- * executed in the same order with other calls to {@code then},
- * {@code thenAlways}, or {@code thenCatch}.
- *
- * Since it does not produce a new child Promise, cancellation propagation is
- * not prevented by adding callbacks with {@code thenAlways}. A Promise that has
- * a cleanup handler added with {@code thenAlways} will be canceled if all of
- * its children created by {@code then} (or {@code thenCatch}) are canceled.
- *
- * @param {function(this:THIS): void} onResolved A function that will be invoked
- *     when the Promise is resolved.
- * @param {THIS=} opt_context An optional context object that will be the
- *     execution context for the callbacks. By default, functions are executed
- *     in the global scope.
- * @return {!goog.Promise.<TYPE>} This Promise, for chaining additional calls.
- * @template THIS
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.thenAlways = function(onResolved, opt_context) {
-  if (goog.Promise.LONG_STACK_TRACES) {
-    this.addStackTrace_(new Error('thenAlways'));
-  }
+goog.webgl.FRONT_FACE = 0x0B46;
 
-  var callback = function() {
-    try {
-      // Ensure that no arguments are passed to onResolved.
-      onResolved.call(opt_context);
-    } catch (err) {
-      goog.Promise.handleRejection_.call(null, err);
-    }
-  };
 
-  this.addCallbackEntry_({
-    child: null,
-    onRejected: callback,
-    onFulfilled: callback
-  });
-  return this;
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_RANGE = 0x0B70;
 
 
 /**
- * Adds a callback that will be invoked only if the Promise is rejected. This
- * is equivalent to {@code then(null, onRejected)}.
- *
- * @param {!function(this:THIS, *): *} onRejected A function that will be
- *     invoked with the rejection reason if the Promise is rejected.
- * @param {THIS=} opt_context An optional context object that will be the
- *     execution context for the callbacks. By default, functions are executed
- *     in the global scope.
- * @return {!goog.Promise} A new Promise that will receive the result of the
- *     callback.
- * @template THIS
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.thenCatch = function(onRejected, opt_context) {
-  if (goog.Promise.LONG_STACK_TRACES) {
-    this.addStackTrace_(new Error('thenCatch'));
-  }
-  return this.addChildPromise_(null, onRejected, opt_context);
-};
+goog.webgl.DEPTH_WRITEMASK = 0x0B72;
 
 
 /**
- * Cancels the Promise if it is still pending by rejecting it with a cancel
- * Error. No action is performed if the Promise is already resolved.
- *
- * All child Promises of the canceled Promise will be rejected with the same
- * cancel error, as with normal Promise rejection. If the Promise to be canceled
- * is the only child of a pending Promise, the parent Promise will also be
- * canceled. Cancellation may propagate upward through multiple generations.
- *
- * @param {string=} opt_message An optional debugging message for describing the
- *     cancellation reason.
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.cancel = function(opt_message) {
-  if (this.state_ == goog.Promise.State_.PENDING) {
-    goog.async.run(function() {
-      var err = new goog.Promise.CancellationError(opt_message);
-      this.cancelInternal_(err);
-    }, this);
-  }
-};
+goog.webgl.DEPTH_CLEAR_VALUE = 0x0B73;
 
 
 /**
- * Cancels this Promise with the given error.
- *
- * @param {!Error} err The cancellation error.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.cancelInternal_ = function(err) {
-  if (this.state_ == goog.Promise.State_.PENDING) {
-    if (this.parent_) {
-      // Cancel the Promise and remove it from the parent's child list.
-      this.parent_.cancelChild_(this, err);
-    } else {
-      this.resolve_(goog.Promise.State_.REJECTED, err);
-    }
-  }
-};
+goog.webgl.DEPTH_FUNC = 0x0B74;
 
 
 /**
- * Cancels a child Promise from the list of callback entries. If the Promise has
- * not already been resolved, reject it with a cancel error. If there are no
- * other children in the list of callback entries, propagate the cancellation
- * by canceling this Promise as well.
- *
- * @param {!goog.Promise} childPromise The Promise to cancel.
- * @param {!Error} err The cancel error to use for rejecting the Promise.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.cancelChild_ = function(childPromise, err) {
-  if (!this.callbackEntries_) {
-    return;
-  }
-  var childCount = 0;
-  var childIndex = -1;
+goog.webgl.STENCIL_CLEAR_VALUE = 0x0B91;
 
-  // Find the callback entry for the childPromise, and count whether there are
-  // additional child Promises.
-  for (var i = 0, entry; entry = this.callbackEntries_[i]; i++) {
-    var child = entry.child;
-    if (child) {
-      childCount++;
-      if (child == childPromise) {
-        childIndex = i;
-      }
-      if (childIndex >= 0 && childCount > 1) {
-        break;
-      }
-    }
-  }
 
-  // If the child Promise was the only child, cancel this Promise as well.
-  // Otherwise, reject only the child Promise with the cancel error.
-  if (childIndex >= 0) {
-    if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) {
-      this.cancelInternal_(err);
-    } else {
-      var callbackEntry = this.callbackEntries_.splice(childIndex, 1)[0];
-      this.executeCallback_(
-          callbackEntry, goog.Promise.State_.REJECTED, err);
-    }
-  }
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_FUNC = 0x0B92;
 
 
 /**
- * Adds a callback entry to the current Promise, and schedules callback
- * execution if the Promise has already been resolved.
- *
- * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing
- *     {@code onFulfilled} and {@code onRejected} callbacks to execute after
- *     the Promise is resolved.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) {
-  if ((!this.callbackEntries_ || !this.callbackEntries_.length) &&
-      (this.state_ == goog.Promise.State_.FULFILLED ||
-       this.state_ == goog.Promise.State_.REJECTED)) {
-    this.scheduleCallbacks_();
-  }
-  if (!this.callbackEntries_) {
-    this.callbackEntries_ = [];
-  }
-  this.callbackEntries_.push(callbackEntry);
-};
+goog.webgl.STENCIL_FAIL = 0x0B94;
 
 
 /**
- * Creates a child Promise and adds it to the callback entry list. The result of
- * the child Promise is determined by the state of the parent Promise and the
- * result of the {@code onFulfilled} or {@code onRejected} callbacks as
- * specified in the Promise resolution procedure.
- *
- * @see http://promisesaplus.com/#the__method
- *
- * @param {?function(this:THIS, TYPE):
- *          (RESULT|goog.Promise.<RESULT>|Thenable)} onFulfilled A callback that
- *     will be invoked if the Promise is fullfilled, or null.
- * @param {?function(this:THIS, *): *} onRejected A callback that will be
- *     invoked if the Promise is rejected, or null.
- * @param {THIS=} opt_context An optional execution context for the callbacks.
- *     in the default calling context.
- * @return {!goog.Promise} The child Promise.
- * @template RESULT,THIS
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.addChildPromise_ = function(
-    onFulfilled, onRejected, opt_context) {
+goog.webgl.STENCIL_PASS_DEPTH_FAIL = 0x0B95;
 
-  var callbackEntry = {
-    child: null,
-    onFulfilled: null,
-    onRejected: null
-  };
 
-  callbackEntry.child = new goog.Promise(function(resolve, reject) {
-    // Invoke onFulfilled, or resolve with the parent's value if absent.
-    callbackEntry.onFulfilled = onFulfilled ? function(value) {
-      try {
-        var result = onFulfilled.call(opt_context, value);
-        resolve(result);
-      } catch (err) {
-        reject(err);
-      }
-    } : resolve;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_PASS_DEPTH_PASS = 0x0B96;
 
-    // Invoke onRejected, or reject with the parent's reason if absent.
-    callbackEntry.onRejected = onRejected ? function(reason) {
-      try {
-        var result = onRejected.call(opt_context, reason);
-        if (!goog.isDef(result) &&
-            reason instanceof goog.Promise.CancellationError) {
-          // Propagate cancellation to children if no other result is returned.
-          reject(reason);
-        } else {
-          resolve(result);
-        }
-      } catch (err) {
-        reject(err);
-      }
-    } : reject;
-  });
 
-  callbackEntry.child.parent_ = this;
-  this.addCallbackEntry_(
-      /** @type {goog.Promise.CallbackEntry_} */ (callbackEntry));
-  return callbackEntry.child;
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_REF = 0x0B97;
 
 
 /**
- * Unblocks the Promise and fulfills it with the given value.
- *
- * @param {TYPE} value
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.unblockAndFulfill_ = function(value) {
-  goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
-  this.state_ = goog.Promise.State_.PENDING;
-  this.resolve_(goog.Promise.State_.FULFILLED, value);
-};
+goog.webgl.STENCIL_VALUE_MASK = 0x0B93;
 
 
 /**
- * Unblocks the Promise and rejects it with the given rejection reason.
- *
- * @param {*} reason
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.unblockAndReject_ = function(reason) {
-  goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
-  this.state_ = goog.Promise.State_.PENDING;
-  this.resolve_(goog.Promise.State_.REJECTED, reason);
-};
+goog.webgl.STENCIL_WRITEMASK = 0x0B98;
 
 
 /**
- * Attempts to resolve a Promise with a given resolution state and value. This
- * is a no-op if the given Promise has already been resolved.
- *
- * If the given result is a Thenable (such as another Promise), the Promise will
- * be resolved with the same state and result as the Thenable once it is itself
- * resolved.
- *
- * If the given result is not a Thenable, the Promise will be fulfilled or
- * rejected with that result based on the given state.
- *
- * @see http://promisesaplus.com/#the_promise_resolution_procedure
- *
- * @param {goog.Promise.State_} state
- * @param {*} x The result to apply to the Promise.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.resolve_ = function(state, x) {
-  if (this.state_ != goog.Promise.State_.PENDING) {
-    return;
-  }
-
-  if (this == x) {
-    state = goog.Promise.State_.REJECTED;
-    x = new TypeError('Promise cannot resolve to itself');
+goog.webgl.STENCIL_BACK_FUNC = 0x8800;
 
-  } else if (goog.Thenable.isImplementedBy(x)) {
-    x = /** @type {!goog.Thenable} */ (x);
-    this.state_ = goog.Promise.State_.BLOCKED;
-    x.then(this.unblockAndFulfill_, this.unblockAndReject_, this);
-    return;
 
-  } else if (goog.isObject(x)) {
-    try {
-      var then = x['then'];
-      if (goog.isFunction(then)) {
-        this.tryThen_(x, then);
-        return;
-      }
-    } catch (e) {
-      state = goog.Promise.State_.REJECTED;
-      x = e;
-    }
-  }
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_FAIL = 0x8801;
 
-  this.result_ = x;
-  this.state_ = state;
-  this.scheduleCallbacks_();
 
-  if (state == goog.Promise.State_.REJECTED &&
-      !(x instanceof goog.Promise.CancellationError)) {
-    goog.Promise.addUnhandledRejection_(this, x);
-  }
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802;
 
 
 /**
- * Attempts to call the {@code then} method on an object in the hopes that it is
- * a Promise-compatible instance. This allows interoperation between different
- * Promise implementations, however a non-compliant object may cause a Promise
- * to hang indefinitely. If the {@code then} method throws an exception, the
- * dependent Promise will be rejected with the thrown value.
- *
- * @see http://promisesaplus.com/#point-70
- *
- * @param {Thenable} thenable An object with a {@code then} method that may be
- *     compatible with the Promise/A+ specification.
- * @param {!Function} then The {@code then} method of the Thenable object.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.tryThen_ = function(thenable, then) {
-  this.state_ = goog.Promise.State_.BLOCKED;
-  var promise = this;
-  var called = false;
-
-  var resolve = function(value) {
-    if (!called) {
-      called = true;
-      promise.unblockAndFulfill_(value);
-    }
-  };
+goog.webgl.STENCIL_BACK_PASS_DEPTH_PASS = 0x8803;
 
-  var reject = function(reason) {
-    if (!called) {
-      called = true;
-      promise.unblockAndReject_(reason);
-    }
-  };
 
-  try {
-    then.call(thenable, resolve, reject);
-  } catch (e) {
-    reject(e);
-  }
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_BACK_REF = 0x8CA3;
 
 
 /**
- * Executes the pending callbacks of a resolved Promise after a timeout.
- *
- * Section 2.2.4 of the Promises/A+ specification requires that Promise
- * callbacks must only be invoked from a call stack that only contains Promise
- * implementation code, which we accomplish by invoking callback execution after
- * a timeout. If {@code startExecution_} is called multiple times for the same
- * Promise, the callback chain will be evaluated only once. Additional callbacks
- * may be added during the evaluation phase, and will be executed in the same
- * event loop.
- *
- * All Promises added to the waiting list during the same browser event loop
- * will be executed in one batch to avoid using a separate timeout per Promise.
- *
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.scheduleCallbacks_ = function() {
-  if (!this.executing_) {
-    this.executing_ = true;
-    goog.async.run(this.executeCallbacks_, this);
-  }
-};
+goog.webgl.STENCIL_BACK_VALUE_MASK = 0x8CA4;
 
 
 /**
- * Executes all pending callbacks for this Promise.
- *
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.executeCallbacks_ = function() {
-  while (this.callbackEntries_ && this.callbackEntries_.length) {
-    var entries = this.callbackEntries_;
-    this.callbackEntries_ = [];
-
-    for (var i = 0; i < entries.length; i++) {
-      if (goog.Promise.LONG_STACK_TRACES) {
-        this.currentStep_++;
-      }
-      this.executeCallback_(entries[i], this.state_, this.result_);
-    }
-  }
-  this.executing_ = false;
-};
+goog.webgl.STENCIL_BACK_WRITEMASK = 0x8CA5;
 
 
 /**
- * Executes a pending callback for this Promise. Invokes an {@code onFulfilled}
- * or {@code onRejected} callback based on the resolved state of the Promise.
- *
- * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the
- *     onFulfilled and/or onRejected callbacks for this step.
- * @param {goog.Promise.State_} state The resolution status of the Promise,
- *     either FULFILLED or REJECTED.
- * @param {*} result The resolved result of the Promise.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.executeCallback_ = function(
-    callbackEntry, state, result) {
-  if (state == goog.Promise.State_.FULFILLED) {
-    callbackEntry.onFulfilled(result);
-  } else {
-    this.removeUnhandledRejection_();
-    callbackEntry.onRejected(result);
-  }
-};
+goog.webgl.VIEWPORT = 0x0BA2;
 
 
 /**
- * Records a stack trace entry for functions that call {@code then} or the
- * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}.
- *
- * @param {!Error} err An Error object created by the calling function for
- *     providing a stack trace.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.addStackTrace_ = function(err) {
-  if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) {
-    // Extract the third line of the stack trace, which is the entry for the
-    // user function that called into Promise code.
-    var trace = err.stack.split('\n', 4)[3];
-    var message = err.message;
-
-    // Pad the message to align the traces.
-    message += Array(11 - message.length).join(' ');
-    this.stack_.push(message + trace);
-  }
-};
+goog.webgl.SCISSOR_BOX = 0x0C10;
 
 
 /**
- * Adds extra stack trace information to an exception for the list of
- * asynchronous {@code then} calls that have been run for this Promise. Stack
- * trace information is recorded in {@see #addStackTrace_}, and appended to
- * rethrown errors when {@code LONG_STACK_TRACES} is enabled.
- *
- * @param {*} err An unhandled exception captured during callback execution.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.appendLongStack_ = function(err) {
-  if (goog.Promise.LONG_STACK_TRACES &&
-      err && goog.isString(err.stack) && this.stack_.length) {
-    var longTrace = ['Promise trace:'];
-
-    for (var promise = this; promise; promise = promise.parent_) {
-      for (var i = this.currentStep_; i >= 0; i--) {
-        longTrace.push(promise.stack_[i]);
-      }
-      longTrace.push('Value: ' +
-          '[' + (promise.state_ == goog.Promise.State_.REJECTED ?
-              'REJECTED' : 'FULFILLED') + '] ' +
-          '<' + String(promise.result_) + '>');
-    }
-    err.stack += '\n\n' + longTrace.join('\n');
-  }
-};
+goog.webgl.COLOR_CLEAR_VALUE = 0x0C22;
 
 
 /**
- * Marks this rejected Promise as having being handled. Also marks any parent
- * Promises in the rejected state as handled. The rejection handler will no
- * longer be invoked for this Promise (if it has not been called already).
- *
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.prototype.removeUnhandledRejection_ = function() {
-  if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
-    for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) {
-      goog.global.clearTimeout(p.unhandledRejectionId_);
-      p.unhandledRejectionId_ = 0;
-    }
-  } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
-    for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) {
-      p.hadUnhandledRejection_ = false;
-    }
-  }
-};
+goog.webgl.COLOR_WRITEMASK = 0x0C23;
 
 
 /**
- * Marks this rejected Promise as unhandled. If no {@code onRejected} callback
- * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY}
- * expires, the reason will be passed to the unhandled rejection handler. The
- * handler typically rethrows the rejection reason so that it becomes visible in
- * the developer console.
- *
- * @param {!goog.Promise} promise The rejected Promise.
- * @param {*} reason The Promise rejection reason.
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.addUnhandledRejection_ = function(promise, reason) {
-  if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
-    promise.unhandledRejectionId_ = goog.global.setTimeout(function() {
-      promise.appendLongStack_(reason);
-      goog.Promise.handleRejection_.call(null, reason);
-    }, goog.Promise.UNHANDLED_REJECTION_DELAY);
-
-  } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
-    promise.hadUnhandledRejection_ = true;
-    goog.async.run(function() {
-      if (promise.hadUnhandledRejection_) {
-        promise.appendLongStack_(reason);
-        goog.Promise.handleRejection_.call(null, reason);
-      }
-    });
-  }
-};
+goog.webgl.UNPACK_ALIGNMENT = 0x0CF5;
 
 
 /**
- * A method that is invoked with the rejection reasons for Promises that are
- * rejected but have no {@code onRejected} callbacks registered yet.
- * @type {function(*)}
- * @private
+ * @const
+ * @type {number}
  */
-goog.Promise.handleRejection_ = goog.async.throwException;
+goog.webgl.PACK_ALIGNMENT = 0x0D05;
 
 
 /**
- * Sets a handler that will be called with reasons from unhandled rejected
- * Promises. If the rejected Promise (or one of its descendants) has an
- * {@code onRejected} callback registered, the rejection will be considered
- * handled, and the rejection handler will not be called.
- *
- * By default, unhandled rejections are rethrown so that the error may be
- * captured by the developer console or a {@code window.onerror} handler.
- *
- * @param {function(*)} handler A function that will be called with reasons from
- *     rejected Promises. Defaults to {@code goog.async.throwException}.
+ * @const
+ * @type {number}
  */
-goog.Promise.setUnhandledRejectionHandler = function(handler) {
-  goog.Promise.handleRejection_ = handler;
-};
-
+goog.webgl.MAX_TEXTURE_SIZE = 0x0D33;
 
 
 /**
- * Error used as a rejection reason for canceled Promises.
- *
- * @param {string=} opt_message
- * @constructor
- * @extends {goog.debug.Error}
- * @final
+ * @const
+ * @type {number}
  */
-goog.Promise.CancellationError = function(opt_message) {
-  goog.Promise.CancellationError.base(this, 'constructor', opt_message);
-};
-goog.inherits(goog.Promise.CancellationError, goog.debug.Error);
-
+goog.webgl.MAX_VIEWPORT_DIMS = 0x0D3A;
 
-/** @override */
-goog.Promise.CancellationError.prototype.name = 'cancel';
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SUBPIXEL_BITS = 0x0D50;
 
 
 /**
- * Internal implementation of the resolver interface.
- *
- * @param {!goog.Promise.<TYPE>} promise
- * @param {function((TYPE|goog.Promise.<TYPE>|Thenable)=)} resolve
- * @param {function(*): void} reject
- * @implements {goog.promise.Resolver.<TYPE>}
- * @final @struct
- * @constructor
- * @private
- * @template TYPE
+ * @const
+ * @type {number}
  */
-goog.Promise.Resolver_ = function(promise, resolve, reject) {
-  /** @const */
-  this.promise = promise;
+goog.webgl.RED_BITS = 0x0D52;
 
-  /** @const */
-  this.resolve = resolve;
 
-  /** @const */
-  this.reject = reject;
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.GREEN_BITS = 0x0D53;
 
-// Copyright 2007 Bob Ippolito. All Rights Reserved.
-// Modifications Copyright 2009 The Closure Library Authors. All Rights
-// Reserved.
 
 /**
- * @license Portions of this code are from MochiKit, received by
- * The Closure Authors under the MIT license. All other code is Copyright
- * 2005-2009 The Closure Authors. All Rights Reserved.
+ * @const
+ * @type {number}
  */
+goog.webgl.BLUE_BITS = 0x0D54;
+
 
 /**
- * @fileoverview Classes for tracking asynchronous operations and handling the
- * results. The Deferred object here is patterned after the Deferred object in
- * the Twisted python networking framework.
- *
- * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
- *
- * Based on the Dojo code which in turn is based on the MochiKit code.
- *
+ * @const
+ * @type {number}
  */
+goog.webgl.ALPHA_BITS = 0x0D55;
 
-goog.provide('goog.async.Deferred');
-goog.provide('goog.async.Deferred.AlreadyCalledError');
-goog.provide('goog.async.Deferred.CanceledError');
-
-goog.require('goog.Promise');
-goog.require('goog.Thenable');
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.debug.Error');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_BITS = 0x0D56;
 
 
 /**
- * A Deferred represents the result of an asynchronous operation. A Deferred
- * instance has no result when it is created, and is "fired" (given an initial
- * result) by calling {@code callback} or {@code errback}.
- *
- * Once fired, the result is passed through a sequence of callback functions
- * registered with {@code addCallback} or {@code addErrback}. The functions may
- * mutate the result before it is passed to the next function in the sequence.
- *
- * Callbacks and errbacks may be added at any time, including after the Deferred
- * has been "fired". If there are no pending actions in the execution sequence
- * of a fired Deferred, any new callback functions will be called with the last
- * computed result. Adding a callback function is the only way to access the
- * result of the Deferred.
- *
- * If a Deferred operation is canceled, an optional user-provided cancellation
- * function is invoked which may perform any special cleanup, followed by firing
- * the Deferred's errback sequence with a {@code CanceledError}. If the
- * Deferred has already fired, cancellation is ignored.
- *
- * Deferreds may be templated to a specific type they produce using generics
- * with syntax such as:
- * <code>
- *   /** @type {goog.async.Deferred.<string>} *&#47;
- *   var d = new goog.async.Deferred();
- *   // Compiler can infer that foo is a string.
- *   d.addCallback(function(foo) {...});
- *   d.callback('string');  // Checked to be passed a string
- * </code>
- * Since deferreds are often used to produce different values across a chain,
- * the type information is not propagated across chains, but rather only
- * associated with specifically cast objects.
- *
- * @param {Function=} opt_onCancelFunction A function that will be called if the
- *     Deferred is canceled. If provided, this function runs before the
- *     Deferred is fired with a {@code CanceledError}.
- * @param {Object=} opt_defaultScope The default object context to call
- *     callbacks and errbacks in.
- * @constructor
- * @implements {goog.Thenable.<VALUE>}
- * @template VALUE
+ * @const
+ * @type {number}
  */
-goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
-  /**
-   * Entries in the sequence are arrays containing a callback, an errback, and
-   * an optional scope. The callback or errback in an entry may be null.
-   * @type {!Array.<!Array>}
-   * @private
-   */
-  this.sequence_ = [];
+goog.webgl.STENCIL_BITS = 0x0D57;
 
-  /**
-   * Optional function that will be called if the Deferred is canceled.
-   * @type {Function|undefined}
-   * @private
-   */
-  this.onCancelFunction_ = opt_onCancelFunction;
 
-  /**
-   * The default scope to execute callbacks and errbacks in.
-   * @type {Object}
-   * @private
-   */
-  this.defaultScope_ = opt_defaultScope || null;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.POLYGON_OFFSET_UNITS = 0x2A00;
 
-  /**
-   * Whether the Deferred has been fired.
-   * @type {boolean}
-   * @private
-   */
-  this.fired_ = false;
 
-  /**
-   * Whether the last result in the execution sequence was an error.
-   * @type {boolean}
-   * @private
-   */
-  this.hadError_ = false;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.POLYGON_OFFSET_FACTOR = 0x8038;
 
-  /**
-   * The current Deferred result, updated as callbacks and errbacks are
-   * executed.
-   * @type {*}
-   * @private
-   */
-  this.result_ = undefined;
 
-  /**
-   * Whether the Deferred is blocked waiting on another Deferred to fire. If a
-   * callback or errback returns a Deferred as a result, the execution sequence
-   * is blocked until that Deferred result becomes available.
-   * @type {boolean}
-   * @private
-   */
-  this.blocked_ = false;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_BINDING_2D = 0x8069;
 
-  /**
-   * Whether this Deferred is blocking execution of another Deferred. If this
-   * instance was returned as a result in another Deferred's execution
-   * sequence,that other Deferred becomes blocked until this instance's
-   * execution sequence completes. No additional callbacks may be added to a
-   * Deferred once it is blocking another instance.
-   * @type {boolean}
-   * @private
-   */
-  this.blocking_ = false;
 
-  /**
-   * Whether the Deferred has been canceled without having a custom cancel
-   * function.
-   * @type {boolean}
-   * @private
-   */
-  this.silentlyCanceled_ = false;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLE_BUFFERS = 0x80A8;
 
-  /**
-   * If an error is thrown during Deferred execution with no errback to catch
-   * it, the error is rethrown after a timeout. Reporting the error after a
-   * timeout allows execution to continue in the calling context (empty when
-   * no error is scheduled).
-   * @type {number}
-   * @private
-   */
-  this.unhandledErrorId_ = 0;
 
-  /**
-   * If this Deferred was created by branch(), this will be the "parent"
-   * Deferred.
-   * @type {goog.async.Deferred}
-   * @private
-   */
-  this.parent_ = null;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLES = 0x80A9;
 
-  /**
-   * The number of Deferred objects that have been branched off this one. This
-   * will be decremented whenever a branch is fired or canceled.
-   * @type {number}
-   * @private
-   */
-  this.branches_ = 0;
 
-  if (goog.async.Deferred.LONG_STACK_TRACES) {
-    /**
-     * Holds the stack trace at time of deferred creation if the JS engine
-     * provides the Error.captureStackTrace API.
-     * @private {?string}
-     */
-    this.constructorStack_ = null;
-    if (Error.captureStackTrace) {
-      var target = { stack: '' };
-      Error.captureStackTrace(target, goog.async.Deferred);
-      // Check if Error.captureStackTrace worked. It fails in gjstest.
-      if (typeof target.stack == 'string') {
-        // Remove first line and force stringify to prevent memory leak due to
-        // holding on to actual stack frames.
-        this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, '');
-      }
-    }
-  }
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLE_COVERAGE_VALUE = 0x80AA;
 
 
 /**
- * @define {boolean} Whether unhandled errors should always get rethrown to the
- * global scope. Defaults to the value of goog.DEBUG.
+ * @const
+ * @type {number}
  */
-goog.define('goog.async.Deferred.STRICT_ERRORS', false);
+goog.webgl.SAMPLE_COVERAGE_INVERT = 0x80AB;
 
 
 /**
- * @define {boolean} Whether to attempt to make stack traces long.  Defaults to
- * the value of goog.DEBUG.
+ * @const
+ * @type {number}
  */
-goog.define('goog.async.Deferred.LONG_STACK_TRACES', false);
+goog.webgl.COMPRESSED_TEXTURE_FORMATS = 0x86A3;
 
 
 /**
- * Cancels a Deferred that has not yet been fired, or is blocked on another
- * deferred operation. If this Deferred is waiting for a blocking Deferred to
- * fire, the blocking Deferred will also be canceled.
- *
- * If this Deferred was created by calling branch() on a parent Deferred with
- * opt_propagateCancel set to true, the parent may also be canceled. If
- * opt_deepCancel is set, cancel() will be called on the parent (as well as any
- * other ancestors if the parent is also a branch). If one or more branches were
- * created with opt_propagateCancel set to true, the parent will be canceled if
- * cancel() is called on all of those branches.
- *
- * @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
- *     if cancel() hasn't been called on some of the parent's branches. Has no
- *     effect on a branch without opt_propagateCancel set to true.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
-  if (!this.hasFired()) {
-    if (this.parent_) {
-      // Get rid of the parent reference before potentially running the parent's
-      // canceler function to ensure that this cancellation isn't
-      // double-counted.
-      var parent = this.parent_;
-      delete this.parent_;
-      if (opt_deepCancel) {
-        parent.cancel(opt_deepCancel);
-      } else {
-        parent.branchCancel_();
-      }
-    }
+goog.webgl.DONT_CARE = 0x1100;
 
-    if (this.onCancelFunction_) {
-      // Call in user-specified scope.
-      this.onCancelFunction_.call(this.defaultScope_, this);
-    } else {
-      this.silentlyCanceled_ = true;
-    }
-    if (!this.hasFired()) {
-      this.errback(new goog.async.Deferred.CanceledError(this));
-    }
-  } else if (this.result_ instanceof goog.async.Deferred) {
-    this.result_.cancel();
-  }
-};
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FASTEST = 0x1101;
 
 
 /**
- * Handle a single branch being canceled. Once all branches are canceled, this
- * Deferred will be canceled as well.
- *
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.branchCancel_ = function() {
-  this.branches_--;
-  if (this.branches_ <= 0) {
-    this.cancel();
-  }
-};
+goog.webgl.NICEST = 0x1102;
 
 
 /**
- * Called after a blocking Deferred fires. Unblocks this Deferred and resumes
- * its execution sequence.
- *
- * @param {boolean} isSuccess Whether the result is a success or an error.
- * @param {*} res The result of the blocking Deferred.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
-  this.blocked_ = false;
-  this.updateResult_(isSuccess, res);
-};
+goog.webgl.GENERATE_MIPMAP_HINT = 0x8192;
 
 
 /**
- * Updates the current result based on the success or failure of the last action
- * in the execution sequence.
- *
- * @param {boolean} isSuccess Whether the new result is a success or an error.
- * @param {*} res The result.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
-  this.fired_ = true;
-  this.result_ = res;
-  this.hadError_ = !isSuccess;
-  this.fire_();
-};
+goog.webgl.BYTE = 0x1400;
 
 
 /**
- * Verifies that the Deferred has not yet been fired.
- *
- * @private
- * @throws {Error} If this has already been fired.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.check_ = function() {
-  if (this.hasFired()) {
-    if (!this.silentlyCanceled_) {
-      throw new goog.async.Deferred.AlreadyCalledError(this);
-    }
-    this.silentlyCanceled_ = false;
-  }
-};
+goog.webgl.UNSIGNED_BYTE = 0x1401;
 
 
 /**
- * Fire the execution sequence for this Deferred by passing the starting result
- * to the first registered callback.
- * @param {VALUE=} opt_result The starting result.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.callback = function(opt_result) {
-  this.check_();
-  this.assertNotDeferred_(opt_result);
-  this.updateResult_(true /* isSuccess */, opt_result);
-};
+goog.webgl.SHORT = 0x1402;
 
 
 /**
- * Fire the execution sequence for this Deferred by passing the starting error
- * result to the first registered errback.
- * @param {*=} opt_result The starting error.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.errback = function(opt_result) {
-  this.check_();
-  this.assertNotDeferred_(opt_result);
-  this.makeStackTraceLong_(opt_result);
-  this.updateResult_(false /* isSuccess */, opt_result);
-};
+goog.webgl.UNSIGNED_SHORT = 0x1403;
 
 
 /**
- * Attempt to make the error's stack trace be long in that it contains the
- * stack trace from the point where the deferred was created on top of the
- * current stack trace to give additional context.
- * @param {*} error
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
-  if (!goog.async.Deferred.LONG_STACK_TRACES) {
-    return;
-  }
-  if (this.constructorStack_ && goog.isObject(error) && error.stack &&
-      // Stack looks like it was system generated. See
-      // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
-      (/^[^\n]+(\n   [^\n]+)+/).test(error.stack)) {
-    error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
-        this.constructorStack_;
-  }
-};
+goog.webgl.INT = 0x1404;
 
 
 /**
- * Asserts that an object is not a Deferred.
- * @param {*} obj The object to test.
- * @throws {Error} Throws an exception if the object is a Deferred.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
-  goog.asserts.assert(
-      !(obj instanceof goog.async.Deferred),
-      'An execution sequence may not be initiated with a blocking Deferred.');
-};
+goog.webgl.UNSIGNED_INT = 0x1405;
 
 
 /**
- * Register a callback function to be called with a successful result. If no
- * value is returned by the callback function, the result value is unchanged. If
- * a new value is returned, it becomes the Deferred result and will be passed to
- * the next callback in the execution sequence.
- *
- * If the function throws an error, the error becomes the new result and will be
- * passed to the next errback in the execution chain.
- *
- * If the function returns a Deferred, the execution sequence will be blocked
- * until that Deferred fires. Its result will be passed to the next callback (or
- * errback if it is an error result) in this Deferred's execution sequence.
- *
- * @param {!function(this:T,VALUE):?} cb The function to be called with a
- *     successful result.
- * @param {T=} opt_scope An optional scope to call the callback in.
- * @return {!goog.async.Deferred} This Deferred.
- * @template T
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
-  return this.addCallbacks(cb, null, opt_scope);
-};
+goog.webgl.FLOAT = 0x1406;
 
 
 /**
- * Register a callback function to be called with an error result. If no value
- * is returned by the function, the error result is unchanged. If a new error
- * value is returned or thrown, that error becomes the Deferred result and will
- * be passed to the next errback in the execution sequence.
- *
- * If the errback function handles the error by returning a non-error value,
- * that result will be passed to the next normal callback in the sequence.
- *
- * If the function returns a Deferred, the execution sequence will be blocked
- * until that Deferred fires. Its result will be passed to the next callback (or
- * errback if it is an error result) in this Deferred's execution sequence.
- *
- * @param {!function(this:T,?):?} eb The function to be called on an
- *     unsuccessful result.
- * @param {T=} opt_scope An optional scope to call the errback in.
- * @return {!goog.async.Deferred.<VALUE>} This Deferred.
- * @template T
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
-  return this.addCallbacks(null, eb, opt_scope);
-};
+goog.webgl.DEPTH_COMPONENT = 0x1902;
 
 
 /**
- * Registers one function as both a callback and errback.
- *
- * @param {!function(this:T,?):?} f The function to be called on any result.
- * @param {T=} opt_scope An optional scope to call the function in.
- * @return {!goog.async.Deferred} This Deferred.
- * @template T
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
-  return this.addCallbacks(f, f, opt_scope);
-};
+goog.webgl.ALPHA = 0x1906;
 
 
 /**
- * Registers a callback function and an errback function at the same position
- * in the execution sequence. Only one of these functions will execute,
- * depending on the error state during the execution sequence.
- *
- * NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
- * the callback is invoked, the errback will be skipped, and vice versa.
- *
- * @param {(function(this:T,VALUE):?)|null} cb The function to be called on a
- *     successful result.
- * @param {(function(this:T,?):?)|null} eb The function to be called on an
- *     unsuccessful result.
- * @param {T=} opt_scope An optional scope to call the functions in.
- * @return {!goog.async.Deferred} This Deferred.
- * @template T
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
-  goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
-  this.sequence_.push([cb, eb, opt_scope]);
-  if (this.hasFired()) {
-    this.fire_();
-  }
-  return this;
-};
+goog.webgl.RGB = 0x1907;
 
 
 /**
- * Implements {@see goog.Thenable} for seamless integration with
- * {@see goog.Promise}.
- * Deferred results are mutable and may represent multiple values over
- * their lifetime. Calling {@code then} on a Deferred returns a Promise
- * with the result of the Deferred at that point in its callback chain.
- * Note that if the Deferred result is never mutated, and only
- * {@code then} calls are made, the Deferred will behave like a Promise.
- *
- * @override
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected,
-    opt_context) {
-  var resolve, reject;
-  var promise = new goog.Promise(function(res, rej) {
-    // Copying resolvers to outer scope, so that they are available when the
-    // deferred callback fires (which may be synchronous).
-    resolve = res;
-    reject = rej;
-  });
-  this.addCallbacks(resolve, function(reason) {
-    if (reason instanceof goog.async.Deferred.CanceledError) {
-      promise.cancel();
-    } else {
-      reject(reason);
-    }
-  });
-  return promise.then(opt_onFulfilled, opt_onRejected, opt_context);
-};
-goog.Thenable.addImplementation(goog.async.Deferred);
+goog.webgl.RGBA = 0x1908;
 
 
 /**
- * Links another Deferred to the end of this Deferred's execution sequence. The
- * result of this execution sequence will be passed as the starting result for
- * the chained Deferred, invoking either its first callback or errback.
- *
- * @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
- * @return {!goog.async.Deferred} This Deferred.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
-  this.addCallbacks(
-      otherDeferred.callback, otherDeferred.errback, otherDeferred);
-  return this;
-};
+goog.webgl.LUMINANCE = 0x1909;
 
 
 /**
- * Makes this Deferred wait for another Deferred's execution sequence to
- * complete before continuing.
- *
- * This is equivalent to adding a callback that returns {@code otherDeferred},
- * but doesn't prevent additional callbacks from being added to
- * {@code otherDeferred}.
- *
- * @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred
- *     to wait for.
- * @return {!goog.async.Deferred} This Deferred.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
-  if (!(otherDeferred instanceof goog.async.Deferred)) {
-    // The Thenable case.
-    return this.addCallback(function() {
-      return otherDeferred;
-    });
-  }
-  return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
-};
+goog.webgl.LUMINANCE_ALPHA = 0x190A;
 
 
 /**
- * Creates a branch off this Deferred's execution sequence, and returns it as a
- * new Deferred. The branched Deferred's starting result will be shared with the
- * parent at the point of the branch, even if further callbacks are added to the
- * parent.
- *
- * All branches at the same stage in the execution sequence will receive the
- * same starting value.
- *
- * @param {boolean=} opt_propagateCancel If cancel() is called on every child
- *     branch created with opt_propagateCancel, the parent will be canceled as
- *     well.
- * @return {!goog.async.Deferred.<VALUE>} A Deferred that will be started with
- *     the computed result from this stage in the execution sequence.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
-  var d = new goog.async.Deferred();
-  this.chainDeferred(d);
-  if (opt_propagateCancel) {
-    d.parent_ = this;
-    this.branches_++;
-  }
-  return d;
-};
+goog.webgl.UNSIGNED_SHORT_4_4_4_4 = 0x8033;
 
 
 /**
- * @return {boolean} Whether the execution sequence has been started on this
- *     Deferred by invoking {@code callback} or {@code errback}.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.hasFired = function() {
-  return this.fired_;
-};
+goog.webgl.UNSIGNED_SHORT_5_5_5_1 = 0x8034;
 
 
 /**
- * @param {*} res The latest result in the execution sequence.
- * @return {boolean} Whether the current result is an error that should cause
- *     the next errback to fire. May be overridden by subclasses to handle
- *     special error types.
- * @protected
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.isError = function(res) {
-  return res instanceof Error;
-};
+goog.webgl.UNSIGNED_SHORT_5_6_5 = 0x8363;
 
 
 /**
- * @return {boolean} Whether an errback exists in the remaining sequence.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.hasErrback_ = function() {
-  return goog.array.some(this.sequence_, function(sequenceRow) {
-    // The errback is the second element in the array.
-    return goog.isFunction(sequenceRow[1]);
-  });
-};
+goog.webgl.FRAGMENT_SHADER = 0x8B30;
 
 
 /**
- * Exhausts the execution sequence while a result is available. The result may
- * be modified by callbacks or errbacks, and execution will block if the
- * returned result is an incomplete Deferred.
- *
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.prototype.fire_ = function() {
-  if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) {
-    // It is possible to add errbacks after the Deferred has fired. If a new
-    // errback is added immediately after the Deferred encountered an unhandled
-    // error, but before that error is rethrown, the error is unscheduled.
-    goog.async.Deferred.unscheduleError_(this.unhandledErrorId_);
-    this.unhandledErrorId_ = 0;
-  }
-
-  if (this.parent_) {
-    this.parent_.branches_--;
-    delete this.parent_;
-  }
+goog.webgl.VERTEX_SHADER = 0x8B31;
 
-  var res = this.result_;
-  var unhandledException = false;
-  var isNewlyBlocked = false;
 
-  while (this.sequence_.length && !this.blocked_) {
-    var sequenceEntry = this.sequence_.shift();
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VERTEX_ATTRIBS = 0x8869;
 
-    var callback = sequenceEntry[0];
-    var errback = sequenceEntry[1];
-    var scope = sequenceEntry[2];
 
-    var f = this.hadError_ ? errback : callback;
-    if (f) {
-      /** @preserveTry */
-      try {
-        var ret = f.call(scope || this.defaultScope_, res);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB;
 
-        // If no result, then use previous result.
-        if (goog.isDef(ret)) {
-          // Bubble up the error as long as the return value hasn't changed.
-          this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
-          this.result_ = res = ret;
-        }
 
-        if (goog.Thenable.isImplementedBy(res)) {
-          isNewlyBlocked = true;
-          this.blocked_ = true;
-        }
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VARYING_VECTORS = 0x8DFC;
 
-      } catch (ex) {
-        res = ex;
-        this.hadError_ = true;
-        this.makeStackTraceLong_(res);
 
-        if (!this.hasErrback_()) {
-          // If an error is thrown with no additional errbacks in the queue,
-          // prepare to rethrow the error.
-          unhandledException = true;
-        }
-      }
-    }
-  }
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D;
 
-  this.result_ = res;
 
-  if (isNewlyBlocked) {
-    var onCallback = goog.bind(this.continue_, this, true /* isSuccess */);
-    var onErrback = goog.bind(this.continue_, this, false /* isSuccess */);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C;
 
-    if (res instanceof goog.async.Deferred) {
-      res.addCallbacks(onCallback, onErrback);
-      res.blocking_ = true;
-    } else {
-      res.then(onCallback, onErrback);
-    }
-  } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
-      !(res instanceof goog.async.Deferred.CanceledError)) {
-    this.hadError_ = true;
-    unhandledException = true;
-  }
 
-  if (unhandledException) {
-    // Rethrow the unhandled error after a timeout. Execution will continue, but
-    // the error will be seen by global handlers and the user. The throw will
-    // be canceled if another errback is appended before the timeout executes.
-    // The error's original stack trace is preserved where available.
-    this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res);
-  }
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_TEXTURE_IMAGE_UNITS = 0x8872;
 
 
 /**
- * Creates a Deferred that has an initial result.
- *
- * @param {*=} opt_result The result.
- * @return {!goog.async.Deferred} The new Deferred.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.succeed = function(opt_result) {
-  var d = new goog.async.Deferred();
-  d.callback(opt_result);
-  return d;
-};
+goog.webgl.MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD;
 
 
 /**
- * Creates a Deferred that fires when the given promise resolves.
- * Use only during migration to Promises.
- *
- * @param {!goog.Promise.<T>} promise
- * @return {!goog.async.Deferred.<T>} The new Deferred.
- * @template T
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.fromPromise = function(promise) {
-  var d = new goog.async.Deferred();
-  d.callback();
-  d.addCallback(function() {
-    return promise;
-  });
-  return d;
-};
+goog.webgl.SHADER_TYPE = 0x8B4F;
 
 
 /**
- * Creates a Deferred that has an initial error result.
- *
- * @param {*} res The error result.
- * @return {!goog.async.Deferred} The new Deferred.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.fail = function(res) {
-  var d = new goog.async.Deferred();
-  d.errback(res);
-  return d;
-};
+goog.webgl.DELETE_STATUS = 0x8B80;
 
 
 /**
- * Creates a Deferred that has already been canceled.
- *
- * @return {!goog.async.Deferred} The new Deferred.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.canceled = function() {
-  var d = new goog.async.Deferred();
-  d.cancel();
-  return d;
-};
+goog.webgl.LINK_STATUS = 0x8B82;
 
 
 /**
- * Normalizes values that may or may not be Deferreds.
- *
- * If the input value is a Deferred, the Deferred is branched (so the original
- * execution sequence is not modified) and the input callback added to the new
- * branch. The branch is returned to the caller.
- *
- * If the input value is not a Deferred, the callback will be executed
- * immediately and an already firing Deferred will be returned to the caller.
- *
- * In the following (contrived) example, if <code>isImmediate</code> is true
- * then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
- *
- * <pre>
- * var value;
- * if (isImmediate) {
- *   value = 3;
- * } else {
- *   value = new goog.async.Deferred();
- *   setTimeout(function() { value.callback(6); }, 2000);
- * }
- *
- * var d = goog.async.Deferred.when(value, alert);
- * </pre>
- *
- * @param {*} value Deferred or normal value to pass to the callback.
- * @param {!function(this:T, ?):?} callback The callback to execute.
- * @param {T=} opt_scope An optional scope to call the callback in.
- * @return {!goog.async.Deferred} A new Deferred that will call the input
- *     callback with the input value.
- * @template T
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.when = function(value, callback, opt_scope) {
-  if (value instanceof goog.async.Deferred) {
-    return value.branch(true).addCallback(callback, opt_scope);
-  } else {
-    return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
-  }
-};
+goog.webgl.VALIDATE_STATUS = 0x8B83;
+
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ATTACHED_SHADERS = 0x8B85;
 
 
 /**
- * An error sub class that is used when a Deferred has already been called.
- * @param {!goog.async.Deferred} deferred The Deferred.
- *
- * @constructor
- * @extends {goog.debug.Error}
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.AlreadyCalledError = function(deferred) {
-  goog.debug.Error.call(this);
+goog.webgl.ACTIVE_UNIFORMS = 0x8B86;
 
-  /**
-   * The Deferred that raised this error.
-   * @type {goog.async.Deferred}
-   */
-  this.deferred = deferred;
-};
-goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ACTIVE_ATTRIBUTES = 0x8B89;
 
-/** @override */
-goog.async.Deferred.AlreadyCalledError.prototype.message =
-    'Deferred has already fired';
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SHADING_LANGUAGE_VERSION = 0x8B8C;
 
-/** @override */
-goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CURRENT_PROGRAM = 0x8B8D;
 
 
 /**
- * An error sub class that is used when a Deferred is canceled.
- *
- * @param {!goog.async.Deferred} deferred The Deferred object.
- * @constructor
- * @extends {goog.debug.Error}
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.CanceledError = function(deferred) {
-  goog.debug.Error.call(this);
+goog.webgl.NEVER = 0x0200;
 
-  /**
-   * The Deferred that raised this error.
-   * @type {goog.async.Deferred}
-   */
-  this.deferred = deferred;
-};
-goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LESS = 0x0201;
 
-/** @override */
-goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.EQUAL = 0x0202;
 
-/** @override */
-goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LEQUAL = 0x0203;
 
 
 /**
- * Wrapper around errors that are scheduled to be thrown by failing deferreds
- * after a timeout.
- *
- * @param {*} error Error from a failing deferred.
- * @constructor
- * @final
- * @private
- * @struct
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.Error_ = function(error) {
-  /** @const @private {number} */
-  this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0);
+goog.webgl.GREATER = 0x0204;
 
-  /** @const @private {*} */
-  this.error_ = error;
-};
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NOTEQUAL = 0x0205;
 
 
 /**
- * Actually throws the error and removes it from the list of pending
- * deferred errors.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.Error_.prototype.throwError = function() {
-  goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_],
-      'Cannot throw an error that is not scheduled.');
-  delete goog.async.Deferred.errorMap_[this.id_];
-  throw this.error_;
-};
+goog.webgl.GEQUAL = 0x0206;
 
 
 /**
- * Resets the error throw timer.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.Error_.prototype.resetTimer = function() {
-  goog.global.clearTimeout(this.id_);
-};
+goog.webgl.ALWAYS = 0x0207;
 
 
 /**
- * Map of unhandled errors scheduled to be rethrown in a future timestep.
- * @private {!Object.<number|string, goog.async.Deferred.Error_>}
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.errorMap_ = {};
+goog.webgl.KEEP = 0x1E00;
 
 
 /**
- * Schedules an error to be thrown after a delay.
- * @param {*} error Error from a failing deferred.
- * @return {number} Id of the error.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.scheduleError_ = function(error) {
-  var deferredError = new goog.async.Deferred.Error_(error);
-  goog.async.Deferred.errorMap_[deferredError.id_] = deferredError;
-  return deferredError.id_;
-};
+goog.webgl.REPLACE = 0x1E01;
 
 
 /**
- * Unschedules an error from being thrown.
- * @param {number} id Id of the deferred error to unschedule.
- * @private
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.unscheduleError_ = function(id) {
-  var error = goog.async.Deferred.errorMap_[id];
-  if (error) {
-    error.resetTimer();
-    delete goog.async.Deferred.errorMap_[id];
-  }
-};
+goog.webgl.INCR = 0x1E02;
 
 
 /**
- * Asserts that there are no pending deferred errors. If there are any
- * scheduled errors, one will be thrown immediately to make this function fail.
+ * @const
+ * @type {number}
  */
-goog.async.Deferred.assertNoErrors = function() {
-  var map = goog.async.Deferred.errorMap_;
-  for (var key in map) {
-    var error = map[key];
-    error.resetTimer();
-    error.throwError();
-  }
-};
+goog.webgl.DECR = 0x1E03;
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview A wrapper for the HTML5 FileError object.
- *
+ * @const
+ * @type {number}
  */
+goog.webgl.INVERT = 0x150A;
 
-goog.provide('goog.fs.Error');
-goog.provide('goog.fs.Error.ErrorCode');
 
-goog.require('goog.debug.Error');
-goog.require('goog.object');
-goog.require('goog.string');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INCR_WRAP = 0x8507;
 
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DECR_WRAP = 0x8508;
+
 
 /**
- * A filesystem error. Since the filesystem API is asynchronous, stack traces
- * are less useful for identifying where errors come from, so this includes a
- * large amount of metadata in the message.
- *
- * @param {!DOMError} error
- * @param {string} action The action being undertaken when the error was raised.
- * @constructor
- * @extends {goog.debug.Error}
- * @final
+ * @const
+ * @type {number}
  */
-goog.fs.Error = function(error, action) {
-  /** @type {string} */
-  this.name;
+goog.webgl.VENDOR = 0x1F00;
 
-  /**
-   * @type {goog.fs.Error.ErrorCode}
-   * @deprecated Use the 'name' or 'message' field instead.
-   */
-  this.code;
 
-  if (goog.isDef(error.name)) {
-    this.name = error.name;
-    // TODO(user): Remove warning suppression after JSCompiler stops
-    // firing a spurious warning here.
-    /** @suppress {deprecated} */
-    this.code = goog.fs.Error.getCodeFromName_(error.name);
-  } else {
-    this.code = error.code;
-    this.name = goog.fs.Error.getNameFromCode_(error.code);
-  }
-  goog.fs.Error.base(this, 'constructor',
-      goog.string.subs('%s %s', this.name, action));
-};
-goog.inherits(goog.fs.Error, goog.debug.Error);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERER = 0x1F01;
 
 
 /**
- * Names of errors that may be thrown by the File API, the File System API, or
- * the File Writer API.
- *
- * @see http://dev.w3.org/2006/webapi/FileAPI/#ErrorAndException
- * @see http://www.w3.org/TR/file-system-api/#definitions
- * @see http://dev.w3.org/2009/dap/file-system/file-writer.html#definitions
- * @enum {string}
+ * @const
+ * @type {number}
  */
-goog.fs.Error.ErrorName = {
-  ABORT: 'AbortError',
-  ENCODING: 'EncodingError',
-  INVALID_MODIFICATION: 'InvalidModificationError',
-  INVALID_STATE: 'InvalidStateError',
-  NOT_FOUND: 'NotFoundError',
-  NOT_READABLE: 'NotReadableError',
-  NO_MODIFICATION_ALLOWED: 'NoModificationAllowedError',
-  PATH_EXISTS: 'PathExistsError',
-  QUOTA_EXCEEDED: 'QuotaExceededError',
-  SECURITY: 'SecurityError',
-  SYNTAX: 'SyntaxError',
-  TYPE_MISMATCH: 'TypeMismatchError'
-};
+goog.webgl.VERSION = 0x1F02;
 
 
 /**
- * Error codes for file errors.
- * @see http://www.w3.org/TR/file-system-api/#idl-def-FileException
- *
- * @enum {number}
- * @deprecated Use the 'name' or 'message' attribute instead.
+ * @const
+ * @type {number}
  */
-goog.fs.Error.ErrorCode = {
-  NOT_FOUND: 1,
-  SECURITY: 2,
-  ABORT: 3,
-  NOT_READABLE: 4,
-  ENCODING: 5,
-  NO_MODIFICATION_ALLOWED: 6,
-  INVALID_STATE: 7,
-  SYNTAX: 8,
-  INVALID_MODIFICATION: 9,
-  QUOTA_EXCEEDED: 10,
-  TYPE_MISMATCH: 11,
-  PATH_EXISTS: 12
-};
+goog.webgl.NEAREST = 0x2600;
 
 
 /**
- * @param {goog.fs.Error.ErrorCode} code
- * @return {string} name
- * @private
+ * @const
+ * @type {number}
  */
-goog.fs.Error.getNameFromCode_ = function(code) {
-  var name = goog.object.findKey(goog.fs.Error.NameToCodeMap_, function(c) {
-    return code == c;
-  });
-  if (!goog.isDef(name)) {
-    throw new Error('Invalid code: ' + code);
-  }
-  return name;
-};
+goog.webgl.LINEAR = 0x2601;
 
 
 /**
- * Returns the code that corresponds to the given name.
- * @param {string} name
- * @return {goog.fs.Error.ErrorCode} code
- * @private
+ * @const
+ * @type {number}
  */
-goog.fs.Error.getCodeFromName_ = function(name) {
-  return goog.fs.Error.NameToCodeMap_[name];
-};
+goog.webgl.NEAREST_MIPMAP_NEAREST = 0x2700;
 
 
 /**
- * Mapping from error names to values from the ErrorCode enum.
- * @see http://www.w3.org/TR/file-system-api/#definitions.
- * @private {!Object.<string, goog.fs.Error.ErrorCode>}
+ * @const
+ * @type {number}
  */
-goog.fs.Error.NameToCodeMap_ = goog.object.create(
-    goog.fs.Error.ErrorName.ABORT,
-    goog.fs.Error.ErrorCode.ABORT,
+goog.webgl.LINEAR_MIPMAP_NEAREST = 0x2701;
 
-    goog.fs.Error.ErrorName.ENCODING,
-    goog.fs.Error.ErrorCode.ENCODING,
 
-    goog.fs.Error.ErrorName.INVALID_MODIFICATION,
-    goog.fs.Error.ErrorCode.INVALID_MODIFICATION,
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.NEAREST_MIPMAP_LINEAR = 0x2702;
 
-    goog.fs.Error.ErrorName.INVALID_STATE,
-    goog.fs.Error.ErrorCode.INVALID_STATE,
 
-    goog.fs.Error.ErrorName.NOT_FOUND,
-    goog.fs.Error.ErrorCode.NOT_FOUND,
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.LINEAR_MIPMAP_LINEAR = 0x2703;
 
-    goog.fs.Error.ErrorName.NOT_READABLE,
-    goog.fs.Error.ErrorCode.NOT_READABLE,
 
-    goog.fs.Error.ErrorName.NO_MODIFICATION_ALLOWED,
-    goog.fs.Error.ErrorCode.NO_MODIFICATION_ALLOWED,
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_MAG_FILTER = 0x2800;
 
-    goog.fs.Error.ErrorName.PATH_EXISTS,
-    goog.fs.Error.ErrorCode.PATH_EXISTS,
 
-    goog.fs.Error.ErrorName.QUOTA_EXCEEDED,
-    goog.fs.Error.ErrorCode.QUOTA_EXCEEDED,
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_MIN_FILTER = 0x2801;
 
-    goog.fs.Error.ErrorName.SECURITY,
-    goog.fs.Error.ErrorCode.SECURITY,
 
-    goog.fs.Error.ErrorName.SYNTAX,
-    goog.fs.Error.ErrorCode.SYNTAX,
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_WRAP_S = 0x2802;
 
-    goog.fs.Error.ErrorName.TYPE_MISMATCH,
-    goog.fs.Error.ErrorCode.TYPE_MISMATCH);
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_WRAP_T = 0x2803;
+
 
 /**
- * @fileoverview A wrapper for the HTML5 File ProgressEvent objects.
- *
+ * @const
+ * @type {number}
  */
-goog.provide('goog.fs.ProgressEvent');
+goog.webgl.TEXTURE_2D = 0x0DE1;
 
-goog.require('goog.events.Event');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE = 0x1702;
 
 
 /**
- * A wrapper for the progress events emitted by the File APIs.
- *
- * @param {!ProgressEvent} event The underlying event object.
- * @param {!Object} target The file access object emitting the event.
- * @extends {goog.events.Event}
- * @constructor
- * @final
+ * @const
+ * @type {number}
  */
-goog.fs.ProgressEvent = function(event, target) {
-  goog.fs.ProgressEvent.base(this, 'constructor', event.type, target);
+goog.webgl.TEXTURE_CUBE_MAP = 0x8513;
 
-  /**
-   * The underlying event object.
-   * @type {!ProgressEvent}
-   * @private
-   */
-  this.event_ = event;
-};
-goog.inherits(goog.fs.ProgressEvent, goog.events.Event);
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_BINDING_CUBE_MAP = 0x8514;
 
 
 /**
- * @return {boolean} Whether or not the total size of the of the file being
- *     saved is known.
+ * @const
+ * @type {number}
  */
-goog.fs.ProgressEvent.prototype.isLengthComputable = function() {
-  return this.event_.lengthComputable;
-};
+goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
 
 
 /**
- * @return {number} The number of bytes saved so far.
+ * @const
+ * @type {number}
  */
-goog.fs.ProgressEvent.prototype.getLoaded = function() {
-  return this.event_.loaded;
-};
+goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
 
 
 /**
- * @return {number} The total number of bytes in the file being saved.
+ * @const
+ * @type {number}
  */
-goog.fs.ProgressEvent.prototype.getTotal = function() {
-  return this.event_.total;
-};
+goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview A wrapper for the HTML5 FileReader object.
- *
+ * @const
+ * @type {number}
  */
+goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
 
-goog.provide('goog.fs.FileReader');
-goog.provide('goog.fs.FileReader.EventType');
-goog.provide('goog.fs.FileReader.ReadyState');
 
-goog.require('goog.async.Deferred');
-goog.require('goog.events.EventTarget');
-goog.require('goog.fs.Error');
-goog.require('goog.fs.ProgressEvent');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
 
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
+
 
 /**
- * An object for monitoring the reading of files. This emits ProgressEvents of
- * the types listed in {@link goog.fs.FileReader.EventType}.
- *
- * @constructor
- * @extends {goog.events.EventTarget}
- * @final
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader = function() {
-  goog.fs.FileReader.base(this, 'constructor');
+goog.webgl.MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C;
 
-  /**
-   * The underlying FileReader object.
-   *
-   * @type {!FileReader}
-   * @private
-   */
-  this.reader_ = new FileReader();
 
-  this.reader_.onloadstart = goog.bind(this.dispatchProgressEvent_, this);
-  this.reader_.onprogress = goog.bind(this.dispatchProgressEvent_, this);
-  this.reader_.onload = goog.bind(this.dispatchProgressEvent_, this);
-  this.reader_.onabort = goog.bind(this.dispatchProgressEvent_, this);
-  this.reader_.onerror = goog.bind(this.dispatchProgressEvent_, this);
-  this.reader_.onloadend = goog.bind(this.dispatchProgressEvent_, this);
-};
-goog.inherits(goog.fs.FileReader, goog.events.EventTarget);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE0 = 0x84C0;
 
 
 /**
- * Possible states for a FileReader.
- *
- * @enum {number}
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.ReadyState = {
-  /**
-   * The object has been constructed, but there is no pending read.
-   */
-  INIT: 0,
-  /**
-   * Data is being read.
-   */
-  LOADING: 1,
-  /**
-   * The data has been read from the file, the read was aborted, or an error
-   * occurred.
-   */
-  DONE: 2
-};
+goog.webgl.TEXTURE1 = 0x84C1;
 
 
 /**
- * Events emitted by a FileReader.
- *
- * @enum {string}
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.EventType = {
-  /**
-   * Emitted when the reading begins. readyState will be LOADING.
-   */
-  LOAD_START: 'loadstart',
-  /**
-   * Emitted when progress has been made in reading the file. readyState will be
-   * LOADING.
-   */
-  PROGRESS: 'progress',
-  /**
-   * Emitted when the data has been successfully read. readyState will be
-   * LOADING.
-   */
-  LOAD: 'load',
-  /**
-   * Emitted when the reading has been aborted. readyState will be LOADING.
-   */
-  ABORT: 'abort',
-  /**
-   * Emitted when an error is encountered or the reading has been aborted.
-   * readyState will be LOADING.
-   */
-  ERROR: 'error',
-  /**
-   * Emitted when the reading is finished, whether successfully or not.
-   * readyState will be DONE.
-   */
-  LOAD_END: 'loadend'
-};
+goog.webgl.TEXTURE2 = 0x84C2;
 
 
 /**
- * Abort the reading of the file.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.abort = function() {
-  try {
-    this.reader_.abort();
-  } catch (e) {
-    throw new goog.fs.Error(e, 'aborting read');
-  }
-};
+goog.webgl.TEXTURE3 = 0x84C3;
 
 
 /**
- * @return {goog.fs.FileReader.ReadyState} The current state of the FileReader.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.getReadyState = function() {
-  return /** @type {goog.fs.FileReader.ReadyState} */ (this.reader_.readyState);
-};
+goog.webgl.TEXTURE4 = 0x84C4;
 
 
 /**
- * @return {*} The result of the file read.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.getResult = function() {
-  return this.reader_.result;
-};
+goog.webgl.TEXTURE5 = 0x84C5;
 
 
 /**
- * @return {goog.fs.Error} The error encountered while reading, if any.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.getError = function() {
-  return this.reader_.error &&
-      new goog.fs.Error(this.reader_.error, 'reading file');
-};
+goog.webgl.TEXTURE6 = 0x84C6;
 
 
 /**
- * Wrap a progress event emitted by the underlying file reader and re-emit it.
- *
- * @param {!ProgressEvent} event The underlying event.
- * @private
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.dispatchProgressEvent_ = function(event) {
-  this.dispatchEvent(new goog.fs.ProgressEvent(event, this));
-};
+goog.webgl.TEXTURE7 = 0x84C7;
 
 
-/** @override */
-goog.fs.FileReader.prototype.disposeInternal = function() {
-  goog.fs.FileReader.base(this, 'disposeInternal');
-  delete this.reader_;
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE8 = 0x84C8;
 
 
 /**
- * Starts reading a blob as a binary string.
- * @param {!Blob} blob The blob to read.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.readAsBinaryString = function(blob) {
-  this.reader_.readAsBinaryString(blob);
-};
+goog.webgl.TEXTURE9 = 0x84C9;
 
 
 /**
- * Reads a blob as a binary string.
- * @param {!Blob} blob The blob to read.
- * @return {!goog.async.Deferred} The deferred Blob contents as a binary string.
- *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.readAsBinaryString = function(blob) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsBinaryString(blob);
-  return d;
-};
+goog.webgl.TEXTURE10 = 0x84CA;
 
 
 /**
- * Starts reading a blob as an array buffer.
- * @param {!Blob} blob The blob to read.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.readAsArrayBuffer = function(blob) {
-  this.reader_.readAsArrayBuffer(blob);
-};
+goog.webgl.TEXTURE11 = 0x84CB;
 
 
 /**
- * Reads a blob as an array buffer.
- * @param {!Blob} blob The blob to read.
- * @return {!goog.async.Deferred} The deferred Blob contents as an array buffer.
- *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.readAsArrayBuffer = function(blob) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsArrayBuffer(blob);
-  return d;
-};
+goog.webgl.TEXTURE12 = 0x84CC;
 
 
 /**
- * Starts reading a blob as text.
- * @param {!Blob} blob The blob to read.
- * @param {string=} opt_encoding The name of the encoding to use.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.readAsText = function(blob, opt_encoding) {
-  this.reader_.readAsText(blob, opt_encoding);
-};
+goog.webgl.TEXTURE13 = 0x84CD;
 
 
 /**
- * Reads a blob as text.
- * @param {!Blob} blob The blob to read.
- * @param {string=} opt_encoding The name of the encoding to use.
- * @return {!goog.async.Deferred} The deferred Blob contents as text.
- *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.readAsText = function(blob, opt_encoding) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsText(blob, opt_encoding);
-  return d;
-};
+goog.webgl.TEXTURE14 = 0x84CE;
 
 
 /**
- * Starts reading a blob as a data URL.
- * @param {!Blob} blob The blob to read.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.prototype.readAsDataUrl = function(blob) {
-  this.reader_.readAsDataURL(blob);
-};
+goog.webgl.TEXTURE15 = 0x84CF;
 
 
 /**
- * Reads a blob as a data URL.
- * @param {!Blob} blob The blob to read.
- * @return {!goog.async.Deferred} The deferred Blob contents as a data URL.
- *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.readAsDataUrl = function(blob) {
-  var reader = new goog.fs.FileReader();
-  var d = goog.fs.FileReader.createDeferred_(reader);
-  reader.readAsDataUrl(blob);
-  return d;
-};
+goog.webgl.TEXTURE16 = 0x84D0;
 
 
 /**
- * Creates a new deferred object for the results of a read method.
- * @param {goog.fs.FileReader} reader The reader to create a deferred for.
- * @return {!goog.async.Deferred} The deferred results.
- * @private
+ * @const
+ * @type {number}
  */
-goog.fs.FileReader.createDeferred_ = function(reader) {
-  var deferred = new goog.async.Deferred();
-  reader.listen(goog.fs.FileReader.EventType.LOAD_END,
-      goog.partial(function(d, r, e) {
-        var result = r.getResult();
-        var error = r.getError();
-        if (result != null && !error) {
-          d.callback(result);
-        } else {
-          d.errback(error);
-        }
-        r.dispose();
-      }, deferred, reader));
-  return deferred;
-};
+goog.webgl.TEXTURE17 = 0x84D1;
 
-// FIXME should handle all geo-referenced data, not just vector data
 
-goog.provide('ol.interaction.DragAndDrop');
-goog.provide('ol.interaction.DragAndDropEvent');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE18 = 0x84D2;
 
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.events.FileDropHandler');
-goog.require('goog.events.FileDropHandler.EventType');
-goog.require('goog.fs.FileReader');
-goog.require('goog.functions');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.proj');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE19 = 0x84D3;
 
 
 /**
- * @classdesc
- * Handles input of vector data by drag and drop.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @fires ol.interaction.DragAndDropEvent
- * @param {olx.interaction.DragAndDropOptions=} opt_options Options.
- * @api stable
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDrop = function(opt_options) {
+goog.webgl.TEXTURE20 = 0x84D4;
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  goog.base(this);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE21 = 0x84D5;
 
-  /**
-   * @private
-   * @type {Array.<function(new: ol.format.Feature)>}
-   */
-  this.formatConstructors_ = goog.isDef(options.formatConstructors) ?
-      options.formatConstructors : [];
 
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.reprojectTo_ = goog.isDef(options.reprojectTo) ?
-      ol.proj.get(options.reprojectTo) : null;
-
-  /**
-   * @private
-   * @type {goog.events.FileDropHandler}
-   */
-  this.fileDropHandler_ = null;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE22 = 0x84D6;
 
-  /**
-   * @private
-   * @type {goog.events.Key|undefined}
-   */
-  this.dropListenKey_ = undefined;
 
-};
-goog.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.TEXTURE23 = 0x84D7;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDrop.prototype.disposeInternal = function() {
-  if (goog.isDef(this.dropListenKey_)) {
-    goog.events.unlistenByKey(this.dropListenKey_);
-  }
-  goog.base(this, 'disposeInternal');
-};
+goog.webgl.TEXTURE24 = 0x84D8;
 
 
 /**
- * @param {goog.events.BrowserEvent} event Event.
- * @private
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDrop.prototype.handleDrop_ = function(event) {
-  var files = event.getBrowserEvent().dataTransfer.files;
-  var i, ii, file;
-  for (i = 0, ii = files.length; i < ii; ++i) {
-    file = files[i];
-    // The empty string param is a workaround for
-    // https://code.google.com/p/closure-library/issues/detail?id=524
-    var reader = goog.fs.FileReader.readAsText(file, '');
-    reader.addCallback(goog.partial(this.handleResult_, file), this);
-  }
-};
+goog.webgl.TEXTURE25 = 0x84D9;
 
 
 /**
- * @param {File} file File.
- * @param {string} result Result.
- * @private
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, result) {
-  var map = this.getMap();
-  goog.asserts.assert(!goog.isNull(map));
-  var projection = this.reprojectTo_;
-  if (goog.isNull(projection)) {
-    var view = map.getView();
-    goog.asserts.assert(goog.isDef(view));
-    projection = view.getProjection();
-    goog.asserts.assert(goog.isDef(projection));
-  }
-  var formatConstructors = this.formatConstructors_;
-  var features = [];
-  var i, ii;
-  for (i = 0, ii = formatConstructors.length; i < ii; ++i) {
-    var formatConstructor = formatConstructors[i];
-    var format = new formatConstructor();
-    var readFeatures = this.tryReadFeatures_(format, result);
-    if (!goog.isNull(readFeatures)) {
-      var featureProjection = format.readProjection(result);
-      var transform = ol.proj.getTransform(featureProjection, projection);
-      var j, jj;
-      for (j = 0, jj = readFeatures.length; j < jj; ++j) {
-        var feature = readFeatures[j];
-        var geometry = feature.getGeometry();
-        if (goog.isDefAndNotNull(geometry)) {
-          geometry.applyTransform(transform);
-        }
-        features.push(feature);
-      }
-    }
-  }
-  this.dispatchEvent(
-      new ol.interaction.DragAndDropEvent(
-          ol.interaction.DragAndDropEventType.ADD_FEATURES, this, file,
-          features, projection));
-};
+goog.webgl.TEXTURE26 = 0x84DA;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDrop.prototype.handleMapBrowserEvent =
-    goog.functions.TRUE;
+goog.webgl.TEXTURE27 = 0x84DB;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDrop.prototype.setMap = function(map) {
-  if (goog.isDef(this.dropListenKey_)) {
-    goog.events.unlistenByKey(this.dropListenKey_);
-    this.dropListenKey_ = undefined;
-  }
-  if (!goog.isNull(this.fileDropHandler_)) {
-    goog.dispose(this.fileDropHandler_);
-    this.fileDropHandler_ = null;
-  }
-  goog.asserts.assert(!goog.isDef(this.dropListenKey_));
-  goog.base(this, 'setMap', map);
-  if (!goog.isNull(map)) {
-    this.fileDropHandler_ = new goog.events.FileDropHandler(map.getViewport());
-    this.dropListenKey_ = goog.events.listen(
-        this.fileDropHandler_, goog.events.FileDropHandler.EventType.DROP,
-        this.handleDrop_, false, this);
-  }
-};
+goog.webgl.TEXTURE28 = 0x84DC;
 
 
 /**
- * @param {ol.format.Feature} format Format.
- * @param {string} text Text.
- * @private
- * @return {Array.<ol.Feature>} Features.
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text) {
-  try {
-    return format.readFeatures(text);
-  } catch (e) {
-    return null;
-  }
-};
+goog.webgl.TEXTURE29 = 0x84DD;
 
 
 /**
- * @enum {string}
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDropEventType = {
-  /**
-   * Triggered when features are added
-   * @event ol.interaction.DragAndDropEvent#addfeatures
-   * @api stable
-   */
-  ADD_FEATURES: 'addfeatures'
-};
-
+goog.webgl.TEXTURE30 = 0x84DE;
 
 
 /**
- * @classdesc
- * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances
- * of this type.
- *
- * @constructor
- * @extends {goog.events.Event}
- * @implements {oli.interaction.DragAndDropEvent}
- * @param {ol.interaction.DragAndDropEventType} type Type.
- * @param {Object} target Target.
- * @param {File} file File.
- * @param {Array.<ol.Feature>=} opt_features Features.
- * @param {ol.proj.Projection=} opt_projection Projection.
+ * @const
+ * @type {number}
  */
-ol.interaction.DragAndDropEvent =
-    function(type, target, file, opt_features, opt_projection) {
+goog.webgl.TEXTURE31 = 0x84DF;
 
-  goog.base(this, type, target);
 
-  /**
-   * @type {Array.<ol.Feature>|undefined}
-   * @api stable
-   */
-  this.features = opt_features;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.ACTIVE_TEXTURE = 0x84E0;
 
-  /**
-   * @type {File}
-   * @api stable
-   */
-  this.file = file;
 
-  /**
-   * @type {ol.proj.Projection|undefined}
-   * @api
-   */
-  this.projection = opt_projection;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.REPEAT = 0x2901;
 
-};
-goog.inherits(ol.interaction.DragAndDropEvent, goog.events.Event);
 
-goog.provide('ol.interaction.Pointer');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.CLAMP_TO_EDGE = 0x812F;
 
-goog.require('goog.asserts');
-goog.require('goog.functions');
-goog.require('goog.object');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserPointerEvent');
-goog.require('ol.Pixel');
-goog.require('ol.interaction.Interaction');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MIRRORED_REPEAT = 0x8370;
 
 
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer = function() {
-
-  goog.base(this);
+goog.webgl.FLOAT_VEC2 = 0x8B50;
 
-  /**
-   * @type {boolean}
-   * @protected
-   */
-  this.handlingDownUpSequence = false;
 
-  /**
-   * @type {Object.<number, ol.pointer.PointerEvent>}
-   * @private
-   */
-  this.trackedPointers_ = {};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_VEC3 = 0x8B51;
 
-  /**
-   * @type {Array.<ol.pointer.PointerEvent>}
-   * @protected
-   */
-  this.targetPointers = [];
 
-};
-goog.inherits(ol.interaction.Pointer, ol.interaction.Interaction);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_VEC4 = 0x8B52;
 
 
 /**
- * @param {Array.<ol.pointer.PointerEvent>} pointerEvents
- * @return {ol.Pixel} Centroid pixel.
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer.centroid = function(pointerEvents) {
-  var length = pointerEvents.length;
-  var clientX = 0;
-  var clientY = 0;
-  for (var i = 0; i < length; i++) {
-    clientX += pointerEvents[i].clientX;
-    clientY += pointerEvents[i].clientY;
-  }
-  return [clientX / length, clientY / length];
-};
+goog.webgl.INT_VEC2 = 0x8B53;
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Whether the event is a pointerdown, pointerdrag
- *     or pointerup event.
- * @private
+ * @const
+ * @type {number}
  */
-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);
-};
+goog.webgl.INT_VEC3 = 0x8B54;
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @private
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer.prototype.updateTrackedPointers_ =
-    function(mapBrowserEvent) {
-  if (this.isPointerDraggingEvent_(mapBrowserEvent)) {
-    var event = mapBrowserEvent.pointerEvent;
-
-    if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) {
-      delete this.trackedPointers_[event.pointerId];
-    } else if (mapBrowserEvent.type ==
-        ol.MapBrowserEvent.EventType.POINTERDOWN) {
-      this.trackedPointers_[event.pointerId] = event;
-    } else if (event.pointerId in this.trackedPointers_) {
-      // update only when there was a pointerdown event for this pointer
-      this.trackedPointers_[event.pointerId] = event;
-    }
-    this.targetPointers = goog.object.getValues(this.trackedPointers_);
-  }
-};
+goog.webgl.INT_VEC4 = 0x8B55;
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @protected
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer.prototype.handlePointerDrag = goog.nullFunction;
+goog.webgl.BOOL = 0x8B56;
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @protected
- * @return {boolean} Capture dragging.
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer.prototype.handlePointerUp = goog.functions.FALSE;
+goog.webgl.BOOL_VEC2 = 0x8B57;
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @protected
- * @return {boolean} Capture dragging.
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer.prototype.handlePointerDown = goog.functions.FALSE;
+goog.webgl.BOOL_VEC3 = 0x8B58;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer.prototype.handleMapBrowserEvent =
-    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.handlePointerDrag(mapBrowserEvent);
-    } else if (mapBrowserEvent.type ==
-        ol.MapBrowserEvent.EventType.POINTERUP) {
-      this.handlingDownUpSequence =
-          this.handlePointerUp(mapBrowserEvent);
-    }
-  }
-  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
-    var handled = this.handlePointerDown(mapBrowserEvent);
-    this.handlingDownUpSequence = handled;
-    stopEvent = this.shouldStopEvent(handled);
-  }
-  return !stopEvent;
-};
+goog.webgl.BOOL_VEC4 = 0x8B59;
 
 
 /**
- * This method allows inheriting classes to stop the event from being
- * passed to further interactions. For example, this is required for
- * interaction `DragRotateAndZoom`.
- *
- * @protected
- * @param {boolean} handled Was the event handled by the interaction?
- * @return {boolean} Should the event be stopped?
+ * @const
+ * @type {number}
  */
-ol.interaction.Pointer.prototype.shouldStopEvent = goog.functions.FALSE;
+goog.webgl.FLOAT_MAT2 = 0x8B5A;
 
-// FIXME add rotation
 
-goog.provide('ol.render.Box');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_MAT3 = 0x8B5B;
 
-goog.require('goog.Disposable');
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('ol.geom.Polygon');
-goog.require('ol.render.EventType');
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FLOAT_MAT4 = 0x8B5C;
 
 
 /**
- * @constructor
- * @extends {goog.Disposable}
- * @param {ol.style.Style} style Style.
+ * @const
+ * @type {number}
  */
-ol.render.Box = function(style) {
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = null;
+goog.webgl.SAMPLER_2D = 0x8B5E;
 
-  /**
-   * @private
-   * @type {goog.events.Key}
-   */
-  this.postComposeListenerKey_ = null;
 
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.startPixel_ = null;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.SAMPLER_CUBE = 0x8B60;
 
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.endPixel_ = null;
 
-  /**
-   * @private
-   * @type {ol.geom.Polygon}
-   */
-  this.geometry_ = null;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622;
 
-  /**
-   * @private
-   * @type {ol.style.Style}
-   */
-  this.style_ = style;
 
-};
-goog.inherits(ol.render.Box, goog.Disposable);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ATTRIB_ARRAY_SIZE = 0x8623;
 
 
 /**
- * @private
- * @return {ol.geom.Polygon} Geometry.
+ * @const
+ * @type {number}
  */
-ol.render.Box.prototype.createGeometry_ = function() {
-  goog.asserts.assert(!goog.isNull(this.startPixel_));
-  goog.asserts.assert(!goog.isNull(this.endPixel_));
-  goog.asserts.assert(!goog.isNull(this.map_));
-  var startPixel = this.startPixel_;
-  var endPixel = this.endPixel_;
-  var pixels = [
-    startPixel,
-    [startPixel[0], endPixel[1]],
-    endPixel,
-    [endPixel[0], startPixel[1]]
-  ];
-  var coordinates = goog.array.map(pixels,
-      this.map_.getCoordinateFromPixel, this.map_);
-  // close the polygon
-  coordinates[4] = coordinates[0].slice();
-  return new ol.geom.Polygon([coordinates]);
-};
+goog.webgl.VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.render.Box.prototype.disposeInternal = function() {
-  this.setMap(null);
-};
+goog.webgl.VERTEX_ATTRIB_ARRAY_TYPE = 0x8625;
 
 
 /**
- * @param {ol.render.Event} event Event.
- * @private
+ * @const
+ * @type {number}
  */
-ol.render.Box.prototype.handleMapPostCompose_ = function(event) {
-  var geometry = this.geometry_;
-  goog.asserts.assert(goog.isDefAndNotNull(geometry));
-  var style = this.style_;
-  goog.asserts.assert(!goog.isNull(style));
-  // use drawAsync(Infinity) to draw above everything
-  event.vectorContext.drawAsync(Infinity, function(render) {
-    render.setFillStrokeStyle(style.getFill(), style.getStroke());
-    render.setTextStyle(style.getText());
-    render.drawPolygonGeometry(geometry, null);
-  });
-};
+goog.webgl.VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A;
 
 
 /**
- * @return {ol.geom.Polygon} Geometry.
+ * @const
+ * @type {number}
  */
-ol.render.Box.prototype.getGeometry = function() {
-  return this.geometry_;
-};
+goog.webgl.VERTEX_ATTRIB_ARRAY_POINTER = 0x8645;
 
 
 /**
- * @private
+ * @const
+ * @type {number}
  */
-ol.render.Box.prototype.requestMapRenderFrame_ = function() {
-  if (!goog.isNull(this.map_) &&
-      !goog.isNull(this.startPixel_) &&
-      !goog.isNull(this.endPixel_)) {
-    this.map_.render();
-  }
-};
+goog.webgl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F;
 
 
 /**
- * @param {ol.Map} map Map.
+ * @const
+ * @type {number}
  */
-ol.render.Box.prototype.setMap = function(map) {
-  if (!goog.isNull(this.postComposeListenerKey_)) {
-    goog.events.unlistenByKey(this.postComposeListenerKey_);
-    this.postComposeListenerKey_ = null;
-    this.map_.render();
-    this.map_ = null;
-  }
-  this.map_ = map;
-  if (!goog.isNull(this.map_)) {
-    this.postComposeListenerKey_ = goog.events.listen(
-        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
-        this);
-    this.requestMapRenderFrame_();
-  }
-};
+goog.webgl.COMPILE_STATUS = 0x8B81;
 
 
 /**
- * @param {ol.Pixel} startPixel Start pixel.
- * @param {ol.Pixel} endPixel End pixel.
+ * @const
+ * @type {number}
  */
-ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
-  this.startPixel_ = startPixel;
-  this.endPixel_ = endPixel;
-  this.geometry_ = this.createGeometry_();
-  this.requestMapRenderFrame_();
-};
+goog.webgl.LOW_FLOAT = 0x8DF0;
 
-// FIXME draw drag box
-goog.provide('ol.DragBoxEvent');
-goog.provide('ol.interaction.DragBox');
 
-goog.require('goog.events.Event');
-goog.require('goog.functions');
-goog.require('ol');
-goog.require('ol.events.ConditionType');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.render.Box');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MEDIUM_FLOAT = 0x8DF1;
 
 
 /**
  * @const
  * @type {number}
  */
-ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED =
-    ol.DRAG_BOX_HYSTERESIS_PIXELS *
-    ol.DRAG_BOX_HYSTERESIS_PIXELS;
+goog.webgl.HIGH_FLOAT = 0x8DF2;
 
 
 /**
- * @enum {string}
+ * @const
+ * @type {number}
  */
-ol.DragBoxEventType = {
-  /**
-   * Triggered upon drag box start.
-   * @event ol.DragBoxEvent#boxstart
-   * @api stable
-   */
-  BOXSTART: 'boxstart',
-  /**
-   * Triggered upon drag box end.
-   * @event ol.DragBoxEvent#boxend
-   * @api stable
-   */
-  BOXEND: 'boxend'
-};
-
+goog.webgl.LOW_INT = 0x8DF3;
 
 
 /**
- * @classdesc
- * Events emitted by {@link ol.interaction.DragBox} instances are instances of
- * this type.
- *
- * @param {string} type The event type.
- * @param {ol.Coordinate} coordinate The event coordinate.
- * @extends {goog.events.Event}
- * @constructor
- * @implements {oli.DragBoxEvent}
+ * @const
+ * @type {number}
  */
-ol.DragBoxEvent = function(type, coordinate) {
-  goog.base(this, type);
+goog.webgl.MEDIUM_INT = 0x8DF4;
 
-  /**
-   * The coordinate of the drag event.
-   * @const
-   * @type {ol.Coordinate}
-   * @api stable
-   */
-  this.coordinate = coordinate;
 
-};
-goog.inherits(ol.DragBoxEvent, goog.events.Event);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.HIGH_INT = 0x8DF5;
+
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER = 0x8D40;
 
 
 /**
- * @classdesc
- * Allows the user to draw a vector box by clicking and dragging on the map,
- * normally combined with an {@link ol.events.condition} that limits
- * it to when the shift or other key is held down. This is used, for example,
- * for zooming to a specific area of the map
- * (see {@link ol.interaction.DragZoom} and
- * {@link ol.interaction.DragRotateAndZoom}).
- *
- * This interaction is only supported for mouse devices.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.DragBoxEvent
- * @param {olx.interaction.DragBoxOptions=} opt_options Options.
- * @api stable
+ * @const
+ * @type {number}
  */
-ol.interaction.DragBox = function(opt_options) {
+goog.webgl.RENDERBUFFER = 0x8D41;
 
-  goog.base(this);
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGBA4 = 0x8056;
 
-  /**
-   * @private
-   * @type {ol.style.Style}
-   */
-  var style = goog.isDef(options.style) ? options.style : null;
 
-  /**
-   * @type {ol.render.Box}
-   * @private
-   */
-  this.box_ = new ol.render.Box(style);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGB5_A1 = 0x8057;
 
-  /**
-   * @type {ol.Pixel}
-   * @private
-   */
-  this.startPixel_ = null;
 
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.condition_ = goog.isDef(options.condition) ?
-      options.condition : ol.events.condition.always;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RGB565 = 0x8D62;
 
-};
-goog.inherits(ol.interaction.DragBox, ol.interaction.Pointer);
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_COMPONENT16 = 0x81A5;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragBox.prototype.handlePointerDrag = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return;
-  }
+goog.webgl.STENCIL_INDEX = 0x1901;
 
-  this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);
-};
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.STENCIL_INDEX8 = 0x8D48;
 
 
 /**
- * Returns geometry of last drawn box.
- * @return {ol.geom.Geometry} Geometry.
- * @api stable
+ * @const
+ * @type {number}
  */
-ol.interaction.DragBox.prototype.getGeometry = function() {
-  return this.box_.getGeometry();
-};
+goog.webgl.DEPTH_STENCIL = 0x84F9;
 
 
 /**
- * To be overriden by child classes.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @protected
+ * @const
+ * @type {number}
  */
-ol.interaction.DragBox.prototype.onBoxEnd = goog.nullFunction;
+goog.webgl.RENDERBUFFER_WIDTH = 0x8D42;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragBox.prototype.handlePointerUp = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
-  }
+goog.webgl.RENDERBUFFER_HEIGHT = 0x8D43;
 
-  this.box_.setMap(null);
 
-  var deltaX = mapBrowserEvent.pixel[0] - this.startPixel_[0];
-  var deltaY = mapBrowserEvent.pixel[1] - this.startPixel_[1];
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_INTERNAL_FORMAT = 0x8D44;
 
-  if (deltaX * deltaX + deltaY * deltaY >=
-      ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED) {
-    this.onBoxEnd(mapBrowserEvent);
-    this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXEND,
-        mapBrowserEvent.coordinate));
-  }
-  return false;
-};
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_RED_SIZE = 0x8D50;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragBox.prototype.handlePointerDown = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return false;
-  }
+goog.webgl.RENDERBUFFER_GREEN_SIZE = 0x8D51;
 
-  var browserEvent = mapBrowserEvent.browserEvent;
-  if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) {
-    this.startPixel_ = mapBrowserEvent.pixel;
-    this.box_.setMap(mapBrowserEvent.map);
-    this.box_.setPixels(this.startPixel_, this.startPixel_);
-    this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXSTART,
-        mapBrowserEvent.coordinate));
-    return true;
-  } else {
-    return false;
-  }
-};
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_BLUE_SIZE = 0x8D52;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragBox.prototype.shouldStopEvent = goog.functions.identity;
+goog.webgl.RENDERBUFFER_ALPHA_SIZE = 0x8D53;
 
-goog.provide('ol.Kinetic');
 
-goog.require('ol.Coordinate');
-goog.require('ol.PreRenderFunction');
-goog.require('ol.animation');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_DEPTH_SIZE = 0x8D54;
 
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_STENCIL_SIZE = 0x8D55;
+
 
 /**
- * @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
+ * @const
+ * @type {number}
  */
-ol.Kinetic = function(decay, minVelocity, delay) {
+goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.decay_ = decay;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minVelocity_ = minVelocity;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delay_ = delay;
 
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.points_ = [];
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.angle_ = 0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.initialVelocity_ = 0;
-};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3;
 
 
 /**
- * FIXME empty description for jsdoc
+ * @const
+ * @type {number}
  */
-ol.Kinetic.prototype.begin = function() {
-  this.points_.length = 0;
-  this.angle_ = 0;
-  this.initialVelocity_ = 0;
-};
+goog.webgl.COLOR_ATTACHMENT0 = 0x8CE0;
 
 
 /**
- * @param {number} x X.
- * @param {number} y Y.
+ * @const
+ * @type {number}
  */
-ol.Kinetic.prototype.update = function(x, y) {
-  this.points_.push(x, y, goog.now());
-};
+goog.webgl.DEPTH_ATTACHMENT = 0x8D00;
 
 
 /**
- * @return {boolean} Whether we should do kinetic animation.
+ * @const
+ * @type {number}
  */
-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 = goog.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;
-  }
+goog.webgl.STENCIL_ATTACHMENT = 0x8D20;
 
-  // 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_;
-};
+
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.DEPTH_STENCIL_ATTACHMENT = 0x821A;
 
 
 /**
- * @param {ol.Coordinate} source Source coordinate for the animation.
- * @return {ol.PreRenderFunction} Pre-render function for kinetic animation.
+ * @const
+ * @type {number}
  */
-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
-  });
-};
+goog.webgl.NONE = 0;
 
 
 /**
- * @private
- * @return {number} Duration of animation (milliseconds).
+ * @const
+ * @type {number}
  */
-ol.Kinetic.prototype.getDuration_ = function() {
-  return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_;
-};
+goog.webgl.FRAMEBUFFER_COMPLETE = 0x8CD5;
 
 
 /**
- * @return {number} Total distance travelled (pixels).
+ * @const
+ * @type {number}
  */
-ol.Kinetic.prototype.getDistance = function() {
-  return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
-};
+goog.webgl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6;
 
 
 /**
- * @return {number} Angle of the kinetic panning animation (radians).
+ * @const
+ * @type {number}
  */
-ol.Kinetic.prototype.getAngle = function() {
-  return this.angle_;
-};
+goog.webgl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7;
 
-goog.provide('ol.interaction.DragPan');
 
-goog.require('goog.asserts');
-goog.require('ol.Kinetic');
-goog.require('ol.Pixel');
-goog.require('ol.PreRenderFunction');
-goog.require('ol.ViewHint');
-goog.require('ol.coordinate');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Pointer');
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9;
+
 
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.FRAMEBUFFER_UNSUPPORTED = 0x8CDD;
 
 
 /**
- * @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
+ * @const
+ * @type {number}
  */
-ol.interaction.DragPan = function(opt_options) {
+goog.webgl.FRAMEBUFFER_BINDING = 0x8CA6;
 
-  goog.base(this);
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.RENDERBUFFER_BINDING = 0x8CA7;
 
-  /**
-   * @private
-   * @type {ol.Kinetic|undefined}
-   */
-  this.kinetic_ = options.kinetic;
 
-  /**
-   * @private
-   * @type {?ol.PreRenderFunction}
-   */
-  this.kineticPreRenderFn_ = null;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.MAX_RENDERBUFFER_SIZE = 0x84E8;
 
-  /**
-   * @type {ol.Pixel}
-   */
-  this.lastCentroid = null;
 
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.condition_ = goog.isDef(opt_options.condition) ?
-      opt_options.condition : ol.events.condition.noModifierKeys;
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.INVALID_FRAMEBUFFER_OPERATION = 0x0506;
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.noKinetic_ = false;
 
-};
-goog.inherits(ol.interaction.DragPan, ol.interaction.Pointer);
+/**
+ * @const
+ * @type {number}
+ */
+goog.webgl.UNPACK_FLIP_Y_WEBGL = 0x9240;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragPan.prototype.handlePointerDrag = function(mapBrowserEvent) {
-  goog.asserts.assert(this.targetPointers.length >= 1);
-  var centroid =
-      ol.interaction.Pointer.centroid(this.targetPointers);
-  if (this.kinetic_) {
-    this.kinetic_.update(centroid[0], centroid[1]);
-  }
-  if (!goog.isNull(this.lastCentroid)) {
-    var deltaX = this.lastCentroid[0] - centroid[0];
-    var deltaY = centroid[1] - this.lastCentroid[1];
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    var viewState = view.getState();
-    var center = [deltaX, deltaY];
-    ol.coordinate.scale(center, viewState.resolution);
-    ol.coordinate.rotate(center, viewState.rotation);
-    ol.coordinate.add(center, viewState.center);
-    center = view.constrainCenter(center);
-    map.render();
-    view.setCenter(center);
-  }
-  this.lastCentroid = centroid;
-};
+goog.webgl.UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragPan.prototype.handlePointerUp = function(mapBrowserEvent) {
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  if (this.targetPointers.length === 0) {
-    if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) {
-      var distance = this.kinetic_.getDistance();
-      var angle = this.kinetic_.getAngle();
-      var center = view.getCenter();
-      goog.asserts.assert(goog.isDef(center));
-      this.kineticPreRenderFn_ = this.kinetic_.pan(center);
-      map.beforeRender(this.kineticPreRenderFn_);
-      var centerpx = map.getPixelFromCoordinate(center);
-      var dest = map.getCoordinateFromPixel([
-        centerpx[0] - distance * Math.cos(angle),
-        centerpx[1] - distance * Math.sin(angle)
-      ]);
-      dest = view.constrainCenter(dest);
-      view.setCenter(dest);
-    }
-    view.setHint(ol.ViewHint.INTERACTING, -1);
-    map.render();
-    return false;
-  } else {
-    this.lastCentroid = null;
-    return true;
-  }
-};
+goog.webgl.CONTEXT_LOST_WEBGL = 0x9242;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.interaction.DragPan.prototype.handlePointerDown = function(mapBrowserEvent) {
-  if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) {
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    this.lastCentroid = null;
-    if (!this.handlingDownUpSequence) {
-      view.setHint(ol.ViewHint.INTERACTING, 1);
-    }
-    map.render();
-    if (!goog.isNull(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;
-  }
-};
+goog.webgl.UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243;
 
-// Copyright 2007 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Defines a 2-element vector class that can be used for
- * coordinate math, useful for animation systems and point manipulation.
- *
- * Vec2 objects inherit from goog.math.Coordinate and may be used wherever a
- * Coordinate is required. Where appropriate, Vec2 functions accept both Vec2
- * and Coordinate objects as input.
- *
- * @author brenneman@google.com (Shawn Brenneman)
+ * @const
+ * @type {number}
  */
+goog.webgl.BROWSER_DEFAULT_WEBGL = 0x9244;
 
-goog.provide('goog.math.Vec2');
-
-goog.require('goog.math');
-goog.require('goog.math.Coordinate');
 
+/**
+ * From the OES_texture_half_float extension.
+ * http://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/
+ * @const
+ * @type {number}
+ */
+goog.webgl.HALF_FLOAT_OES = 0x8D61;
 
 
 /**
- * Class for a two-dimensional vector object and assorted functions useful for
- * manipulating points.
- *
- * @param {number} x The x coordinate for the vector.
- * @param {number} y The y coordinate for the vector.
- * @constructor
- * @extends {goog.math.Coordinate}
+ * From the OES_standard_derivatives extension.
+ * http://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2 = function(x, y) {
-  /**
-   * X-value
-   * @type {number}
-   */
-  this.x = x;
+goog.webgl.FRAGMENT_SHADER_DERIVATIVE_HINT_OES = 0x8B8B;
 
-  /**
-   * Y-value
-   * @type {number}
-   */
-  this.y = y;
-};
-goog.inherits(goog.math.Vec2, goog.math.Coordinate);
+
+/**
+ * From the OES_vertex_array_object extension.
+ * http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/
+ * @const
+ * @type {number}
+ */
+goog.webgl.VERTEX_ARRAY_BINDING_OES = 0x85B5;
 
 
 /**
- * @return {!goog.math.Vec2} A random unit-length vector.
+ * From the WEBGL_debug_renderer_info extension.
+ * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2.randomUnit = function() {
-  var angle = Math.random() * Math.PI * 2;
-  return new goog.math.Vec2(Math.cos(angle), Math.sin(angle));
-};
+goog.webgl.UNMASKED_VENDOR_WEBGL = 0x9245;
 
 
 /**
- * @return {!goog.math.Vec2} A random vector inside the unit-disc.
+ * From the WEBGL_debug_renderer_info extension.
+ * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2.random = function() {
-  var mag = Math.sqrt(Math.random());
-  var angle = Math.random() * Math.PI * 2;
+goog.webgl.UNMASKED_RENDERER_WEBGL = 0x9246;
 
-  return new goog.math.Vec2(Math.cos(angle) * mag, Math.sin(angle) * mag);
-};
+
+/**
+ * From the WEBGL_compressed_texture_s3tc extension.
+ * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
+ * @const
+ * @type {number}
+ */
+goog.webgl.COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
 
 
 /**
- * Returns a new Vec2 object from a given coordinate.
- * @param {!goog.math.Coordinate} a The coordinate.
- * @return {!goog.math.Vec2} A new vector object.
+ * From the WEBGL_compressed_texture_s3tc extension.
+ * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2.fromCoordinate = function(a) {
-  return new goog.math.Vec2(a.x, a.y);
-};
+goog.webgl.COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
 
 
 /**
- * @return {!goog.math.Vec2} A new vector with the same coordinates as this one.
- * @override
+ * From the WEBGL_compressed_texture_s3tc extension.
+ * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2.prototype.clone = function() {
-  return new goog.math.Vec2(this.x, this.y);
-};
+goog.webgl.COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
 
 
 /**
- * Returns the magnitude of the vector measured from the origin.
- * @return {number} The length of the vector.
+ * From the WEBGL_compressed_texture_s3tc extension.
+ * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2.prototype.magnitude = function() {
-  return Math.sqrt(this.x * this.x + this.y * this.y);
-};
+goog.webgl.COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
 
 
 /**
- * Returns the squared magnitude of the vector measured from the origin.
- * NOTE(brenneman): Leaving out the square root is not a significant
- * optimization in JavaScript.
- * @return {number} The length of the vector, squared.
+ * From the EXT_texture_filter_anisotropic extension.
+ * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2.prototype.squaredMagnitude = function() {
-  return this.x * this.x + this.y * this.y;
-};
+goog.webgl.TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE;
 
 
 /**
- * @return {!goog.math.Vec2} This coordinate after scaling.
- * @override
+ * From the EXT_texture_filter_anisotropic extension.
+ * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/
+ * @const
+ * @type {number}
  */
-goog.math.Vec2.prototype.scale =
-    /** @type {function(number, number=):!goog.math.Vec2} */
-    (goog.math.Coordinate.prototype.scale);
+goog.webgl.MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF;
+
+goog.provide('ol.webgl.shader');
+
+goog.require('goog.functions');
+goog.require('goog.webgl');
+goog.require('ol.webgl');
+
 
 
 /**
- * Reverses the sign of the vector. Equivalent to scaling the vector by -1.
- * @return {!goog.math.Vec2} The inverted vector.
+ * @constructor
+ * @param {string} source Source.
+ * @struct
  */
-goog.math.Vec2.prototype.invert = function() {
-  this.x = -this.x;
-  this.y = -this.y;
-  return this;
+ol.webgl.Shader = function(source) {
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.source_ = source;
+
 };
 
 
 /**
- * Normalizes the current vector to have a magnitude of 1.
- * @return {!goog.math.Vec2} The normalized vector.
+ * @return {number} Type.
  */
-goog.math.Vec2.prototype.normalize = function() {
-  return this.scale(1 / this.magnitude());
-};
+ol.webgl.Shader.prototype.getType = goog.abstractMethod;
 
 
 /**
- * Adds another vector to this vector in-place.
- * @param {!goog.math.Coordinate} b The vector to add.
- * @return {!goog.math.Vec2}  This vector with {@code b} added.
+ * @return {string} Source.
  */
-goog.math.Vec2.prototype.add = function(b) {
-  this.x += b.x;
-  this.y += b.y;
-  return this;
+ol.webgl.Shader.prototype.getSource = function() {
+  return this.source_;
 };
 
 
 /**
- * Subtracts another vector from this vector in-place.
- * @param {!goog.math.Coordinate} b The vector to subtract.
- * @return {!goog.math.Vec2} This vector with {@code b} subtracted.
+ * @return {boolean} Is animated?
  */
-goog.math.Vec2.prototype.subtract = function(b) {
-  this.x -= b.x;
-  this.y -= b.y;
-  return this;
+ol.webgl.Shader.prototype.isAnimated = goog.functions.FALSE;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.webgl.Shader}
+ * @param {string} source Source.
+ * @struct
+ */
+ol.webgl.shader.Fragment = function(source) {
+  goog.base(this, source);
 };
+goog.inherits(ol.webgl.shader.Fragment, ol.webgl.Shader);
 
 
 /**
- * Rotates this vector in-place by a given angle, specified in radians.
- * @param {number} angle The angle, in radians.
- * @return {!goog.math.Vec2} This vector rotated {@code angle} radians.
+ * @inheritDoc
  */
-goog.math.Vec2.prototype.rotate = function(angle) {
-  var cos = Math.cos(angle);
-  var sin = Math.sin(angle);
-  var newX = this.x * cos - this.y * sin;
-  var newY = this.y * cos + this.x * sin;
-  this.x = newX;
-  this.y = newY;
-  return this;
+ol.webgl.shader.Fragment.prototype.getType = function() {
+  return goog.webgl.FRAGMENT_SHADER;
 };
 
 
+
 /**
- * Rotates a vector by a given angle, specified in radians, relative to a given
- * axis rotation point. The returned vector is a newly created instance - no
- * in-place changes are done.
- * @param {!goog.math.Vec2} v A vector.
- * @param {!goog.math.Vec2} axisPoint The rotation axis point.
- * @param {number} angle The angle, in radians.
- * @return {!goog.math.Vec2} The rotated vector in a newly created instance.
+ * @constructor
+ * @extends {ol.webgl.Shader}
+ * @param {string} source Source.
+ * @struct
  */
-goog.math.Vec2.rotateAroundPoint = function(v, axisPoint, angle) {
-  var res = v.clone();
-  return res.subtract(axisPoint).rotate(angle).add(axisPoint);
+ol.webgl.shader.Vertex = function(source) {
+  goog.base(this, source);
 };
+goog.inherits(ol.webgl.shader.Vertex, ol.webgl.Shader);
 
 
 /**
- * Compares this vector with another for equality.
- * @param {!goog.math.Vec2} b The other vector.
- * @return {boolean} Whether this vector has the same x and y as the given
- *     vector.
+ * @inheritDoc
  */
-goog.math.Vec2.prototype.equals = function(b) {
-  return this == b || !!b && this.x == b.x && this.y == b.y;
+ol.webgl.shader.Vertex.prototype.getType = function() {
+  return goog.webgl.VERTEX_SHADER;
 };
 
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.imagereplay.shader.Color');
+
+goog.require('ol.webgl.shader');
+
+
 
 /**
- * Returns the distance between two vectors.
- * @param {!goog.math.Coordinate} a The first vector.
- * @param {!goog.math.Coordinate} b The second vector.
- * @return {number} The distance.
+ * @constructor
+ * @extends {ol.webgl.shader.Fragment}
+ * @struct
  */
-goog.math.Vec2.distance = goog.math.Coordinate.distance;
+ol.render.webgl.imagereplay.shader.ColorFragment = function() {
+  goog.base(this, ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE);
+};
+goog.inherits(ol.render.webgl.imagereplay.shader.ColorFragment, ol.webgl.shader.Fragment);
+goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorFragment);
 
 
 /**
- * Returns the squared distance between two vectors.
- * @param {!goog.math.Coordinate} a The first vector.
- * @param {!goog.math.Coordinate} b The second vector.
- * @return {number} The squared distance.
+ * @const
+ * @type {string}
  */
-goog.math.Vec2.squaredDistance = goog.math.Coordinate.squaredDistance;
+ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_image, v_texCoord);\n  float alpha = texColor.a * v_opacity * u_opacity;\n  if (alpha == 0.0) {\n    discard;\n  }\n  gl_FragColor.a = alpha;\n  gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n}\n';
 
 
 /**
- * Compares vectors for equality.
- * @param {!goog.math.Coordinate} a The first vector.
- * @param {!goog.math.Coordinate} b The second vector.
- * @return {boolean} Whether the vectors have the same x and y coordinates.
+ * @const
+ * @type {string}
  */
-goog.math.Vec2.equals = goog.math.Coordinate.equals;
+ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}';
 
 
 /**
- * Returns the sum of two vectors as a new Vec2.
- * @param {!goog.math.Coordinate} a The first vector.
- * @param {!goog.math.Coordinate} b The second vector.
- * @return {!goog.math.Vec2} The sum vector.
+ * @const
+ * @type {string}
  */
-goog.math.Vec2.sum = function(a, b) {
-  return new goog.math.Vec2(a.x + b.x, a.y + b.y);
-};
+ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE = goog.DEBUG ?
+    ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE :
+    ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE;
+
 
 
 /**
- * Returns the difference between two vectors as a new Vec2.
- * @param {!goog.math.Coordinate} a The first vector.
- * @param {!goog.math.Coordinate} b The second vector.
- * @return {!goog.math.Vec2} The difference vector.
+ * @constructor
+ * @extends {ol.webgl.shader.Vertex}
+ * @struct
  */
-goog.math.Vec2.difference = function(a, b) {
-  return new goog.math.Vec2(a.x - b.x, a.y - b.y);
+ol.render.webgl.imagereplay.shader.ColorVertex = function() {
+  goog.base(this, ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE);
 };
+goog.inherits(ol.render.webgl.imagereplay.shader.ColorVertex, ol.webgl.shader.Vertex);
+goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorVertex);
 
 
 /**
- * Returns the dot-product of two vectors.
- * @param {!goog.math.Coordinate} a The first vector.
- * @param {!goog.math.Coordinate} b The second vector.
- * @return {number} The dot-product of the two vectors.
+ * @const
+ * @type {string}
  */
-goog.math.Vec2.dot = function(a, b) {
-  return a.x * b.x + a.y * b.y;
-};
+ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n  mat4 offsetMatrix = u_offsetScaleMatrix;\n  if (a_rotateWithView == 1.0) {\n    offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n  }\n  vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n  v_texCoord = a_texCoord;\n  v_opacity = a_opacity;\n}\n\n\n';
 
 
 /**
- * Returns a new Vec2 that is the linear interpolant between vectors a and b at
- * scale-value x.
- * @param {!goog.math.Coordinate} a Vector a.
- * @param {!goog.math.Coordinate} b Vector b.
- * @param {number} x The proportion between a and b.
- * @return {!goog.math.Vec2} The interpolated vector.
+ * @const
+ * @type {string}
  */
-goog.math.Vec2.lerp = function(a, b, x) {
-  return new goog.math.Vec2(goog.math.lerp(a.x, b.x, x),
-                            goog.math.lerp(a.y, b.y, x));
-};
+ol.render.webgl.imagereplay.shader.ColorVertex.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;}';
 
-goog.provide('ol.interaction.DragRotateAndZoom');
 
-goog.require('goog.asserts');
-goog.require('goog.functions');
-goog.require('goog.math.Vec2');
-goog.require('ol');
-goog.require('ol.ViewHint');
-goog.require('ol.events.ConditionType');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE = goog.DEBUG ?
+    ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE :
+    ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE;
 
 
 
 /**
- * @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
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-ol.interaction.DragRotateAndZoom = function(opt_options) {
+ol.render.webgl.imagereplay.shader.Color.Locations = function(gl, program) {
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_colorMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_colorMatrix' : 'k');
 
-  goog.base(this);
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_image = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_image' : 'm');
 
   /**
-   * @private
-   * @type {ol.events.ConditionType}
+   * @type {WebGLUniformLocation}
    */
-  this.condition_ = goog.isDef(options.condition) ?
-      options.condition : ol.events.condition.shiftKeyOnly;
+  this.u_offsetRotateMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j');
 
   /**
-   * @private
-   * @type {number|undefined}
+   * @type {WebGLUniformLocation}
    */
-  this.lastAngle_ = undefined;
+  this.u_offsetScaleMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i');
 
   /**
-   * @private
-   * @type {number|undefined}
+   * @type {WebGLUniformLocation}
    */
-  this.lastMagnitude_ = undefined;
+  this.u_opacity = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_opacity' : 'l');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_projectionMatrix' : 'h');
 
   /**
-   * @private
    * @type {number}
    */
-  this.lastScaleDelta_ = 0;
+  this.a_offsets = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_offsets' : 'e');
 
-};
-goog.inherits(ol.interaction.DragRotateAndZoom,
-    ol.interaction.Pointer);
+  /**
+   * @type {number}
+   */
+  this.a_opacity = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_opacity' : 'f');
 
+  /**
+   * @type {number}
+   */
+  this.a_position = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_position' : 'c');
 
-/**
- * @inheritDoc
- */
-ol.interaction.DragRotateAndZoom.prototype.handlePointerDrag =
-    function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return;
-  }
+  /**
+   * @type {number}
+   */
+  this.a_rotateWithView = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_rotateWithView' : 'g');
 
-  var map = mapBrowserEvent.map;
-  var size = map.getSize();
-  var offset = mapBrowserEvent.pixel;
-  var delta = new goog.math.Vec2(
-      offset[0] - size[0] / 2,
-      size[1] / 2 - offset[1]);
-  var theta = Math.atan2(delta.y, delta.x);
-  var magnitude = delta.magnitude();
-  var view = map.getView();
-  var viewState = view.getState();
-  map.render();
-  if (goog.isDef(this.lastAngle_)) {
-    var angleDelta = theta - this.lastAngle_;
-    ol.interaction.Interaction.rotateWithoutConstraints(
-        map, view, viewState.rotation - angleDelta);
-  }
-  this.lastAngle_ = theta;
-  if (goog.isDef(this.lastMagnitude_)) {
-    var resolution = this.lastMagnitude_ * (viewState.resolution / magnitude);
-    ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution);
-  }
-  if (goog.isDef(this.lastMagnitude_)) {
-    this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
-  }
-  this.lastMagnitude_ = magnitude;
+  /**
+   * @type {number}
+   */
+  this.a_texCoord = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_texCoord' : 'd');
 };
 
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.imagereplay.shader.Default');
+
+goog.require('ol.webgl.shader');
+
+
 
 /**
- * @inheritDoc
+ * @constructor
+ * @extends {ol.webgl.shader.Fragment}
+ * @struct
  */
-ol.interaction.DragRotateAndZoom.prototype.handlePointerUp =
-    function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
-  }
-
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  view.setHint(ol.ViewHint.INTERACTING, -1);
-  var viewState = view.getState();
-  var direction = this.lastScaleDelta_ - 1;
-  ol.interaction.Interaction.rotate(map, view, viewState.rotation);
-  ol.interaction.Interaction.zoom(map, view, viewState.resolution,
-      undefined, ol.DRAGROTATEANDZOOM_ANIMATION_DURATION,
-      direction);
-  this.lastScaleDelta_ = 0;
-  return false;
+ol.render.webgl.imagereplay.shader.DefaultFragment = function() {
+  goog.base(this, ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE);
 };
+goog.inherits(ol.render.webgl.imagereplay.shader.DefaultFragment, ol.webgl.shader.Fragment);
+goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultFragment);
 
 
 /**
- * @inheritDoc
- */
-ol.interaction.DragRotateAndZoom.prototype.handlePointerDown =
-    function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return false;
-  }
+ * @const
+ * @type {string}
+ */
+ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_image, v_texCoord);\n  gl_FragColor.rgb = texColor.rgb;\n  float alpha = texColor.a * v_opacity * u_opacity;\n  if (alpha == 0.0) {\n    discard;\n  }\n  gl_FragColor.a = alpha;\n}\n';
 
-  if (this.condition_(mapBrowserEvent)) {
-    mapBrowserEvent.map.getView().setHint(ol.ViewHint.INTERACTING, 1);
-    this.lastAngle_ = undefined;
-    this.lastMagnitude_ = undefined;
-    return true;
-  } else {
-    return false;
-  }
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE = goog.DEBUG ?
+    ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE :
+    ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.webgl.shader.Vertex}
+ * @struct
+ */
+ol.render.webgl.imagereplay.shader.DefaultVertex = function() {
+  goog.base(this, ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE);
 };
+goog.inherits(ol.render.webgl.imagereplay.shader.DefaultVertex, ol.webgl.shader.Vertex);
+goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultVertex);
 
 
 /**
- * @inheritDoc
- * Stop the event if it was handled, so that interaction `DragZoom`
- * does not interfere.
+ * @const
+ * @type {string}
  */
-ol.interaction.DragRotateAndZoom.prototype.shouldStopEvent =
-    goog.functions.identity;
+ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n  mat4 offsetMatrix = u_offsetScaleMatrix;\n  if (a_rotateWithView == 1.0) {\n    offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n  }\n  vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n  v_texCoord = a_texCoord;\n  v_opacity = a_opacity;\n}\n\n\n';
 
-goog.provide('ol.interaction.DragRotate');
 
-goog.require('goog.asserts');
-goog.require('ol');
-goog.require('ol.ViewHint');
-goog.require('ol.events.ConditionType');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}';
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE = goog.DEBUG ?
+    ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE :
+    ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE;
 
 
 
 /**
- * @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
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-ol.interaction.DragRotate = function(opt_options) {
+ol.render.webgl.imagereplay.shader.Default.Locations = function(gl, program) {
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_image = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_image' : 'l');
 
-  goog.base(this);
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_offsetRotateMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j');
 
   /**
-   * @private
-   * @type {ol.events.ConditionType}
+   * @type {WebGLUniformLocation}
    */
-  this.condition_ = goog.isDef(options.condition) ?
-      options.condition : ol.events.condition.altShiftKeysOnly;
+  this.u_offsetScaleMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i');
 
   /**
-   * @private
-   * @type {number|undefined}
+   * @type {WebGLUniformLocation}
    */
-  this.lastAngle_ = undefined;
+  this.u_opacity = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_opacity' : 'k');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_projectionMatrix' : 'h');
+
+  /**
+   * @type {number}
+   */
+  this.a_offsets = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_offsets' : 'e');
 
+  /**
+   * @type {number}
+   */
+  this.a_opacity = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_opacity' : 'f');
+
+  /**
+   * @type {number}
+   */
+  this.a_position = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_position' : 'c');
+
+  /**
+   * @type {number}
+   */
+  this.a_rotateWithView = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_rotateWithView' : 'g');
+
+  /**
+   * @type {number}
+   */
+  this.a_texCoord = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_texCoord' : 'd');
 };
-goog.inherits(ol.interaction.DragRotate, ol.interaction.Pointer);
+
+goog.provide('ol.webgl.Buffer');
+
+goog.require('goog.array');
+goog.require('goog.webgl');
+goog.require('ol');
 
 
 /**
- * @inheritDoc
+ * @enum {number}
  */
-ol.interaction.DragRotate.prototype.handlePointerDrag =
-    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 (goog.isDef(this.lastAngle_)) {
-    var delta = theta - this.lastAngle_;
-    var view = map.getView();
-    var viewState = view.getState();
-    map.render();
-    ol.interaction.Interaction.rotateWithoutConstraints(
-        map, view, viewState.rotation - delta);
-  }
-  this.lastAngle_ = theta;
+ol.webgl.BufferUsage = {
+  STATIC_DRAW: goog.webgl.STATIC_DRAW,
+  STREAM_DRAW: goog.webgl.STREAM_DRAW,
+  DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW
 };
 
 
+
 /**
- * @inheritDoc
+ * @constructor
+ * @param {Array.<number>=} opt_arr Array.
+ * @param {number=} opt_usage Usage.
+ * @struct
  */
-ol.interaction.DragRotate.prototype.handlePointerUp =
-    function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
-  }
+ol.webgl.Buffer = function(opt_arr, opt_usage) {
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.usage_ = goog.isDef(opt_usage) ?
+      opt_usage : ol.webgl.BufferUsage.STATIC_DRAW;
 
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  view.setHint(ol.ViewHint.INTERACTING, -1);
-  var viewState = view.getState();
-  ol.interaction.Interaction.rotate(map, view, viewState.rotation,
-      undefined, ol.DRAGROTATE_ANIMATION_DURATION);
-  return false;
 };
 
 
 /**
- * @inheritDoc
+ * @return {Array.<number>} Array.
  */
-ol.interaction.DragRotate.prototype.handlePointerDown =
-    function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return false;
-  }
+ol.webgl.Buffer.prototype.getArray = function() {
+  return this.arr_;
+};
 
-  var browserEvent = mapBrowserEvent.browserEvent;
-  if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) {
-    var map = mapBrowserEvent.map;
-    map.getView().setHint(ol.ViewHint.INTERACTING, 1);
-    map.render();
-    this.lastAngle_ = undefined;
-    return true;
-  } else {
-    return false;
-  }
+
+/**
+ * @return {number} Usage.
+ */
+ol.webgl.Buffer.prototype.getUsage = function() {
+  return this.usage_;
 };
 
-goog.provide('ol.interaction.DragZoom');
+goog.provide('ol.render.webgl.ImageReplay');
+goog.provide('ol.render.webgl.ReplayGroup');
 
+goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('ol');
-goog.require('ol.events.condition');
+goog.require('goog.functions');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('ol.color.Matrix');
 goog.require('ol.extent');
-goog.require('ol.interaction.DragBox');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.style.Stroke');
-goog.require('ol.style.Style');
+goog.require('ol.render.IReplayGroup');
+goog.require('ol.render.webgl.imagereplay.shader.Color');
+goog.require('ol.render.webgl.imagereplay.shader.Default');
+goog.require('ol.vec.Mat4');
+goog.require('ol.webgl.Buffer');
 
 
 
 /**
- * @classdesc
- * Allows the user to zoom the map by clicking and dragging on the map,
- * normally combined with an {@link ol.events.condition} that limits
- * it to when a key, shift by default, is held down.
- *
  * @constructor
- * @extends {ol.interaction.DragBox}
- * @param {olx.interaction.DragZoomOptions=} opt_options Options.
- * @api stable
+ * @implements {ol.render.IVectorContext}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @protected
+ * @struct
  */
-ol.interaction.DragZoom = function(opt_options) {
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
 
-  var condition = goog.isDef(options.condition) ?
-      options.condition : ol.events.condition.shiftKeyOnly;
+  /**
+   * @type {number|undefined}
+   * @private
+   */
+  this.anchorX_ = undefined;
 
   /**
+   * @type {number|undefined}
    * @private
-   * @type {ol.style.Style}
    */
-  var style = goog.isDef(options.style) ?
-      options.style : new ol.style.Style({
-        stroke: new ol.style.Stroke({
-          color: [0, 0, 255, 1]
-        })
-      });
+  this.anchorY_ = undefined;
 
-  goog.base(this, {
-    condition: condition,
-    style: style
-  });
+  /**
+   * @private
+   * @type {ol.color.Matrix}
+   */
+  this.colorMatrix_ = new ol.color.Matrix();
 
-};
-goog.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
+  /**
+   * 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 {number|undefined}
+   * @private
+   */
+  this.height_ = undefined;
 
-/**
- * @inheritDoc
- */
-ol.interaction.DragZoom.prototype.onBoxEnd = function() {
-  var map = this.getMap();
-  var view = map.getView();
-  goog.asserts.assert(goog.isDef(view));
-  var extent = this.getGeometry().getExtent();
-  var center = ol.extent.getCenter(extent);
-  var size = map.getSize();
-  goog.asserts.assert(goog.isDef(size));
-  ol.interaction.Interaction.zoom(map, view,
-      view.getResolutionForExtent(extent, size),
-      center, ol.DRAGZOOM_ANIMATION_DURATION);
-};
+  /**
+   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+   * @private
+   */
+  this.images_ = [];
 
-// Copyright 2012 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+  /**
+   * @type {number|undefined}
+   * @private
+   */
+  this.imageHeight_ = undefined;
 
-/**
- * @fileoverview A delayed callback that pegs to the next animation frame
- * instead of a user-configurable timeout.
- *
- */
+  /**
+   * @type {number|undefined}
+   * @private
+   */
+  this.imageWidth_ = undefined;
 
-goog.provide('goog.async.AnimationDelay');
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.indices_ = [];
 
-goog.require('goog.Disposable');
-goog.require('goog.events');
-goog.require('goog.functions');
+  /**
+   * @type {ol.webgl.Buffer}
+   * @private
+   */
+  this.indicesBuffer_ = null;
 
+  /**
+   * @private
+   * @type {ol.render.webgl.imagereplay.shader.Color.Locations}
+   */
+  this.colorLocations_ = null;
 
+  /**
+   * @private
+   * @type {ol.render.webgl.imagereplay.shader.Default.Locations}
+   */
+  this.defaultLocations_ = null;
 
-// TODO(nicksantos): Should we factor out the common code between this and
-// goog.async.Delay? I'm not sure if there's enough code for this to really
-// make sense. Subclassing seems like the wrong approach for a variety of
-// reasons. Maybe there should be a common interface?
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.opacity_ = undefined;
 
+  /**
+   * @type {!goog.vec.Mat4.Number}
+   * @private
+   */
+  this.offsetRotateMatrix_ = goog.vec.Mat4.createNumberIdentity();
 
+  /**
+   * @type {!goog.vec.Mat4.Number}
+   * @private
+   */
+  this.offsetScaleMatrix_ = goog.vec.Mat4.createNumberIdentity();
 
-/**
- * A delayed callback that pegs to the next animation frame
- * instead of a user configurable timeout. By design, this should have
- * the same interface as goog.async.Delay.
- *
- * Uses requestAnimationFrame and friends when available, but falls
- * back to a timeout of goog.async.AnimationDelay.TIMEOUT.
- *
- * For more on requestAnimationFrame and how you can use it to create smoother
- * animations, see:
- * @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
- *
- * @param {function(number)} listener Function to call when the delay completes.
- *     Will be passed the timestamp when it's called, in unix ms.
- * @param {Window=} opt_window The window object to execute the delay in.
- *     Defaults to the global object.
- * @param {Object=} opt_handler The object scope to invoke the function in.
- * @constructor
- * @extends {goog.Disposable}
- * @final
- */
-goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
-  goog.async.AnimationDelay.base(this, 'constructor');
+  /**
+   * @type {number|undefined}
+   * @private
+   */
+  this.originX_ = undefined;
 
   /**
-   * The function that will be invoked after a delay.
-   * @type {function(number)}
+   * @type {number|undefined}
    * @private
    */
-  this.listener_ = listener;
+  this.originY_ = undefined;
 
   /**
-   * The object context to invoke the callback in.
-   * @type {Object|undefined}
+   * @type {!goog.vec.Mat4.Number}
    * @private
    */
-  this.handler_ = opt_handler;
+  this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity();
+
+  /**
+   * @private
+   * @type {boolean|undefined}
+   */
+  this.rotateWithView_ = undefined;
 
   /**
-   * @type {Window}
    * @private
+   * @type {number|undefined}
    */
-  this.win_ = opt_window || window;
+  this.rotation_ = undefined;
 
   /**
-   * Cached callback function invoked when the delay finishes.
-   * @type {function()}
    * @private
+   * @type {number|undefined}
    */
-  this.callback_ = goog.bind(this.doAction_, this);
+  this.scale_ = undefined;
+
+  /**
+   * @type {Array.<WebGLTexture>}
+   * @private
+   */
+  this.textures_ = [];
+
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.vertices_ = [];
+
+  /**
+   * @type {ol.webgl.Buffer}
+   * @private
+   */
+  this.verticesBuffer_ = null;
+
+  /**
+   * @type {number|undefined}
+   * @private
+   */
+  this.width_ = undefined;
+
 };
-goog.inherits(goog.async.AnimationDelay, goog.Disposable);
 
 
 /**
- * Identifier of the active delay timeout, or event listener,
- * or null when inactive.
- * @type {goog.events.Key|number|null}
- * @private
+ * @param {ol.webgl.Context} context WebGL context.
+ * @return {function()} Delete resources function.
+ */
+ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction =
+    function(context) {
+  // We only delete our stuff here. The shaders and the program may
+  // be used by other ImageReplay instances (for other layers). And
+  // they will be deleted when disposing of the ol.webgl.Context
+  // object.
+  goog.asserts.assert(!goog.isNull(this.verticesBuffer_));
+  goog.asserts.assert(!goog.isNull(this.indicesBuffer_));
+  var verticesBuffer = this.verticesBuffer_;
+  var indicesBuffer = this.indicesBuffer_;
+  var textures = this.textures_;
+  var gl = context.getGL();
+  return function() {
+    if (!gl.isContextLost()) {
+      var i, ii;
+      for (i = 0, ii = textures.length; i < ii; ++i) {
+        gl.deleteTexture(textures[i]);
+      }
+    }
+    context.deleteBuffer(verticesBuffer);
+    context.deleteBuffer(indicesBuffer);
+  };
+};
+
+
+/**
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.id_ = null;
+ol.render.webgl.ImageReplay.prototype.drawAsync = goog.abstractMethod;
 
 
 /**
- * If we're using dom listeners.
- * @type {?boolean}
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} My end.
  * @private
  */
-goog.async.AnimationDelay.prototype.usingListeners_ = false;
+ol.render.webgl.ImageReplay.prototype.drawCoordinates_ =
+    function(flatCoordinates, offset, end, stride) {
+  goog.asserts.assert(goog.isDef(this.anchorX_));
+  goog.asserts.assert(goog.isDef(this.anchorY_));
+  goog.asserts.assert(goog.isDef(this.height_));
+  goog.asserts.assert(goog.isDef(this.imageHeight_));
+  goog.asserts.assert(goog.isDef(this.imageWidth_));
+  goog.asserts.assert(goog.isDef(this.opacity_));
+  goog.asserts.assert(goog.isDef(this.originX_));
+  goog.asserts.assert(goog.isDef(this.originY_));
+  goog.asserts.assert(goog.isDef(this.rotateWithView_));
+  goog.asserts.assert(goog.isDef(this.rotation_));
+  goog.asserts.assert(goog.isDef(this.scale_));
+  goog.asserts.assert(goog.isDef(this.width_));
+  var anchorX = this.anchorX_;
+  var anchorY = this.anchorY_;
+  var height = this.height_;
+  var imageHeight = this.imageHeight_;
+  var imageWidth = this.imageWidth_;
+  var opacity = this.opacity_;
+  var originX = this.originX_;
+  var originY = this.originY_;
+  var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0;
+  var rotation = this.rotation_;
+  var scale = this.scale_;
+  var width = this.width_;
+  var cos = Math.cos(rotation);
+  var sin = Math.sin(rotation);
+  var numIndices = this.indices_.length;
+  var numVertices = this.vertices_.length;
+  var i, n, offsetX, offsetY, x, y;
+  for (i = offset; i < end; i += stride) {
+    x = flatCoordinates[i] - this.origin_[0];
+    y = flatCoordinates[i + 1] - this.origin_[1];
+
+    // There are 4 vertices per [x, y] point, one for each corner of the
+    // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if
+    // WebGL supported Geometry Shaders (which can emit new vertices), but that
+    // is not currently the case.
+    //
+    // And each vertex includes 8 values: the x and y coordinates, the x and
+    // y offsets used to calculate the position of the corner, the u and
+    // v texture coordinates for the corner, the opacity, and whether the
+    // the image should be rotated with the view (rotateWithView).
+
+    n = numVertices / 8;
+
+    // bottom-left corner
+    offsetX = -scale * anchorX;
+    offsetY = -scale * (height - anchorY);
+    this.vertices_[numVertices++] = x;
+    this.vertices_[numVertices++] = y;
+    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
+    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
+    this.vertices_[numVertices++] = originX / imageWidth;
+    this.vertices_[numVertices++] = (originY + height) / imageHeight;
+    this.vertices_[numVertices++] = opacity;
+    this.vertices_[numVertices++] = rotateWithView;
+
+    // bottom-right corner
+    offsetX = scale * (width - anchorX);
+    offsetY = -scale * (height - anchorY);
+    this.vertices_[numVertices++] = x;
+    this.vertices_[numVertices++] = y;
+    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
+    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
+    this.vertices_[numVertices++] = (originX + width) / imageWidth;
+    this.vertices_[numVertices++] = (originY + height) / imageHeight;
+    this.vertices_[numVertices++] = opacity;
+    this.vertices_[numVertices++] = rotateWithView;
+
+    // top-right corner
+    offsetX = scale * (width - anchorX);
+    offsetY = scale * anchorY;
+    this.vertices_[numVertices++] = x;
+    this.vertices_[numVertices++] = y;
+    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
+    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
+    this.vertices_[numVertices++] = (originX + width) / imageWidth;
+    this.vertices_[numVertices++] = originY / imageHeight;
+    this.vertices_[numVertices++] = opacity;
+    this.vertices_[numVertices++] = rotateWithView;
+
+    // top-left corner
+    offsetX = -scale * anchorX;
+    offsetY = scale * anchorY;
+    this.vertices_[numVertices++] = x;
+    this.vertices_[numVertices++] = y;
+    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
+    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
+    this.vertices_[numVertices++] = originX / imageWidth;
+    this.vertices_[numVertices++] = originY / imageHeight;
+    this.vertices_[numVertices++] = opacity;
+    this.vertices_[numVertices++] = rotateWithView;
+
+    this.indices_[numIndices++] = n;
+    this.indices_[numIndices++] = n + 1;
+    this.indices_[numIndices++] = n + 2;
+    this.indices_[numIndices++] = n;
+    this.indices_[numIndices++] = n + 2;
+    this.indices_[numIndices++] = n + 3;
+  }
+
+  return numVertices;
+};
 
 
 /**
- * Default wait timeout for animations (in milliseconds).  Only used for timed
- * animation, which uses a timer (setTimeout) to schedule animation.
- *
- * @type {number}
- * @const
+ * @inheritDoc
  */
-goog.async.AnimationDelay.TIMEOUT = 20;
+ol.render.webgl.ImageReplay.prototype.drawCircleGeometry = goog.abstractMethod;
 
 
 /**
- * Name of event received from the requestAnimationFrame in Firefox.
- *
- * @type {string}
- * @const
- * @private
+ * @inheritDoc
  */
-goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint';
+ol.render.webgl.ImageReplay.prototype.drawFeature = goog.abstractMethod;
 
 
 /**
- * Starts the delay timer. The provided listener function will be called
- * before the next animation frame.
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.start = function() {
-  this.stop();
-  this.usingListeners_ = false;
-
-  var raf = this.getRaf_();
-  var cancelRaf = this.getCancelRaf_();
-  if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
-    // Because Firefox (Gecko) runs animation in separate threads, it also saves
-    // time by running the requestAnimationFrame callbacks in that same thread.
-    // Sadly this breaks the assumption of implicit thread-safety in JS, and can
-    // thus create thread-based inconsistencies on counters etc.
-    //
-    // Calling cycleAnimations_ using the MozBeforePaint event instead of as
-    // callback fixes this.
-    //
-    // Trigger this condition only if the mozRequestAnimationFrame is available,
-    // but not the W3C requestAnimationFrame function (as in draft) or the
-    // equivalent cancel functions.
-    this.id_ = goog.events.listen(
-        this.win_,
-        goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_,
-        this.callback_);
-    this.win_.mozRequestAnimationFrame(null);
-    this.usingListeners_ = true;
-  } else if (raf && cancelRaf) {
-    this.id_ = raf.call(this.win_, this.callback_);
-  } else {
-    this.id_ = this.win_.setTimeout(
-        // Prior to Firefox 13, Gecko passed a non-standard parameter
-        // to the callback that we want to ignore.
-        goog.functions.lock(this.callback_),
-        goog.async.AnimationDelay.TIMEOUT);
-  }
-};
+ol.render.webgl.ImageReplay.prototype.drawGeometryCollectionGeometry =
+    goog.abstractMethod;
 
 
 /**
- * Stops the delay timer if it is active. No action is taken if the timer is not
- * in use.
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.stop = function() {
-  if (this.isActive()) {
-    var raf = this.getRaf_();
-    var cancelRaf = this.getCancelRaf_();
-    if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
-      goog.events.unlistenByKey(this.id_);
-    } else if (raf && cancelRaf) {
-      cancelRaf.call(this.win_, /** @type {number} */ (this.id_));
-    } else {
-      this.win_.clearTimeout(/** @type {number} */ (this.id_));
-    }
-  }
-  this.id_ = null;
-};
+ol.render.webgl.ImageReplay.prototype.drawLineStringGeometry =
+    goog.abstractMethod;
 
 
 /**
- * Fires delay's action even if timer has already gone off or has not been
- * started yet; guarantees action firing. Stops the delay timer.
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.fire = function() {
-  this.stop();
-  this.doAction_();
-};
+ol.render.webgl.ImageReplay.prototype.drawMultiLineStringGeometry =
+    goog.abstractMethod;
 
 
 /**
- * Fires delay's action only if timer is currently active. Stops the delay
- * timer.
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.fireIfActive = function() {
-  if (this.isActive()) {
-    this.fire();
-  }
+ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry =
+    function(multiPointGeometry, feature) {
+  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
+  var stride = multiPointGeometry.getStride();
+  this.drawCoordinates_(
+      flatCoordinates, 0, flatCoordinates.length, stride);
 };
 
 
 /**
- * @return {boolean} True if the delay is currently active, false otherwise.
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.isActive = function() {
-  return this.id_ != null;
+ol.render.webgl.ImageReplay.prototype.drawMultiPolygonGeometry =
+    goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.webgl.ImageReplay.prototype.drawPointGeometry =
+    function(pointGeometry, feature) {
+  var flatCoordinates = pointGeometry.getFlatCoordinates();
+  var stride = pointGeometry.getStride();
+  this.drawCoordinates_(
+      flatCoordinates, 0, flatCoordinates.length, stride);
 };
 
 
 /**
- * Invokes the callback function after the delay successfully completes.
- * @private
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.doAction_ = function() {
-  if (this.usingListeners_ && this.id_) {
-    goog.events.unlistenByKey(this.id_);
+ol.render.webgl.ImageReplay.prototype.drawPolygonGeometry = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.render.webgl.ImageReplay.prototype.drawText = goog.abstractMethod;
+
+
+/**
+ * @param {ol.webgl.Context} context Context.
+ */
+ol.render.webgl.ImageReplay.prototype.finish = function(context) {
+  var gl = context.getGL();
+
+  this.groupIndices_.push(this.indices_.length);
+  goog.asserts.assert(this.images_.length == this.groupIndices_.length);
+
+  // create, bind, and populate the vertices buffer
+  this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_);
+  context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_);
+
+  var indices = this.indices_;
+  var bits = context.hasOESElementIndexUint ? 32 : 16;
+  goog.asserts.assert(indices[indices.length - 1] < Math.pow(2, bits),
+      'Too large element index detected [%s] (OES_element_index_uint "%s")',
+      indices[indices.length - 1], context.hasOESElementIndexUint);
+
+  // create, bind, and populate the indices buffer
+  this.indicesBuffer_ = new ol.webgl.Buffer(indices);
+  context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
+
+  goog.asserts.assert(this.textures_.length === 0);
+
+  // create textures
+  var texture, image, uid;
+  /** @type {Object.<string, WebGLTexture>} */
+  var texturePerImage = {};
+  var i;
+  var ii = this.images_.length;
+  for (i = 0; i < ii; ++i) {
+    image = this.images_[i];
+
+    uid = goog.getUid(image).toString();
+    if (goog.object.containsKey(texturePerImage, uid)) {
+      texture = goog.object.get(texturePerImage, uid);
+    } else {
+      texture = gl.createTexture();
+      gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
+      gl.texParameteri(goog.webgl.TEXTURE_2D,
+          goog.webgl.TEXTURE_WRAP_S, goog.webgl.CLAMP_TO_EDGE);
+      gl.texParameteri(goog.webgl.TEXTURE_2D,
+          goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE);
+      gl.texParameteri(goog.webgl.TEXTURE_2D,
+          goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR);
+      gl.texParameteri(goog.webgl.TEXTURE_2D,
+          goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR);
+      gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA,
+          goog.webgl.UNSIGNED_BYTE, image);
+      goog.object.set(texturePerImage, uid, texture);
+    }
+    this.textures_[i] = texture;
   }
-  this.id_ = null;
 
-  // We are not using the timestamp returned by requestAnimationFrame
-  // because it may be either a Date.now-style time or a
-  // high-resolution time (depending on browser implementation). Using
-  // goog.now() will ensure that the timestamp used is consistent and
-  // compatible with goog.fx.Animation.
-  this.listener_.call(this.handler_, goog.now());
+  goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
+
+  this.anchorX_ = undefined;
+  this.anchorY_ = undefined;
+  this.height_ = undefined;
+  this.images_ = null;
+  this.imageHeight_ = undefined;
+  this.imageWidth_ = undefined;
+  this.indices_ = null;
+  this.opacity_ = undefined;
+  this.originX_ = undefined;
+  this.originY_ = undefined;
+  this.rotateWithView_ = undefined;
+  this.rotation_ = undefined;
+  this.scale_ = undefined;
+  this.vertices_ = null;
+  this.width_ = undefined;
 };
 
 
-/** @override */
-goog.async.AnimationDelay.prototype.disposeInternal = function() {
-  this.stop();
-  goog.async.AnimationDelay.base(this, 'disposeInternal');
+/**
+ * @param {ol.webgl.Context} context Context.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Size} size Size.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {number} opacity Global opacity.
+ * @param {number} brightness Global brightness.
+ * @param {number} contrast Global contrast.
+ * @param {number} hue Global hue.
+ * @param {number} saturation Global saturation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.webgl.ImageReplay.prototype.replay = function(context,
+    center, resolution, rotation, size, pixelRatio,
+    opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
+  var gl = context.getGL();
+
+  // bind the vertices buffer
+  goog.asserts.assert(!goog.isNull(this.verticesBuffer_));
+  context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_);
+
+  // bind the indices buffer
+  goog.asserts.assert(!goog.isNull(this.indicesBuffer_));
+  context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
+
+  var useColor = brightness || contrast != 1 || hue || saturation != 1;
+
+  // get the program
+  var fragmentShader, vertexShader;
+  if (useColor) {
+    fragmentShader =
+        ol.render.webgl.imagereplay.shader.ColorFragment.getInstance();
+    vertexShader =
+        ol.render.webgl.imagereplay.shader.ColorVertex.getInstance();
+  } else {
+    fragmentShader =
+        ol.render.webgl.imagereplay.shader.DefaultFragment.getInstance();
+    vertexShader =
+        ol.render.webgl.imagereplay.shader.DefaultVertex.getInstance();
+  }
+  var program = context.getProgram(fragmentShader, vertexShader);
+
+  // get the locations
+  var locations;
+  if (useColor) {
+    if (goog.isNull(this.colorLocations_)) {
+      locations =
+          new ol.render.webgl.imagereplay.shader.Color.Locations(gl, program);
+      this.colorLocations_ = locations;
+    } else {
+      locations = this.colorLocations_;
+    }
+  } else {
+    if (goog.isNull(this.defaultLocations_)) {
+      locations =
+          new ol.render.webgl.imagereplay.shader.Default.Locations(gl, program);
+      this.defaultLocations_ = locations;
+    } else {
+      locations = this.defaultLocations_;
+    }
+  }
+
+  // use the program (FIXME: use the return value)
+  context.useProgram(program);
+
+  // enable the vertex attrib arrays
+  gl.enableVertexAttribArray(locations.a_position);
+  gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT,
+      false, 32, 0);
+
+  gl.enableVertexAttribArray(locations.a_offsets);
+  gl.vertexAttribPointer(locations.a_offsets, 2, goog.webgl.FLOAT,
+      false, 32, 8);
+
+  gl.enableVertexAttribArray(locations.a_texCoord);
+  gl.vertexAttribPointer(locations.a_texCoord, 2, goog.webgl.FLOAT,
+      false, 32, 16);
+
+  gl.enableVertexAttribArray(locations.a_opacity);
+  gl.vertexAttribPointer(locations.a_opacity, 1, goog.webgl.FLOAT,
+      false, 32, 24);
+
+  gl.enableVertexAttribArray(locations.a_rotateWithView);
+  gl.vertexAttribPointer(locations.a_rotateWithView, 1, goog.webgl.FLOAT,
+      false, 32, 28);
+
+  // set the "uniform" values
+  var projectionMatrix = this.projectionMatrix_;
+  ol.vec.Mat4.makeTransform2D(projectionMatrix,
+      0.0, 0.0,
+      2 / (resolution * size[0]),
+      2 / (resolution * size[1]),
+      -rotation,
+      -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1]));
+
+  var offsetScaleMatrix = this.offsetScaleMatrix_;
+  goog.vec.Mat4.makeScale(offsetScaleMatrix, 2 / size[0], 2 / size[1], 1);
+
+  var offsetRotateMatrix = this.offsetRotateMatrix_;
+  goog.vec.Mat4.makeIdentity(offsetRotateMatrix);
+  if (rotation !== 0) {
+    goog.vec.Mat4.rotateZ(offsetRotateMatrix, -rotation);
+  }
+
+  gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix);
+  gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, offsetScaleMatrix);
+  gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false,
+      offsetRotateMatrix);
+  gl.uniform1f(locations.u_opacity, opacity);
+  if (useColor) {
+    gl.uniformMatrix4fv(locations.u_colorMatrix, false,
+        this.colorMatrix_.getMatrix(brightness, contrast, hue, saturation));
+  }
+
+  // draw!
+  goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
+  var i, ii, start;
+  for (i = 0, ii = this.textures_.length, start = 0; i < ii; ++i) {
+    gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]);
+    var end = this.groupIndices_[i];
+    var numItems = end - start;
+    var offsetInBytes = start * (context.hasOESElementIndexUint ? 4 : 2);
+    var elementType = context.hasOESElementIndexUint ?
+        goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT;
+    gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
+    start = end;
+  }
+
+  // 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 {?function(function(number)): number} The requestAnimationFrame
- *     function, or null if not available on this browser.
- * @private
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.getRaf_ = function() {
-  var win = this.win_;
-  return win.requestAnimationFrame ||
-      win.webkitRequestAnimationFrame ||
-      win.mozRequestAnimationFrame ||
-      win.oRequestAnimationFrame ||
-      win.msRequestAnimationFrame ||
-      null;
-};
+ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = goog.abstractMethod;
 
 
 /**
- * @return {?function(number): number} The cancelAnimationFrame function,
- *     or null if not available on this browser.
- * @private
+ * @inheritDoc
  */
-goog.async.AnimationDelay.prototype.getCancelRaf_ = function() {
-  var win = this.win_;
-  return win.cancelRequestAnimationFrame ||
-      win.webkitCancelRequestAnimationFrame ||
-      win.mozCancelRequestAnimationFrame ||
-      win.oCancelRequestAnimationFrame ||
-      win.msCancelRequestAnimationFrame ||
-      null;
+ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) {
+  var anchor = imageStyle.getAnchor();
+  goog.asserts.assert(!goog.isNull(anchor));
+  var image = imageStyle.getImage(1);
+  goog.asserts.assert(!goog.isNull(image));
+  var imageSize = imageStyle.getImageSize();
+  goog.asserts.assert(!goog.isNull(imageSize));
+  var opacity = imageStyle.getOpacity();
+  goog.asserts.assert(goog.isDef(opacity));
+  var origin = imageStyle.getOrigin();
+  goog.asserts.assert(!goog.isNull(origin));
+  var rotateWithView = imageStyle.getRotateWithView();
+  goog.asserts.assert(goog.isDef(rotateWithView));
+  var rotation = imageStyle.getRotation();
+  goog.asserts.assert(goog.isDef(rotation));
+  var size = imageStyle.getSize();
+  goog.asserts.assert(!goog.isNull(size));
+  var scale = imageStyle.getScale();
+  goog.asserts.assert(goog.isDef(scale));
+
+  if (this.images_.length === 0) {
+    this.images_.push(image);
+  } else {
+    var currentImage = this.images_[this.images_.length - 1];
+    if (goog.getUid(currentImage) != goog.getUid(image)) {
+      this.groupIndices_.push(this.indices_.length);
+      goog.asserts.assert(this.groupIndices_.length == this.images_.length);
+      this.images_.push(image);
+    }
+  }
+
+  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];
 };
 
-// Copyright 2007 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Definition the goog.debug.RelativeTimeProvider class.
- *
+ * @inheritDoc
  */
-
-goog.provide('goog.debug.RelativeTimeProvider');
+ol.render.webgl.ImageReplay.prototype.setTextStyle = goog.abstractMethod;
 
 
 
 /**
- * A simple object to keep track of a timestamp considered the start of
- * something. The main use is for the logger system to maintain a start time
- * that is occasionally reset. For example, in Gmail, we reset this relative
- * time at the start of a user action so that timings are offset from the
- * beginning of the action. This class also provides a singleton as the default
- * behavior for most use cases is to share the same start time.
- *
  * @constructor
- * @final
+ * @implements {ol.render.IReplayGroup}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @struct
  */
-goog.debug.RelativeTimeProvider = function() {
+ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) {
+
+  /**
+   * @type {ol.Extent}
+   * @private
+   */
+  this.maxExtent_ = maxExtent;
+
   /**
-   * The start time.
    * @type {number}
    * @private
    */
-  this.relativeTimeStart_ = goog.now();
+  this.tolerance_ = tolerance;
+
+  /**
+   * ImageReplay only is supported at this point.
+   * @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>}
+   * @private
+   */
+  this.replays_ = {};
+
 };
 
 
 /**
- * Default instance.
- * @type {goog.debug.RelativeTimeProvider}
- * @private
+ * @param {ol.webgl.Context} context WebGL context.
+ * @return {function()} Delete resources function.
  */
-goog.debug.RelativeTimeProvider.defaultInstance_ =
-    new goog.debug.RelativeTimeProvider();
+ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction =
+    function(context) {
+  var functions = [];
+  var replayKey;
+  for (replayKey in this.replays_) {
+    functions.push(
+        this.replays_[replayKey].getDeleteResourcesFunction(context));
+  }
+  return goog.functions.sequence.apply(null, functions);
+};
 
 
 /**
- * Sets the start time to the specified time.
- * @param {number} timeStamp The start time.
+ * @param {ol.webgl.Context} context Context.
  */
-goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
-  this.relativeTimeStart_ = timeStamp;
+ol.render.webgl.ReplayGroup.prototype.finish = function(context) {
+  var replayKey;
+  for (replayKey in this.replays_) {
+    this.replays_[replayKey].finish(context);
+  }
 };
 
 
 /**
- * Resets the start time to now.
+ * @inheritDoc
  */
-goog.debug.RelativeTimeProvider.prototype.reset = function() {
-  this.set(goog.now());
+ol.render.webgl.ReplayGroup.prototype.getReplay =
+    function(zIndex, replayType) {
+  var replay = this.replays_[replayType];
+  if (!goog.isDef(replay)) {
+    var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType];
+    goog.asserts.assert(goog.isDef(constructor));
+    replay = new constructor(this.tolerance_, this.maxExtent_);
+    this.replays_[replayType] = replay;
+  }
+  return replay;
 };
 
 
 /**
- * @return {number} The start time.
+ * @inheritDoc
  */
-goog.debug.RelativeTimeProvider.prototype.get = function() {
-  return this.relativeTimeStart_;
+ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
+  return goog.object.isEmpty(this.replays_);
 };
 
 
 /**
- * @return {goog.debug.RelativeTimeProvider} The default instance.
+ * @param {ol.webgl.Context} context Context.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Size} size Size.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {number} opacity Global opacity.
+ * @param {number} brightness Global brightness.
+ * @param {number} contrast Global contrast.
+ * @param {number} hue Global hue.
+ * @param {number} saturation Global saturation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @return {T|undefined} Callback result.
+ * @template T
  */
-goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
-  return goog.debug.RelativeTimeProvider.defaultInstance_;
+ol.render.webgl.ReplayGroup.prototype.replay = function(context,
+    center, resolution, rotation, size, pixelRatio,
+    opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
+  var i, ii, replay, result;
+  for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) {
+    replay = this.replays_[ol.render.REPLAY_ORDER[i]];
+    if (goog.isDef(replay)) {
+      result = replay.replay(context,
+          center, resolution, rotation, size, pixelRatio,
+          opacity, brightness, contrast, hue, saturation, skippedFeaturesHash);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return undefined;
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Definition of various formatters for logging. Please minimize
- * dependencies this file has on other closure classes as any dependency it
- * takes won't be able to use the logging infrastructure.
- *
+ * @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
+};
 
-goog.provide('goog.debug.Formatter');
-goog.provide('goog.debug.HtmlFormatter');
-goog.provide('goog.debug.TextFormatter');
-
-goog.require('goog.debug.RelativeTimeProvider');
-goog.require('goog.string');
+goog.provide('ol.render.webgl.Immediate');
+goog.require('goog.array');
+goog.require('goog.object');
+goog.require('ol.extent');
+goog.require('ol.render.webgl.ReplayGroup');
 
 
 
 /**
- * Base class for Formatters. A Formatter is used to format a LogRecord into
- * something that can be displayed to the user.
- *
- * @param {string=} opt_prefix The prefix to place before text records.
  * @constructor
+ * @implements {ol.render.IVectorContext}
+ * @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
  */
-goog.debug.Formatter = function(opt_prefix) {
-  this.prefix_ = opt_prefix || '';
+ol.render.webgl.Immediate = function(context,
+    center, resolution, rotation, size, extent, pixelRatio) {
 
   /**
-   * A provider that returns the relative start time.
-   * @type {goog.debug.RelativeTimeProvider}
    * @private
    */
-  this.startTimeProvider_ =
-      goog.debug.RelativeTimeProvider.getDefaultInstance();
-};
+  this.context_ = context;
 
+  /**
+   * @private
+   */
+  this.center_ = center;
 
-/**
- * Whether to append newlines to the end of formatted log records.
- * @type {boolean}
- */
-goog.debug.Formatter.prototype.appendNewline = true;
+  /**
+   * @private
+   */
+  this.extent_ = extent;
+
+  /**
+   * @private
+   */
+  this.pixelRatio_ = pixelRatio;
+
+  /**
+   * @private
+   */
+  this.size_ = size;
+
+  /**
+   * @private
+   */
+  this.rotation_ = rotation;
+
+  /**
+   * @private
+   */
+  this.resolution_ = resolution;
+
+  /**
+   * @private
+   * @type {ol.style.Image}
+   */
+  this.imageStyle_ = null;
+
+  /**
+   * @private
+   * @type {Object.<string,
+   *        Array.<function(ol.render.webgl.Immediate)>>}
+   */
+  this.callbacksByZIndex_ = {};
+};
 
 
 /**
- * Whether to show absolute time in the DebugWindow.
- * @type {boolean}
+ * FIXME: empty description for jsdoc
  */
-goog.debug.Formatter.prototype.showAbsoluteTime = true;
+ol.render.webgl.Immediate.prototype.flush = function() {
+  /** @type {Array.<number>} */
+  var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number);
+  goog.array.sort(zs);
+  var i, ii, callbacks, j, jj;
+  for (i = 0, ii = zs.length; i < ii; ++i) {
+    callbacks = this.callbacksByZIndex_[zs[i].toString()];
+    for (j = 0, jj = callbacks.length; j < jj; ++j) {
+      callbacks[j](this);
+    }
+  }
+};
 
 
 /**
- * Whether to show relative time in the DebugWindow.
- * @type {boolean}
+ * @param {number} zIndex Z index.
+ * @param {function(ol.render.webgl.Immediate)} callback Callback.
+ * @api
  */
-goog.debug.Formatter.prototype.showRelativeTime = true;
+ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) {
+  var zIndexKey = zIndex.toString();
+  var callbacks = this.callbacksByZIndex_[zIndexKey];
+  if (goog.isDef(callbacks)) {
+    callbacks.push(callback);
+  } else {
+    this.callbacksByZIndex_[zIndexKey] = [callback];
+  }
+};
 
 
 /**
- * Whether to show the logger name in the DebugWindow.
- * @type {boolean}
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.prototype.showLoggerName = true;
+ol.render.webgl.Immediate.prototype.drawCircleGeometry =
+    function(circleGeometry, data) {
+};
 
 
 /**
- * Whether to show the logger exception text.
- * @type {boolean}
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.prototype.showExceptionText = false;
+ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) {
+  var geometry = style.getGeometryFunction()(feature);
+  if (!goog.isDefAndNotNull(geometry) ||
+      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
+    return;
+  }
+  var zIndex = style.getZIndex();
+  if (!goog.isDef(zIndex)) {
+    zIndex = 0;
+  }
+  this.drawAsync(zIndex, function(render) {
+    render.setFillStrokeStyle(style.getFill(), style.getStroke());
+    render.setImageStyle(style.getImage());
+    render.setTextStyle(style.getText());
+    var type = geometry.getType();
+    var renderGeometry = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_[type];
+    // Do not assert since all kinds of geometries are not handled yet.
+    // In spite, render what we support.
+    if (renderGeometry) {
+      renderGeometry.call(render, geometry, null);
+    }
+  });
+};
 
 
 /**
- * Whether to show the severity level.
- * @type {boolean}
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.prototype.showSeverityLevel = false;
+ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry =
+    function(geometryCollectionGeometry, data) {
+  var geometries = geometryCollectionGeometry.getGeometriesArray();
+  var renderers = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_;
+  var i, ii;
+  for (i = 0, ii = geometries.length; i < ii; ++i) {
+    var geometry = geometries[i];
+    var geometryRenderer = renderers[geometry.getType()];
+    // Do not assert since all kinds of geometries are not handled yet.
+    // In order to support hierarchies, delegate instead what we can to
+    // valid renderers.
+    if (geometryRenderer) {
+      geometryRenderer.call(this, geometry, data);
+    }
+  }
+};
 
 
 /**
- * Formats a record.
- * @param {goog.debug.LogRecord} logRecord the logRecord to format.
- * @return {string} The formatted string.
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
+ol.render.webgl.Immediate.prototype.drawPointGeometry =
+    function(pointGeometry, data) {
+  var context = this.context_;
+  var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
+  var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE);
+  replay.setImageStyle(this.imageStyle_);
+  replay.drawPointGeometry(pointGeometry, data);
+  replay.finish(context);
+  // default colors
+  var opacity = 1;
+  var brightness = 0;
+  var contrast = 1;
+  var hue = 0;
+  var saturation = 1;
+  replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
+      this.size_, this.extent_, this.pixelRatio_, opacity, brightness,
+      contrast, hue, saturation, {});
+  replay.getDeleteResourcesFunction(context)();
+};
 
 
 /**
- * Sets the start time provider. By default, this is the default instance
- * but can be changed.
- * @param {goog.debug.RelativeTimeProvider} provider The provider to use.
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
-  this.startTimeProvider_ = provider;
+ol.render.webgl.Immediate.prototype.drawLineStringGeometry =
+    function(lineStringGeometry, data) {
 };
 
 
 /**
- * Returns the start time provider. By default, this is the default instance
- * but can be changed.
- * @return {goog.debug.RelativeTimeProvider} The start time provider.
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.prototype.getStartTimeProvider = function() {
-  return this.startTimeProvider_;
+ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry =
+    function(multiLineStringGeometry, data) {
 };
 
 
 /**
- * Resets the start relative time.
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
-  this.startTimeProvider_.reset();
+ol.render.webgl.Immediate.prototype.drawMultiPointGeometry =
+    function(multiPointGeometry, data) {
+  var context = this.context_;
+  var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
+  var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE);
+  replay.setImageStyle(this.imageStyle_);
+  replay.drawMultiPointGeometry(multiPointGeometry, data);
+  replay.finish(context);
+  // default colors
+  var opacity = 1;
+  var brightness = 0;
+  var contrast = 1;
+  var hue = 0;
+  var saturation = 1;
+  replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
+      this.size_, this.extent_, this.pixelRatio_, opacity, brightness,
+      contrast, hue, saturation, {});
+  replay.getDeleteResourcesFunction(context)();
 };
 
 
 /**
- * Returns a string for the time/date of the LogRecord.
- * @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
- * @return {string} A string representation of the time/date of the LogRecord.
- * @private
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
-  var time = new Date(logRecord.getMillis());
-  return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
-         goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
-         goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
-         goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
-         goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
-         goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
-         goog.debug.Formatter.getTwoDigitString_(
-             Math.floor(time.getMilliseconds() / 10));
+ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry =
+    function(multiPolygonGeometry, data) {
 };
 
 
 /**
- * Returns the number as a two-digit string, meaning it prepends a 0 if the
- * number if less than 10.
- * @param {number} n The number to format.
- * @return {string} A two-digit string representation of {@code n}.
- * @private
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.getTwoDigitString_ = function(n) {
-  if (n < 10) {
-    return '0' + n;
-  }
-  return String(n);
+ol.render.webgl.Immediate.prototype.drawPolygonGeometry =
+    function(polygonGeometry, data) {
 };
 
 
 /**
- * Returns a string for the number of seconds relative to the start time.
- * Prepads with spaces so that anything less than 1000 seconds takes up the
- * same number of characters for better formatting.
- * @param {goog.debug.LogRecord} logRecord The log to compare time to.
- * @param {number} relativeTimeStart The start time to compare to.
- * @return {string} The number of seconds of the LogRecord relative to the
- *     start time.
- * @private
+ * @inheritDoc
+ * @api
  */
-goog.debug.Formatter.getRelativeTime_ = function(logRecord,
-                                                 relativeTimeStart) {
-  var ms = logRecord.getMillis() - relativeTimeStart;
-  var sec = ms / 1000;
-  var str = sec.toFixed(3);
-
-  var spacesToPrepend = 0;
-  if (sec < 1) {
-    spacesToPrepend = 2;
-  } else {
-    while (sec < 100) {
-      spacesToPrepend++;
-      sec *= 10;
-    }
-  }
-  while (spacesToPrepend-- > 0) {
-    str = ' ' + str;
-  }
-  return str;
+ol.render.webgl.Immediate.prototype.drawText =
+    function(flatCoordinates, offset, end, stride, geometry, data) {
 };
 
 
-
 /**
- * Formatter that returns formatted html. See formatRecord for the classes
- * it uses for various types of formatted output.
- *
- * @param {string=} opt_prefix The prefix to place before text records.
- * @constructor
- * @extends {goog.debug.Formatter}
+ * @inheritDoc
+ * @api
  */
-goog.debug.HtmlFormatter = function(opt_prefix) {
-  goog.debug.Formatter.call(this, opt_prefix);
+ol.render.webgl.Immediate.prototype.setFillStrokeStyle =
+    function(fillStyle, strokeStyle) {
 };
-goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
 
 
 /**
- * Whether to show the logger exception text
- * @type {boolean}
- * @override
+ * @inheritDoc
+ * @api
  */
-goog.debug.HtmlFormatter.prototype.showExceptionText = true;
+ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
+  this.imageStyle_ = imageStyle;
+};
 
 
 /**
- * Formats a record
- * @param {goog.debug.LogRecord} logRecord the logRecord to format.
- * @return {string} The formatted string as html.
- * @override
+ * @inheritDoc
+ * @api
  */
-goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
-  var className;
-  switch (logRecord.getLevel().value) {
-    case goog.debug.Logger.Level.SHOUT.value:
-      className = 'dbg-sh';
-      break;
-    case goog.debug.Logger.Level.SEVERE.value:
-      className = 'dbg-sev';
-      break;
-    case goog.debug.Logger.Level.WARNING.value:
-      className = 'dbg-w';
-      break;
-    case goog.debug.Logger.Level.INFO.value:
-      className = 'dbg-i';
-      break;
-    case goog.debug.Logger.Level.FINE.value:
-    default:
-      className = 'dbg-f';
-      break;
-  }
+ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) {
+};
 
-  // Build message html
-  var sb = [];
-  sb.push(this.prefix_, ' ');
-  if (this.showAbsoluteTime) {
-    sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
-  }
-  if (this.showRelativeTime) {
-    sb.push('[',
-        goog.string.whitespaceEscape(
-            goog.debug.Formatter.getRelativeTime_(logRecord,
-                this.startTimeProvider_.get())),
-        's] ');
-  }
 
-  if (this.showLoggerName) {
-    sb.push('[', goog.string.htmlEscape(logRecord.getLoggerName()), '] ');
-  }
-  if (this.showSeverityLevel) {
-    sb.push('[', goog.string.htmlEscape(logRecord.getLevel().name), '] ');
-  }
-  sb.push('<span class="', className, '">',
-      goog.string.newLineToBr(goog.string.whitespaceEscape(
-          goog.string.htmlEscape(logRecord.getMessage()))));
+/**
+ * @const
+ * @private
+ * @type {Object.<ol.geom.GeometryType,
+ *                function(this: ol.render.webgl.Immediate, ol.geom.Geometry,
+ *                         Object)>}
+ */
+ol.render.webgl.Immediate.GEOMETRY_RENDERERS_ = {
+  'Point': ol.render.webgl.Immediate.prototype.drawPointGeometry,
+  'MultiPoint': ol.render.webgl.Immediate.prototype.drawMultiPointGeometry,
+  'GeometryCollection':
+      ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry
+};
 
-  if (this.showExceptionText && logRecord.getException()) {
-    sb.push('<br>',
-        goog.string.newLineToBr(goog.string.whitespaceEscape(
-            logRecord.getExceptionText() || '')));
-  }
-  sb.push('</span>');
-  if (this.appendNewline) {
-    sb.push('<br>');
-  }
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.map.shader.Color');
 
-  return sb.join('');
-};
+goog.require('ol.webgl.shader');
 
 
 
 /**
- * Formatter that returns formatted plain text
- *
- * @param {string=} opt_prefix The prefix to place before text records.
  * @constructor
- * @extends {goog.debug.Formatter}
- * @final
+ * @extends {ol.webgl.shader.Fragment}
+ * @struct
  */
-goog.debug.TextFormatter = function(opt_prefix) {
-  goog.debug.Formatter.call(this, opt_prefix);
+ol.renderer.webgl.map.shader.ColorFragment = function() {
+  goog.base(this, ol.renderer.webgl.map.shader.ColorFragment.SOURCE);
 };
-goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);
+goog.inherits(ol.renderer.webgl.map.shader.ColorFragment, ol.webgl.shader.Fragment);
+goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorFragment);
 
 
 /**
- * Formats a record as text
- * @param {goog.debug.LogRecord} logRecord the logRecord to format.
- * @return {string} The formatted string.
- * @override
+ * @const
+ * @type {string}
  */
-goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
-  // Build message html
-  var sb = [];
-  sb.push(this.prefix_, ' ');
-  if (this.showAbsoluteTime) {
-    sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
-  }
-  if (this.showRelativeTime) {
-    sb.push('[', goog.debug.Formatter.getRelativeTime_(logRecord,
-        this.startTimeProvider_.get()), 's] ');
-  }
-
-  if (this.showLoggerName) {
-    sb.push('[', logRecord.getLoggerName(), '] ');
-  }
-  if (this.showSeverityLevel) {
-    sb.push('[', logRecord.getLevel().name, '] ');
-  }
-  sb.push(logRecord.getMessage());
-  if (this.showExceptionText && logRecord.getException()) {
-    sb.push('\n', logRecord.getExceptionText());
-  }
-  if (this.appendNewline) {
-    sb.push('\n');
-  }
-  return sb.join('');
-};
+ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_texture, v_texCoord);\n  gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n  gl_FragColor.a = texColor.a * u_opacity;\n}\n';
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Simple logger that logs to the window console if available.
- *
- * Has an autoInstall option which can be put into initialization code, which
- * will start logging if "Debug=true" is in document.location.href
- *
+ * @const
+ * @type {string}
  */
+ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}';
 
-goog.provide('goog.debug.Console');
 
-goog.require('goog.debug.LogManager');
-goog.require('goog.debug.Logger.Level');
-goog.require('goog.debug.TextFormatter');
+/**
+ * @const
+ * @type {string}
+ */
+ol.renderer.webgl.map.shader.ColorFragment.SOURCE = goog.DEBUG ?
+    ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE :
+    ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE;
 
 
 
 /**
- * Create and install a log handler that logs to window.console if available
  * @constructor
+ * @extends {ol.webgl.shader.Vertex}
+ * @struct
  */
-goog.debug.Console = function() {
-  this.publishHandler_ = goog.bind(this.addLogRecord, this);
-
-  /**
-   * Formatter for formatted output.
-   * @type {!goog.debug.TextFormatter}
-   * @private
-   */
-  this.formatter_ = new goog.debug.TextFormatter();
-  this.formatter_.showAbsoluteTime = false;
-  this.formatter_.showExceptionText = false;
-  // The console logging methods automatically append a newline.
-  this.formatter_.appendNewline = false;
+ol.renderer.webgl.map.shader.ColorVertex = function() {
+  goog.base(this, ol.renderer.webgl.map.shader.ColorVertex.SOURCE);
+};
+goog.inherits(ol.renderer.webgl.map.shader.ColorVertex, ol.webgl.shader.Vertex);
+goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorVertex);
 
-  this.isCapturing_ = false;
-  this.logBuffer_ = '';
 
-  /**
-   * Loggers that we shouldn't output.
-   * @type {!Object.<boolean>}
-   * @private
-   */
-  this.filteredLoggers_ = {};
-};
+/**
+ * @const
+ * @type {string}
+ */
+ol.renderer.webgl.map.shader.ColorVertex.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';
 
 
 /**
- * Returns the text formatter used by this console
- * @return {!goog.debug.TextFormatter} The text formatter.
+ * @const
+ * @type {string}
  */
-goog.debug.Console.prototype.getFormatter = function() {
-  return this.formatter_;
-};
+ol.renderer.webgl.map.shader.ColorVertex.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;}';
 
 
 /**
- * Sets whether we are currently capturing logger output.
- * @param {boolean} capturing Whether to capture logger output.
+ * @const
+ * @type {string}
  */
-goog.debug.Console.prototype.setCapturing = function(capturing) {
-  if (capturing == this.isCapturing_) {
-    return;
-  }
+ol.renderer.webgl.map.shader.ColorVertex.SOURCE = goog.DEBUG ?
+    ol.renderer.webgl.map.shader.ColorVertex.DEBUG_SOURCE :
+    ol.renderer.webgl.map.shader.ColorVertex.OPTIMIZED_SOURCE;
 
-  // attach or detach handler from the root logger
-  var rootLogger = goog.debug.LogManager.getRoot();
-  if (capturing) {
-    rootLogger.addHandler(this.publishHandler_);
-  } else {
-    rootLogger.removeHandler(this.publishHandler_);
-    this.logBuffer = '';
-  }
-  this.isCapturing_ = capturing;
-};
 
 
 /**
- * Adds a log record.
- * @param {goog.debug.LogRecord} logRecord The log entry.
+ * @constructor
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-goog.debug.Console.prototype.addLogRecord = function(logRecord) {
+ol.renderer.webgl.map.shader.Color.Locations = function(gl, program) {
 
-  // Check to see if the log record is filtered or not.
-  if (this.filteredLoggers_[logRecord.getLoggerName()]) {
-    return;
-  }
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_colorMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_colorMatrix' : 'f');
 
-  var record = this.formatter_.formatRecord(logRecord);
-  var console = goog.debug.Console.console_;
-  if (console) {
-    switch (logRecord.getLevel()) {
-      case goog.debug.Logger.Level.SHOUT:
-        goog.debug.Console.logToConsole_(console, 'info', record);
-        break;
-      case goog.debug.Logger.Level.SEVERE:
-        goog.debug.Console.logToConsole_(console, 'error', record);
-        break;
-      case goog.debug.Logger.Level.WARNING:
-        goog.debug.Console.logToConsole_(console, 'warn', record);
-        break;
-      default:
-        goog.debug.Console.logToConsole_(console, 'debug', record);
-        break;
-    }
-  } else {
-    this.logBuffer_ += record;
-  }
-};
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_opacity = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_opacity' : 'g');
 
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_projectionMatrix' : 'e');
 
-/**
- * Adds a logger name to be filtered.
- * @param {string} loggerName the logger name to add.
- */
-goog.debug.Console.prototype.addFilter = function(loggerName) {
-  this.filteredLoggers_[loggerName] = true;
-};
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_texCoordMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_texCoordMatrix' : 'd');
 
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_texture = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_texture' : 'h');
 
-/**
- * Removes a logger name to be filtered.
- * @param {string} loggerName the logger name to remove.
- */
-goog.debug.Console.prototype.removeFilter = function(loggerName) {
-  delete this.filteredLoggers_[loggerName];
+  /**
+   * @type {number}
+   */
+  this.a_position = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_position' : 'b');
+
+  /**
+   * @type {number}
+   */
+  this.a_texCoord = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_texCoord' : 'c');
 };
 
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.map.shader.Default');
+
+goog.require('ol.webgl.shader');
+
+
 
 /**
- * Global console logger instance
- * @type {goog.debug.Console}
+ * @constructor
+ * @extends {ol.webgl.shader.Fragment}
+ * @struct
  */
-goog.debug.Console.instance = null;
+ol.renderer.webgl.map.shader.DefaultFragment = function() {
+  goog.base(this, ol.renderer.webgl.map.shader.DefaultFragment.SOURCE);
+};
+goog.inherits(ol.renderer.webgl.map.shader.DefaultFragment, ol.webgl.shader.Fragment);
+goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultFragment);
 
 
 /**
- * The console to which to log.  This is a property so it can be mocked out in
- * this unit test for goog.debug.Console. Using goog.global, as console might be
- * used in window-less contexts.
- * @type {Object}
- * @private
+ * @const
+ * @type {string}
  */
-goog.debug.Console.console_ = goog.global['console'];
+ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_texture, v_texCoord);\n  gl_FragColor.rgb = texColor.rgb;\n  gl_FragColor.a = texColor.a * u_opacity;\n}\n';
 
 
 /**
- * Sets the console to which to log.
- * @param {!Object} console The console to which to log.
+ * @const
+ * @type {string}
  */
-goog.debug.Console.setConsole = function(console) {
-  goog.debug.Console.console_ = console;
-};
+ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}';
 
 
 /**
- * Install the console and start capturing if "Debug=true" is in the page URL
+ * @const
+ * @type {string}
  */
-goog.debug.Console.autoInstall = function() {
-  if (!goog.debug.Console.instance) {
-    goog.debug.Console.instance = new goog.debug.Console();
-  }
+ol.renderer.webgl.map.shader.DefaultFragment.SOURCE = goog.DEBUG ?
+    ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE :
+    ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE;
 
-  if (goog.global.location &&
-      goog.global.location.href.indexOf('Debug=true') != -1) {
-    goog.debug.Console.instance.setCapturing(true);
-  }
-};
 
 
 /**
- * Show an alert with all of the captured debug information.
- * Information is only captured if console is not available
+ * @constructor
+ * @extends {ol.webgl.shader.Vertex}
+ * @struct
  */
-goog.debug.Console.show = function() {
-  alert(goog.debug.Console.instance.logBuffer_);
+ol.renderer.webgl.map.shader.DefaultVertex = function() {
+  goog.base(this, ol.renderer.webgl.map.shader.DefaultVertex.SOURCE);
 };
+goog.inherits(ol.renderer.webgl.map.shader.DefaultVertex, ol.webgl.shader.Vertex);
+goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultVertex);
 
 
 /**
- * Logs the record to the console using the given function.  If the function is
- * not available on the console object, the log function is used instead.
- * @param {!Object} console The console object.
- * @param {string} fnName The name of the function to use.
- * @param {string} record The record to log.
- * @private
+ * @const
+ * @type {string}
  */
-goog.debug.Console.logToConsole_ = function(console, fnName, record) {
-  if (console[fnName]) {
-    console[fnName](record);
-  } else {
-    console.log(record);
-  }
-};
+ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n  v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n';
 
-// Copyright 2007 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Utility class that monitors viewport size changes.
- *
- * @author attila@google.com (Attila Bodis)
- * @see ../demos/viewportsizemonitor.html
+ * @const
+ * @type {string}
  */
+ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}';
 
-goog.provide('goog.dom.ViewportSizeMonitor');
 
-goog.require('goog.dom');
-goog.require('goog.events');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
-goog.require('goog.math.Size');
+/**
+ * @const
+ * @type {string}
+ */
+ol.renderer.webgl.map.shader.DefaultVertex.SOURCE = goog.DEBUG ?
+    ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE :
+    ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE;
 
 
 
 /**
- * This class can be used to monitor changes in the viewport size.  Instances
- * dispatch a {@link goog.events.EventType.RESIZE} event when the viewport size
- * changes.  Handlers can call {@link goog.dom.ViewportSizeMonitor#getSize} to
- * get the new viewport size.
- *
- * Use this class if you want to execute resize/reflow logic each time the
- * user resizes the browser window.  This class is guaranteed to only dispatch
- * {@code RESIZE} events when the pixel dimensions of the viewport change.
- * (Internet Explorer fires resize events if any element on the page is resized,
- * even if the viewport dimensions are unchanged, which can lead to infinite
- * resize loops.)
- *
- * Example usage:
- *  <pre>
- *    var vsm = new goog.dom.ViewportSizeMonitor();
- *    goog.events.listen(vsm, goog.events.EventType.RESIZE, function(e) {
- *      alert('Viewport size changed to ' + vsm.getSize());
- *    });
- *  </pre>
- *
- * Manually verified on IE6, IE7, FF2, Opera 11, Safari 4 and Chrome.
- *
- * @param {Window=} opt_window The window to monitor; defaults to the window in
- *    which this code is executing.
  * @constructor
- * @extends {goog.events.EventTarget}
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-goog.dom.ViewportSizeMonitor = function(opt_window) {
-  goog.events.EventTarget.call(this);
-
-  // Default the window to the current window if unspecified.
-  this.window_ = opt_window || window;
-
-  // Listen for window resize events.
-  this.listenerKey_ = goog.events.listen(this.window_,
-      goog.events.EventType.RESIZE, this.handleResize_, false, this);
+ol.renderer.webgl.map.shader.Default.Locations = function(gl, program) {
 
-  // Set the initial size.
-  this.size_ = goog.dom.getViewportSize(this.window_);
-};
-goog.inherits(goog.dom.ViewportSizeMonitor, goog.events.EventTarget);
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_opacity = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_opacity' : 'f');
 
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_projectionMatrix' : 'e');
 
-/**
- * Returns a viewport size monitor for the given window.  A new one is created
- * if it doesn't exist already.  This prevents the unnecessary creation of
- * multiple spooling monitors for a window.
- * @param {Window=} opt_window The window to monitor; defaults to the window in
- *     which this code is executing.
- * @return {!goog.dom.ViewportSizeMonitor} Monitor for the given window.
- */
-goog.dom.ViewportSizeMonitor.getInstanceForWindow = function(opt_window) {
-  var currentWindow = opt_window || window;
-  var uid = goog.getUid(currentWindow);
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_texCoordMatrix = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_texCoordMatrix' : 'd');
 
-  return goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid] =
-      goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid] ||
-      new goog.dom.ViewportSizeMonitor(currentWindow);
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_texture = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_texture' : 'g');
+
+  /**
+   * @type {number}
+   */
+  this.a_position = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_position' : 'b');
+
+  /**
+   * @type {number}
+   */
+  this.a_texCoord = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_texCoord' : 'c');
 };
 
+goog.provide('ol.renderer.webgl.Layer');
+
+goog.require('goog.vec.Mat4');
+goog.require('goog.webgl');
+goog.require('ol.color.Matrix');
+goog.require('ol.layer.Layer');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.webgl.Immediate');
+goog.require('ol.renderer.Layer');
+goog.require('ol.renderer.webgl.map.shader.Color');
+goog.require('ol.renderer.webgl.map.shader.Default');
+goog.require('ol.webgl.Buffer');
+
+
 
 /**
- * Removes and disposes a viewport size monitor for the given window if one
- * exists.
- * @param {Window=} opt_window The window whose monitor should be removed;
- *     defaults to the window in which this code is executing.
+ * @constructor
+ * @extends {ol.renderer.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Layer} layer Layer.
  */
-goog.dom.ViewportSizeMonitor.removeInstanceForWindow = function(opt_window) {
-  var uid = goog.getUid(opt_window || window);
+ol.renderer.webgl.Layer = function(mapRenderer, layer) {
+
+  goog.base(this, mapRenderer, layer);
+
+  /**
+   * @private
+   * @type {ol.webgl.Buffer}
+   */
+  this.arrayBuffer_ = new ol.webgl.Buffer([
+    -1, -1, 0, 0,
+    1, -1, 1, 0,
+    -1, 1, 0, 1,
+    1, 1, 1, 1
+  ]);
+
+  /**
+   * @protected
+   * @type {WebGLTexture}
+   */
+  this.texture = null;
+
+  /**
+   * @protected
+   * @type {WebGLFramebuffer}
+   */
+  this.framebuffer = null;
+
+  /**
+   * @protected
+   * @type {number|undefined}
+   */
+  this.framebufferDimension = undefined;
+
+  /**
+   * @protected
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.texCoordMatrix = goog.vec.Mat4.createNumber();
+
+  /**
+   * @protected
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.projectionMatrix = goog.vec.Mat4.createNumberIdentity();
+
+  /**
+   * @private
+   * @type {ol.color.Matrix}
+   */
+  this.colorMatrix_ = new ol.color.Matrix();
+
+  /**
+   * @private
+   * @type {ol.renderer.webgl.map.shader.Color.Locations}
+   */
+  this.colorLocations_ = null;
+
+  /**
+   * @private
+   * @type {ol.renderer.webgl.map.shader.Default.Locations}
+   */
+  this.defaultLocations_ = null;
 
-  goog.dispose(goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid]);
-  delete goog.dom.ViewportSizeMonitor.windowInstanceMap_[uid];
 };
+goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);
 
 
 /**
- * Map of window hash code to viewport size monitor for that window, if
- * created.
- * @type {Object.<number,goog.dom.ViewportSizeMonitor>}
- * @private
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {number} framebufferDimension Framebuffer dimension.
+ * @protected
  */
-goog.dom.ViewportSizeMonitor.windowInstanceMap_ = {};
+ol.renderer.webgl.Layer.prototype.bindFramebuffer =
+    function(frameState, framebufferDimension) {
 
+  var mapRenderer = this.getWebGLMapRenderer();
+  var gl = mapRenderer.getGL();
 
-/**
- * Event listener key for window the window resize handler, as returned by
- * {@link goog.events.listen}.
- * @type {goog.events.Key}
- * @private
- */
-goog.dom.ViewportSizeMonitor.prototype.listenerKey_ = null;
+  if (!goog.isDef(this.framebufferDimension) ||
+      this.framebufferDimension != framebufferDimension) {
 
+    frameState.postRenderFunctions.push(
+        goog.partial(
+            /**
+             * @param {WebGLRenderingContext} gl GL.
+             * @param {WebGLFramebuffer} framebuffer Framebuffer.
+             * @param {WebGLTexture} texture Texture.
+             */
+            function(gl, framebuffer, texture) {
+              if (!gl.isContextLost()) {
+                gl.deleteFramebuffer(framebuffer);
+                gl.deleteTexture(texture);
+              }
+            }, gl, this.framebuffer, this.texture));
 
-/**
- * The window to monitor.  Defaults to the window in which the code is running.
- * @type {Window}
- * @private
- */
-goog.dom.ViewportSizeMonitor.prototype.window_ = null;
+    var texture = gl.createTexture();
+    gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
+    gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
+        framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA,
+        goog.webgl.UNSIGNED_BYTE, null);
+    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER,
+        goog.webgl.LINEAR);
+    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER,
+        goog.webgl.LINEAR);
 
+    var framebuffer = gl.createFramebuffer();
+    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer);
+    gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER,
+        goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0);
 
-/**
- * The most recently recorded size of the viewport, in pixels.
- * @type {goog.math.Size?}
- * @private
- */
-goog.dom.ViewportSizeMonitor.prototype.size_ = null;
+    this.texture = texture;
+    this.framebuffer = framebuffer;
+    this.framebufferDimension = framebufferDimension;
+
+  } else {
+    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer);
+  }
+
+};
 
 
 /**
- * Returns the most recently recorded size of the viewport, in pixels.  May
- * return null if no window resize event has been handled yet.
- * @return {goog.math.Size} The viewport dimensions, in pixels.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @param {ol.webgl.Context} context Context.
  */
-goog.dom.ViewportSizeMonitor.prototype.getSize = function() {
-  // Return a clone instead of the original to preserve encapsulation.
-  return this.size_ ? this.size_.clone() : null;
-};
+ol.renderer.webgl.Layer.prototype.composeFrame =
+    function(frameState, layerState, context) {
 
+  this.dispatchComposeEvent_(
+      ol.render.EventType.PRECOMPOSE, context, frameState);
 
-/** @override */
-goog.dom.ViewportSizeMonitor.prototype.disposeInternal = function() {
-  goog.dom.ViewportSizeMonitor.superClass_.disposeInternal.call(this);
+  context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
 
-  if (this.listenerKey_) {
-    goog.events.unlistenByKey(this.listenerKey_);
-    this.listenerKey_ = null;
+  var gl = context.getGL();
+
+  var useColor =
+      layerState.brightness ||
+      layerState.contrast != 1 ||
+      layerState.hue ||
+      layerState.saturation != 1;
+
+  var fragmentShader, vertexShader;
+  if (useColor) {
+    fragmentShader = ol.renderer.webgl.map.shader.ColorFragment.getInstance();
+    vertexShader = ol.renderer.webgl.map.shader.ColorVertex.getInstance();
+  } else {
+    fragmentShader =
+        ol.renderer.webgl.map.shader.DefaultFragment.getInstance();
+    vertexShader = ol.renderer.webgl.map.shader.DefaultVertex.getInstance();
   }
 
-  this.window_ = null;
-  this.size_ = null;
+  var program = context.getProgram(fragmentShader, vertexShader);
+
+  // FIXME colorLocations_ and defaultLocations_ should be shared somehow
+  var locations;
+  if (useColor) {
+    if (goog.isNull(this.colorLocations_)) {
+      locations =
+          new ol.renderer.webgl.map.shader.Color.Locations(gl, program);
+      this.colorLocations_ = locations;
+    } else {
+      locations = this.colorLocations_;
+    }
+  } else {
+    if (goog.isNull(this.defaultLocations_)) {
+      locations =
+          new ol.renderer.webgl.map.shader.Default.Locations(gl, program);
+      this.defaultLocations_ = locations;
+    } else {
+      locations = this.defaultLocations_;
+    }
+  }
+
+  if (context.useProgram(program)) {
+    gl.enableVertexAttribArray(locations.a_position);
+    gl.vertexAttribPointer(
+        locations.a_position, 2, goog.webgl.FLOAT, false, 16, 0);
+    gl.enableVertexAttribArray(locations.a_texCoord);
+    gl.vertexAttribPointer(
+        locations.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8);
+    gl.uniform1i(locations.u_texture, 0);
+  }
+
+  gl.uniformMatrix4fv(
+      locations.u_texCoordMatrix, false, this.getTexCoordMatrix());
+  gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
+      this.getProjectionMatrix());
+  if (useColor) {
+    gl.uniformMatrix4fv(locations.u_colorMatrix, false,
+        this.colorMatrix_.getMatrix(
+            layerState.brightness,
+            layerState.contrast,
+            layerState.hue,
+            layerState.saturation
+        ));
+  }
+  gl.uniform1f(locations.u_opacity, layerState.opacity);
+  gl.bindTexture(goog.webgl.TEXTURE_2D, this.getTexture());
+  gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
+
+  this.dispatchComposeEvent_(
+      ol.render.EventType.POSTCOMPOSE, context, frameState);
+
 };
 
 
 /**
- * Handles window resize events by measuring the dimensions of the
- * viewport and dispatching a {@link goog.events.EventType.RESIZE} event if the
- * current dimensions are different from the previous ones.
- * @param {goog.events.Event} event The window resize event to handle.
+ * @param {ol.render.EventType} type Event type.
+ * @param {ol.webgl.Context} context WebGL context.
+ * @param {olx.FrameState} frameState Frame state.
  * @private
  */
-goog.dom.ViewportSizeMonitor.prototype.handleResize_ = function(event) {
-  var size = goog.dom.getViewportSize(this.window_);
-  if (!goog.math.Size.equals(size, this.size_)) {
-    this.size_ = size;
-    this.dispatchEvent(goog.events.EventType.RESIZE);
+ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ =
+    function(type, context, frameState) {
+  var layer = this.getLayer();
+  if (layer.hasListener(type)) {
+    var viewState = frameState.viewState;
+    var resolution = viewState.resolution;
+    var pixelRatio = frameState.pixelRatio;
+    var extent = frameState.extent;
+    var center = viewState.center;
+    var rotation = viewState.rotation;
+    var size = frameState.size;
+
+    var render = new ol.render.webgl.Immediate(
+        context, center, resolution, rotation, size, extent, pixelRatio);
+    var composeEvent = new ol.render.Event(
+        type, layer, render, null, frameState, null, context);
+    layer.dispatchEvent(composeEvent);
   }
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Constant declarations for common key codes.
- *
- * @author eae@google.com (Emil A Eklund)
- * @see ../demos/keyhandler.html
+ * @protected
+ * @return {ol.renderer.webgl.Map} MapRenderer.
  */
+ol.renderer.webgl.Layer.prototype.getWebGLMapRenderer = function() {
+  return /** @type {ol.renderer.webgl.Map} */ (this.getMapRenderer());
+};
 
-goog.provide('goog.events.KeyCodes');
 
-goog.require('goog.userAgent');
+/**
+ * @return {!goog.vec.Mat4.Number} Matrix.
+ */
+ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
+  return this.texCoordMatrix;
+};
 
 
 /**
- * Key codes for common characters.
- *
- * This list is not localized and therefore some of the key codes are not
- * correct for non US keyboard layouts. See comments below.
- *
- * @enum {number}
+ * @return {WebGLTexture} Texture.
  */
-goog.events.KeyCodes = {
-  WIN_KEY_FF_LINUX: 0,
-  MAC_ENTER: 3,
-  BACKSPACE: 8,
-  TAB: 9,
-  NUM_CENTER: 12,  // NUMLOCK on FF/Safari Mac
-  ENTER: 13,
-  SHIFT: 16,
-  CTRL: 17,
-  ALT: 18,
-  PAUSE: 19,
-  CAPS_LOCK: 20,
-  ESC: 27,
-  SPACE: 32,
-  PAGE_UP: 33,     // also NUM_NORTH_EAST
-  PAGE_DOWN: 34,   // also NUM_SOUTH_EAST
-  END: 35,         // also NUM_SOUTH_WEST
-  HOME: 36,        // also NUM_NORTH_WEST
-  LEFT: 37,        // also NUM_WEST
-  UP: 38,          // also NUM_NORTH
-  RIGHT: 39,       // also NUM_EAST
-  DOWN: 40,        // also NUM_SOUTH
-  PRINT_SCREEN: 44,
-  INSERT: 45,      // also NUM_INSERT
-  DELETE: 46,      // also NUM_DELETE
-  ZERO: 48,
-  ONE: 49,
-  TWO: 50,
-  THREE: 51,
-  FOUR: 52,
-  FIVE: 53,
-  SIX: 54,
-  SEVEN: 55,
-  EIGHT: 56,
-  NINE: 57,
-  FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186
-  FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187
-  FF_DASH: 173, // Firefox (Gecko) fires this for dash instead of 189
-  QUESTION_MARK: 63, // needs localization
-  A: 65,
-  B: 66,
-  C: 67,
-  D: 68,
-  E: 69,
-  F: 70,
-  G: 71,
-  H: 72,
-  I: 73,
-  J: 74,
-  K: 75,
-  L: 76,
-  M: 77,
-  N: 78,
-  O: 79,
-  P: 80,
-  Q: 81,
-  R: 82,
-  S: 83,
-  T: 84,
-  U: 85,
-  V: 86,
-  W: 87,
-  X: 88,
-  Y: 89,
-  Z: 90,
-  META: 91, // WIN_KEY_LEFT
-  WIN_KEY_RIGHT: 92,
-  CONTEXT_MENU: 93,
-  NUM_ZERO: 96,
-  NUM_ONE: 97,
-  NUM_TWO: 98,
-  NUM_THREE: 99,
-  NUM_FOUR: 100,
-  NUM_FIVE: 101,
-  NUM_SIX: 102,
-  NUM_SEVEN: 103,
-  NUM_EIGHT: 104,
-  NUM_NINE: 105,
-  NUM_MULTIPLY: 106,
-  NUM_PLUS: 107,
-  NUM_MINUS: 109,
-  NUM_PERIOD: 110,
-  NUM_DIVISION: 111,
-  F1: 112,
-  F2: 113,
-  F3: 114,
-  F4: 115,
-  F5: 116,
-  F6: 117,
-  F7: 118,
-  F8: 119,
-  F9: 120,
-  F10: 121,
-  F11: 122,
-  F12: 123,
-  NUMLOCK: 144,
-  SCROLL_LOCK: 145,
+ol.renderer.webgl.Layer.prototype.getTexture = function() {
+  return this.texture;
+};
 
-  // OS-specific media keys like volume controls and browser controls.
-  FIRST_MEDIA_KEY: 166,
-  LAST_MEDIA_KEY: 183,
 
-  SEMICOLON: 186,            // needs localization
-  DASH: 189,                 // needs localization
-  EQUALS: 187,               // needs localization
-  COMMA: 188,                // needs localization
-  PERIOD: 190,               // needs localization
-  SLASH: 191,                // needs localization
-  APOSTROPHE: 192,           // needs localization
-  TILDE: 192,                // needs localization
-  SINGLE_QUOTE: 222,         // needs localization
-  OPEN_SQUARE_BRACKET: 219,  // needs localization
-  BACKSLASH: 220,            // needs localization
-  CLOSE_SQUARE_BRACKET: 221, // needs localization
-  WIN_KEY: 224,
-  MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91
-  MAC_WK_CMD_LEFT: 91,  // WebKit Left Command key fired, same as META
-  MAC_WK_CMD_RIGHT: 93, // WebKit Right Command key fired, different from META
-  WIN_IME: 229,
+/**
+ * @return {!goog.vec.Mat4.Number} Matrix.
+ */
+ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
+  return this.projectionMatrix;
+};
 
-  // We've seen users whose machines fire this keycode at regular one
-  // second intervals. The common thread among these users is that
-  // they're all using Dell Inspiron laptops, so we suspect that this
-  // indicates a hardware/bios problem.
-  // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
-  PHANTOM: 255
+
+/**
+ * Handle webglcontextlost.
+ */
+ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
+  this.texture = null;
+  this.framebuffer = null;
+  this.framebufferDimension = undefined;
 };
 
 
 /**
- * Returns true if the event contains a text modifying key.
- * @param {goog.events.BrowserEvent} e A key event.
- * @return {boolean} Whether it's a text modifying key.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.layer.LayerState} layerState Layer state.
+ * @param {ol.webgl.Context} context Context.
+ * @return {boolean} whether composeFrame should be called.
  */
-goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) {
-  if (e.altKey && !e.ctrlKey ||
-      e.metaKey ||
-      // Function keys don't generate text
-      e.keyCode >= goog.events.KeyCodes.F1 &&
-      e.keyCode <= goog.events.KeyCodes.F12) {
-    return false;
-  }
+ol.renderer.webgl.Layer.prototype.prepareFrame = goog.abstractMethod;
 
-  // The following keys are quite harmless, even in combination with
-  // CTRL, ALT or SHIFT.
-  switch (e.keyCode) {
-    case goog.events.KeyCodes.ALT:
-    case goog.events.KeyCodes.CAPS_LOCK:
-    case goog.events.KeyCodes.CONTEXT_MENU:
-    case goog.events.KeyCodes.CTRL:
-    case goog.events.KeyCodes.DOWN:
-    case goog.events.KeyCodes.END:
-    case goog.events.KeyCodes.ESC:
-    case goog.events.KeyCodes.HOME:
-    case goog.events.KeyCodes.INSERT:
-    case goog.events.KeyCodes.LEFT:
-    case goog.events.KeyCodes.MAC_FF_META:
-    case goog.events.KeyCodes.META:
-    case goog.events.KeyCodes.NUMLOCK:
-    case goog.events.KeyCodes.NUM_CENTER:
-    case goog.events.KeyCodes.PAGE_DOWN:
-    case goog.events.KeyCodes.PAGE_UP:
-    case goog.events.KeyCodes.PAUSE:
-    case goog.events.KeyCodes.PHANTOM:
-    case goog.events.KeyCodes.PRINT_SCREEN:
-    case goog.events.KeyCodes.RIGHT:
-    case goog.events.KeyCodes.SCROLL_LOCK:
-    case goog.events.KeyCodes.SHIFT:
-    case goog.events.KeyCodes.UP:
-    case goog.events.KeyCodes.WIN_KEY:
-    case goog.events.KeyCodes.WIN_KEY_RIGHT:
-      return false;
-    case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
-      return !goog.userAgent.GECKO;
-    default:
-      return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY ||
-          e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY;
-  }
-};
-
-
-/**
- * Returns true if the key fires a keypress event in the current browser.
- *
- * Accoridng to MSDN [1] IE only fires keypress events for the following keys:
- * - Letters: A - Z (uppercase and lowercase)
- * - Numerals: 0 - 9
- * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
- * - System: ESC, SPACEBAR, ENTER
- *
- * That's not entirely correct though, for instance there's no distinction
- * between upper and lower case letters.
- *
- * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx)
- *
- * Safari is similar to IE, but does not fire keypress for ESC.
- *
- * Additionally, IE6 does not fire keydown or keypress events for letters when
- * the control or alt keys are held down and the shift key is not. IE7 does
- * fire keydown in these cases, though, but not keypress.
- *
- * @param {number} keyCode A key code.
- * @param {number=} opt_heldKeyCode Key code of a currently-held key.
- * @param {boolean=} opt_shiftKey Whether the shift key is held down.
- * @param {boolean=} opt_ctrlKey Whether the control key is held down.
- * @param {boolean=} opt_altKey Whether the alt key is held down.
- * @return {boolean} Whether it's a key that fires a keypress event.
- */
-goog.events.KeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode,
-    opt_shiftKey, opt_ctrlKey, opt_altKey) {
-  if (!goog.userAgent.IE &&
-      !(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) {
-    return true;
-  }
+goog.provide('ol.renderer.webgl.ImageLayer');
 
-  if (goog.userAgent.MAC && opt_altKey) {
-    return goog.events.KeyCodes.isCharacterKey(keyCode);
-  }
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.vec.Mat4');
+goog.require('goog.webgl');
+goog.require('ol.Coordinate');
+goog.require('ol.Extent');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+goog.require('ol.ViewHint');
+goog.require('ol.extent');
+goog.require('ol.layer.Image');
+goog.require('ol.proj');
+goog.require('ol.renderer.webgl.Layer');
 
-  // Alt but not AltGr which is represented as Alt+Ctrl.
-  if (opt_altKey && !opt_ctrlKey) {
-    return false;
-  }
 
-  // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress.
-  // Non-IE browsers and WebKit prior to 525 won't get this far so no need to
-  // check the user agent.
-  if (goog.isNumber(opt_heldKeyCode)) {
-    opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode);
-  }
-  if (!opt_shiftKey &&
-      (opt_heldKeyCode == goog.events.KeyCodes.CTRL ||
-       opt_heldKeyCode == goog.events.KeyCodes.ALT ||
-       goog.userAgent.MAC &&
-       opt_heldKeyCode == goog.events.KeyCodes.META)) {
-    return false;
-  }
 
-  // Some keys with Ctrl/Shift do not issue keypress in WEBKIT.
-  if (goog.userAgent.WEBKIT && opt_ctrlKey && opt_shiftKey) {
-    switch (keyCode) {
-      case goog.events.KeyCodes.BACKSLASH:
-      case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
-      case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
-      case goog.events.KeyCodes.TILDE:
-      case goog.events.KeyCodes.SEMICOLON:
-      case goog.events.KeyCodes.DASH:
-      case goog.events.KeyCodes.EQUALS:
-      case goog.events.KeyCodes.COMMA:
-      case goog.events.KeyCodes.PERIOD:
-      case goog.events.KeyCodes.SLASH:
-      case goog.events.KeyCodes.APOSTROPHE:
-      case goog.events.KeyCodes.SINGLE_QUOTE:
-        return false;
-    }
-  }
+/**
+ * @constructor
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Image} imageLayer Tile layer.
+ */
+ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) {
 
-  // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it
-  // continues to fire keydown events as the event repeats.
-  if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) {
-    return false;
-  }
+  goog.base(this, mapRenderer, imageLayer);
 
-  switch (keyCode) {
-    case goog.events.KeyCodes.ENTER:
-      return true;
-    case goog.events.KeyCodes.ESC:
-      return !goog.userAgent.WEBKIT;
-  }
+  /**
+   * The last rendered image.
+   * @private
+   * @type {?ol.ImageBase}
+   */
+  this.image_ = null;
 
-  return goog.events.KeyCodes.isCharacterKey(keyCode);
 };
+goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);
 
 
 /**
- * Returns true if the key produces a character.
- * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).
- *
- * @param {number} keyCode A key code.
- * @return {boolean} Whether it's a character key.
+ * @param {ol.ImageBase} image Image.
+ * @private
+ * @return {WebGLTexture} Texture.
  */
-goog.events.KeyCodes.isCharacterKey = function(keyCode) {
-  if (keyCode >= goog.events.KeyCodes.ZERO &&
-      keyCode <= goog.events.KeyCodes.NINE) {
-    return true;
-  }
+ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
 
-  if (keyCode >= goog.events.KeyCodes.NUM_ZERO &&
-      keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) {
-    return true;
-  }
+  // 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
 
-  if (keyCode >= goog.events.KeyCodes.A &&
-      keyCode <= goog.events.KeyCodes.Z) {
-    return true;
-  }
+  var imageElement = image.getImage();
+  var gl = this.getWebGLMapRenderer().getGL();
 
-  // Safari sends zero key code for non-latin characters.
-  if (goog.userAgent.WEBKIT && keyCode == 0) {
-    return true;
-  }
+  var texture = gl.createTexture();
 
-  switch (keyCode) {
-    case goog.events.KeyCodes.SPACE:
-    case goog.events.KeyCodes.QUESTION_MARK:
-    case goog.events.KeyCodes.NUM_PLUS:
-    case goog.events.KeyCodes.NUM_MINUS:
-    case goog.events.KeyCodes.NUM_PERIOD:
-    case goog.events.KeyCodes.NUM_DIVISION:
-    case goog.events.KeyCodes.SEMICOLON:
-    case goog.events.KeyCodes.FF_SEMICOLON:
-    case goog.events.KeyCodes.DASH:
-    case goog.events.KeyCodes.EQUALS:
-    case goog.events.KeyCodes.FF_EQUALS:
-    case goog.events.KeyCodes.COMMA:
-    case goog.events.KeyCodes.PERIOD:
-    case goog.events.KeyCodes.SLASH:
-    case goog.events.KeyCodes.APOSTROPHE:
-    case goog.events.KeyCodes.SINGLE_QUOTE:
-    case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
-    case goog.events.KeyCodes.BACKSLASH:
-    case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
-      return true;
-    default:
-      return false;
-  }
+  gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
+  gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
+      goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, imageElement);
+
+  gl.texParameteri(
+      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S,
+      goog.webgl.CLAMP_TO_EDGE);
+  gl.texParameteri(
+      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T,
+      goog.webgl.CLAMP_TO_EDGE);
+  gl.texParameteri(
+      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR);
+  gl.texParameteri(
+      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR);
+
+  return texture;
 };
 
 
 /**
- * Normalizes key codes from OS/Browser-specific value to the general one.
- * @param {number} keyCode The native key code.
- * @return {number} The normalized key code.
+ * @inheritDoc
  */
-goog.events.KeyCodes.normalizeKeyCode = function(keyCode) {
-  if (goog.userAgent.GECKO) {
-    return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode);
-  } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) {
-    return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode);
-  } else {
-    return keyCode;
-  }
+ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtPixel =
+    function(coordinate, frameState, callback, thisArg) {
+  var layer = this.getLayer();
+  var source = layer.getSource();
+  var resolution = frameState.viewState.resolution;
+  var rotation = frameState.viewState.rotation;
+  var skippedFeatureUids = frameState.skippedFeatureUids;
+  return source.forEachFeatureAtPixel(
+      resolution, rotation, coordinate, skippedFeatureUids,
+
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {?} Callback result.
+       */
+      function(feature) {
+        return callback.call(thisArg, feature, layer);
+      });
 };
 
 
 /**
- * Normalizes key codes from their Gecko-specific value to the general one.
- * @param {number} keyCode The native key code.
- * @return {number} The normalized key code.
+ * @inheritDoc
  */
-goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) {
-  switch (keyCode) {
-    case goog.events.KeyCodes.FF_EQUALS:
-      return goog.events.KeyCodes.EQUALS;
-    case goog.events.KeyCodes.FF_SEMICOLON:
-      return goog.events.KeyCodes.SEMICOLON;
-    case goog.events.KeyCodes.FF_DASH:
-      return goog.events.KeyCodes.DASH;
-    case goog.events.KeyCodes.MAC_FF_META:
-      return goog.events.KeyCodes.META;
-    case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
-      return goog.events.KeyCodes.WIN_KEY;
-    default:
-      return keyCode;
+ol.renderer.webgl.ImageLayer.prototype.prepareFrame =
+    function(frameState, layerState, context) {
+
+  var gl = this.getWebGLMapRenderer().getGL();
+
+  var viewState = frameState.viewState;
+  var viewCenter = viewState.center;
+  var viewResolution = viewState.resolution;
+  var viewRotation = viewState.rotation;
+
+  var image = this.image_;
+  var texture = this.texture;
+  var imageLayer = this.getLayer();
+  goog.asserts.assertInstanceof(imageLayer, ol.layer.Image);
+  var imageSource = imageLayer.getSource();
+
+  var hints = frameState.viewHints;
+
+  var renderedExtent = frameState.extent;
+  if (goog.isDef(layerState.extent)) {
+    renderedExtent = ol.extent.getIntersection(
+        renderedExtent, layerState.extent);
+  }
+  if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
+      !ol.extent.isEmpty(renderedExtent)) {
+    var projection = viewState.projection;
+    var sourceProjection = imageSource.getProjection();
+    if (!goog.isNull(sourceProjection)) {
+      goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection));
+      projection = sourceProjection;
+    }
+    var image_ = imageSource.getImage(renderedExtent, viewResolution,
+        frameState.pixelRatio, projection);
+    if (!goog.isNull(image_)) {
+      var imageState = image_.getState();
+      if (imageState == ol.ImageState.IDLE) {
+        goog.events.listenOnce(image_, goog.events.EventType.CHANGE,
+            this.handleImageChange, false, this);
+        image_.load();
+      } else if (imageState == ol.ImageState.LOADED) {
+        image = image_;
+        texture = this.createTexture_(image_);
+        if (!goog.isNull(this.texture)) {
+          frameState.postRenderFunctions.push(
+              goog.partial(
+                  /**
+                   * @param {WebGLRenderingContext} gl GL.
+                   * @param {WebGLTexture} texture Texture.
+                   */
+                  function(gl, texture) {
+                    if (!gl.isContextLost()) {
+                      gl.deleteTexture(texture);
+                    }
+                  }, gl, this.texture));
+        }
+      }
+    }
   }
-};
 
+  if (!goog.isNull(image)) {
+    goog.asserts.assert(!goog.isNull(texture));
 
-/**
- * Normalizes key codes from their Mac WebKit-specific value to the general one.
- * @param {number} keyCode The native key code.
- * @return {number} The normalized key code.
- */
-goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) {
-  switch (keyCode) {
-    case goog.events.KeyCodes.MAC_WK_CMD_RIGHT:  // 93
-      return goog.events.KeyCodes.META;          // 91
-    default:
-      return keyCode;
+    var canvas = this.getWebGLMapRenderer().getContext().getCanvas();
+
+    this.updateProjectionMatrix_(canvas.width, canvas.height,
+        viewCenter, viewResolution, viewRotation, image.getExtent());
+
+    // Translate and scale to flip the Y coord.
+    var texCoordMatrix = this.texCoordMatrix;
+    goog.vec.Mat4.makeIdentity(texCoordMatrix);
+    goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1);
+    goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0);
+
+    this.image_ = image;
+    this.texture = texture;
+
+    this.updateAttributions(frameState.attributions, image.getAttributions());
+    this.updateLogos(frameState, imageSource);
   }
+
+  return true;
 };
 
-// Copyright 2007 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview This file contains a class for working with keyboard events
- * that repeat consistently across browsers and platforms. It also unifies the
- * key code so that it is the same in all browsers and platforms.
- *
- * Different web browsers have very different keyboard event handling. Most
- * importantly is that only certain browsers repeat keydown events:
- * IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
- * FF/Mac and Safari 2 do not.
- *
- * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
- * decided that they should try to match IE's key handling behavior.
- * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
- * Safari 2 behavior.
- *
- * Firefox, Safari, Opera prevent on keypress
- *
- * IE prevents on keydown
- *
- * Firefox does not fire keypress for shift, ctrl, alt
- * Firefox does fire keydown for shift, ctrl, alt, meta
- * Firefox does not repeat keydown for shift, ctrl, alt, meta
- *
- * Firefox does not fire keypress for up and down in an input
- *
- * Opera fires keypress for shift, ctrl, alt, meta
- * Opera does not repeat keypress for shift, ctrl, alt, meta
- *
- * Safari 2 and 3 do not fire keypress for shift, ctrl, alt
- * Safari 2 does not fire keydown for shift, ctrl, alt
- * Safari 3 *does* fire keydown for shift, ctrl, alt
- *
- * IE provides the keycode for keyup/down events and the charcode (in the
- * keycode field) for keypress.
- *
- * Mozilla provides the keycode for keyup/down and the charcode for keypress
- * unless it's a non text modifying key in which case the keycode is provided.
- *
- * Safari 3 provides the keycode and charcode for all events.
- *
- * Opera provides the keycode for keyup/down event and either the charcode or
- * the keycode (in the keycode field) for keypress events.
- *
- * Firefox x11 doesn't fire keydown events if a another key is already held down
- * until the first key is released. This can cause a key event to be fired with
- * a keyCode for the first key and a charCode for the second key.
- *
- * Safari in keypress
- *
- *        charCode keyCode which
- * ENTER:       13      13    13
- * F1:       63236   63236 63236
- * F8:       63243   63243 63243
- * ...
- * p:          112     112   112
- * P:           80      80    80
- *
- * Firefox, keypress:
- *
- *        charCode keyCode which
- * ENTER:        0      13    13
- * F1:           0     112     0
- * F8:           0     119     0
- * ...
- * p:          112       0   112
- * P:           80       0    80
- *
- * Opera, Mac+Win32, keypress:
- *
- *         charCode keyCode which
- * ENTER: undefined      13    13
- * F1:    undefined     112     0
- * F8:    undefined     119     0
- * ...
- * p:     undefined     112   112
- * P:     undefined      80    80
- *
- * IE7, keydown
- *
- *         charCode keyCode     which
- * ENTER: undefined      13 undefined
- * F1:    undefined     112 undefined
- * F8:    undefined     119 undefined
- * ...
- * p:     undefined      80 undefined
- * P:     undefined      80 undefined
- *
- * @author arv@google.com (Erik Arvidsson)
- * @author eae@google.com (Emil A Eklund)
- * @see ../demos/keyhandler.html
+ * @param {number} canvasWidth Canvas width.
+ * @param {number} canvasHeight Canvas height.
+ * @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, viewCenter,
+        viewResolution, viewRotation, imageExtent) {
 
-goog.provide('goog.events.KeyEvent');
-goog.provide('goog.events.KeyHandler');
-goog.provide('goog.events.KeyHandler.EventType');
+  var canvasExtentWidth = canvasWidth * viewResolution;
+  var canvasExtentHeight = canvasHeight * viewResolution;
 
-goog.require('goog.events');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.EventTarget');
-goog.require('goog.events.EventType');
-goog.require('goog.events.KeyCodes');
-goog.require('goog.userAgent');
+  var projectionMatrix = this.projectionMatrix;
+  goog.vec.Mat4.makeIdentity(projectionMatrix);
+  goog.vec.Mat4.scale(projectionMatrix,
+      2 / canvasExtentWidth, 2 / canvasExtentHeight, 1);
+  goog.vec.Mat4.rotateZ(projectionMatrix, -viewRotation);
+  goog.vec.Mat4.translate(projectionMatrix,
+      imageExtent[0] - viewCenter[0],
+      imageExtent[1] - viewCenter[1],
+      0);
+  goog.vec.Mat4.scale(projectionMatrix,
+      (imageExtent[2] - imageExtent[0]) / 2,
+      (imageExtent[3] - imageExtent[1]) / 2,
+      1);
+  goog.vec.Mat4.translate(projectionMatrix, 1, 1, 0);
+
+};
+
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.tilelayer.shader');
+
+goog.require('ol.webgl.shader');
 
 
 
 /**
- * A wrapper around an element that you want to listen to keyboard events on.
- * @param {Element|Document=} opt_element The element or document to listen on.
- * @param {boolean=} opt_capture Whether to listen for browser events in
- *     capture phase (defaults to false).
  * @constructor
- * @extends {goog.events.EventTarget}
- * @final
+ * @extends {ol.webgl.shader.Fragment}
+ * @struct
  */
-goog.events.KeyHandler = function(opt_element, opt_capture) {
-  goog.events.EventTarget.call(this);
-
-  if (opt_element) {
-    this.attach(opt_element, opt_capture);
-  }
+ol.renderer.webgl.tilelayer.shader.Fragment = function() {
+  goog.base(this, ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE);
 };
-goog.inherits(goog.events.KeyHandler, goog.events.EventTarget);
+goog.inherits(ol.renderer.webgl.tilelayer.shader.Fragment, ol.webgl.shader.Fragment);
+goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Fragment);
 
 
 /**
- * This is the element that we will listen to the real keyboard events on.
- * @type {Element|Document|null}
- * @private
+ * @const
+ * @type {string}
  */
-goog.events.KeyHandler.prototype.element_ = null;
+ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n';
 
 
 /**
- * The key for the key press listener.
- * @type {goog.events.Key}
- * @private
+ * @const
+ * @type {string}
  */
-goog.events.KeyHandler.prototype.keyPressKey_ = null;
+ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}';
 
 
 /**
- * The key for the key down listener.
- * @type {goog.events.Key}
- * @private
- */
-goog.events.KeyHandler.prototype.keyDownKey_ = null;
-
-
-/**
- * The key for the key up listener.
- * @type {goog.events.Key}
- * @private
- */
-goog.events.KeyHandler.prototype.keyUpKey_ = null;
-
-
-/**
- * Used to detect keyboard repeat events.
- * @private
- * @type {number}
- */
-goog.events.KeyHandler.prototype.lastKey_ = -1;
-
-
-/**
- * Keycode recorded for key down events. As most browsers don't report the
- * keycode in the key press event we need to record it in the key down phase.
- * @private
- * @type {number}
+ * @const
+ * @type {string}
  */
-goog.events.KeyHandler.prototype.keyCode_ = -1;
-
+ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE = goog.DEBUG ?
+    ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE :
+    ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE;
 
-/**
- * Alt key recorded for key down events. FF on Mac does not report the alt key
- * flag in the key press event, we need to record it in the key down phase.
- * @type {boolean}
- * @private
- */
-goog.events.KeyHandler.prototype.altKey_ = false;
 
 
 /**
- * Enum type for the events fired by the key handler
- * @enum {string}
+ * @constructor
+ * @extends {ol.webgl.shader.Vertex}
+ * @struct
  */
-goog.events.KeyHandler.EventType = {
-  KEY: 'key'
+ol.renderer.webgl.tilelayer.shader.Vertex = function() {
+  goog.base(this, ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE);
 };
+goog.inherits(ol.renderer.webgl.tilelayer.shader.Vertex, ol.webgl.shader.Vertex);
+goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Vertex);
 
 
 /**
- * An enumeration of key codes that Safari 2 does incorrectly
- * @type {Object}
- * @private
+ * @const
+ * @type {string}
  */
-goog.events.KeyHandler.safariKey_ = {
-  '3': goog.events.KeyCodes.ENTER, // 13
-  '12': goog.events.KeyCodes.NUMLOCK, // 144
-  '63232': goog.events.KeyCodes.UP, // 38
-  '63233': goog.events.KeyCodes.DOWN, // 40
-  '63234': goog.events.KeyCodes.LEFT, // 37
-  '63235': goog.events.KeyCodes.RIGHT, // 39
-  '63236': goog.events.KeyCodes.F1, // 112
-  '63237': goog.events.KeyCodes.F2, // 113
-  '63238': goog.events.KeyCodes.F3, // 114
-  '63239': goog.events.KeyCodes.F4, // 115
-  '63240': goog.events.KeyCodes.F5, // 116
-  '63241': goog.events.KeyCodes.F6, // 117
-  '63242': goog.events.KeyCodes.F7, // 118
-  '63243': goog.events.KeyCodes.F8, // 119
-  '63244': goog.events.KeyCodes.F9, // 120
-  '63245': goog.events.KeyCodes.F10, // 121
-  '63246': goog.events.KeyCodes.F11, // 122
-  '63247': goog.events.KeyCodes.F12, // 123
-  '63248': goog.events.KeyCodes.PRINT_SCREEN, // 44
-  '63272': goog.events.KeyCodes.DELETE, // 46
-  '63273': goog.events.KeyCodes.HOME, // 36
-  '63275': goog.events.KeyCodes.END, // 35
-  '63276': goog.events.KeyCodes.PAGE_UP, // 33
-  '63277': goog.events.KeyCodes.PAGE_DOWN, // 34
-  '63289': goog.events.KeyCodes.NUMLOCK, // 144
-  '63302': goog.events.KeyCodes.INSERT // 45
-};
+ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n  gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n  v_texCoord = a_texCoord;\n}\n\n\n';
 
 
 /**
- * An enumeration of key identifiers currently part of the W3C draft for DOM3
- * and their mappings to keyCodes.
- * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
- * This is currently supported in Safari and should be platform independent.
- * @type {Object}
- * @private
+ * @const
+ * @type {string}
  */
-goog.events.KeyHandler.keyIdentifier_ = {
-  'Up': goog.events.KeyCodes.UP, // 38
-  'Down': goog.events.KeyCodes.DOWN, // 40
-  'Left': goog.events.KeyCodes.LEFT, // 37
-  'Right': goog.events.KeyCodes.RIGHT, // 39
-  'Enter': goog.events.KeyCodes.ENTER, // 13
-  'F1': goog.events.KeyCodes.F1, // 112
-  'F2': goog.events.KeyCodes.F2, // 113
-  'F3': goog.events.KeyCodes.F3, // 114
-  'F4': goog.events.KeyCodes.F4, // 115
-  'F5': goog.events.KeyCodes.F5, // 116
-  'F6': goog.events.KeyCodes.F6, // 117
-  'F7': goog.events.KeyCodes.F7, // 118
-  'F8': goog.events.KeyCodes.F8, // 119
-  'F9': goog.events.KeyCodes.F9, // 120
-  'F10': goog.events.KeyCodes.F10, // 121
-  'F11': goog.events.KeyCodes.F11, // 122
-  'F12': goog.events.KeyCodes.F12, // 123
-  'U+007F': goog.events.KeyCodes.DELETE, // 46
-  'Home': goog.events.KeyCodes.HOME, // 36
-  'End': goog.events.KeyCodes.END, // 35
-  'PageUp': goog.events.KeyCodes.PAGE_UP, // 33
-  'PageDown': goog.events.KeyCodes.PAGE_DOWN, // 34
-  'Insert': goog.events.KeyCodes.INSERT // 45
-};
+ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}';
 
 
 /**
- * If true, the KeyEvent fires on keydown. Otherwise, it fires on keypress.
- *
- * @type {boolean}
- * @private
+ * @const
+ * @type {string}
  */
-goog.events.KeyHandler.USES_KEYDOWN_ = goog.userAgent.IE ||
-    goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525');
+ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE = goog.DEBUG ?
+    ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE :
+    ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE;
+
 
 
 /**
- * If true, the alt key flag is saved during the key down and reused when
- * handling the key press. FF on Mac does not set the alt flag in the key press
- * event.
- * @type {boolean}
- * @private
+ * @constructor
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_ = goog.userAgent.MAC &&
-    goog.userAgent.GECKO;
+ol.renderer.webgl.tilelayer.shader.Locations = function(gl, program) {
 
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_texture = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_texture' : 'e');
 
-/**
- * Records the keycode for browsers that only returns the keycode for key up/
- * down events. For browser/key combinations that doesn't trigger a key pressed
- * event it also fires the patched key event.
- * @param {goog.events.BrowserEvent} e The key down event.
- * @private
- */
-goog.events.KeyHandler.prototype.handleKeyDown_ = function(e) {
-  // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
-  // before we've caught a key-up event.  If the last-key was one of these we
-  // reset the state.
-  if (goog.userAgent.WEBKIT) {
-    if (this.lastKey_ == goog.events.KeyCodes.CTRL && !e.ctrlKey ||
-        this.lastKey_ == goog.events.KeyCodes.ALT && !e.altKey ||
-        goog.userAgent.MAC &&
-        this.lastKey_ == goog.events.KeyCodes.META && !e.metaKey) {
-      this.lastKey_ = -1;
-      this.keyCode_ = -1;
-    }
-  }
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_tileOffset = gl.getUniformLocation(
+      program, goog.DEBUG ? 'u_tileOffset' : 'd');
 
-  if (this.lastKey_ == -1) {
-    if (e.ctrlKey && e.keyCode != goog.events.KeyCodes.CTRL) {
-      this.lastKey_ = goog.events.KeyCodes.CTRL;
-    } else if (e.altKey && e.keyCode != goog.events.KeyCodes.ALT) {
-      this.lastKey_ = goog.events.KeyCodes.ALT;
-    } else if (e.metaKey && e.keyCode != goog.events.KeyCodes.META) {
-      this.lastKey_ = goog.events.KeyCodes.META;
-    }
-  }
+  /**
+   * @type {number}
+   */
+  this.a_position = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_position' : 'b');
 
-  if (goog.events.KeyHandler.USES_KEYDOWN_ &&
-      !goog.events.KeyCodes.firesKeyPressEvent(e.keyCode,
-          this.lastKey_, e.shiftKey, e.ctrlKey, e.altKey)) {
-    this.handleEvent(e);
-  } else {
-    this.keyCode_ = goog.events.KeyCodes.normalizeKeyCode(e.keyCode);
-    if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
-      this.altKey_ = e.altKey;
-    }
-  }
+  /**
+   * @type {number}
+   */
+  this.a_texCoord = gl.getAttribLocation(
+      program, goog.DEBUG ? 'a_texCoord' : 'c');
 };
 
+// FIXME large resolutions lead to too large framebuffers :-(
+// FIXME animated shaders! check in redraw
 
-/**
- * Resets the stored previous values. Needed to be called for webkit which will
- * not generate a key up for meta key operations. This should only be called
- * when having finished with repeat key possiblities.
- */
-goog.events.KeyHandler.prototype.resetState = function() {
-  this.lastKey_ = -1;
-  this.keyCode_ = -1;
-};
+goog.provide('ol.renderer.webgl.TileLayer');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('goog.vec.Mat4');
+goog.require('goog.vec.Vec4');
+goog.require('goog.webgl');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.extent');
+goog.require('ol.layer.Tile');
+goog.require('ol.math');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.renderer.webgl.tilelayer.shader');
+goog.require('ol.tilecoord');
+goog.require('ol.webgl.Buffer');
 
-/**
- * Clears the stored previous key value, resetting the key repeat status. Uses
- * -1 because the Safari 3 Windows beta reports 0 for certain keys (like Home
- * and End.)
- * @param {goog.events.BrowserEvent} e The keyup event.
- * @private
- */
-goog.events.KeyHandler.prototype.handleKeyup_ = function(e) {
-  this.resetState();
-  this.altKey_ = e.altKey;
-};
 
 
 /**
- * Handles the events on the element.
- * @param {goog.events.BrowserEvent} e  The keyboard event sent from the
- *     browser.
+ * @constructor
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Tile} tileLayer Tile layer.
  */
-goog.events.KeyHandler.prototype.handleEvent = function(e) {
-  var be = e.getBrowserEvent();
-  var keyCode, charCode;
-  var altKey = be.altKey;
+ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
 
-  // IE reports the character code in the keyCode field for keypress events.
-  // There are two exceptions however, Enter and Escape.
-  if (goog.userAgent.IE && e.type == goog.events.EventType.KEYPRESS) {
-    keyCode = this.keyCode_;
-    charCode = keyCode != goog.events.KeyCodes.ENTER &&
-        keyCode != goog.events.KeyCodes.ESC ?
-            be.keyCode : 0;
+  goog.base(this, mapRenderer, tileLayer);
 
-  // Safari reports the character code in the keyCode field for keypress
-  // events but also has a charCode field.
-  } else if (goog.userAgent.WEBKIT &&
-      e.type == goog.events.EventType.KEYPRESS) {
-    keyCode = this.keyCode_;
-    charCode = be.charCode >= 0 && be.charCode < 63232 &&
-        goog.events.KeyCodes.isCharacterKey(keyCode) ?
-            be.charCode : 0;
+  /**
+   * @private
+   * @type {ol.webgl.shader.Fragment}
+   */
+  this.fragmentShader_ =
+      ol.renderer.webgl.tilelayer.shader.Fragment.getInstance();
 
-  // Opera reports the keycode or the character code in the keyCode field.
-  } else if (goog.userAgent.OPERA) {
-    keyCode = this.keyCode_;
-    charCode = goog.events.KeyCodes.isCharacterKey(keyCode) ?
-        be.keyCode : 0;
+  /**
+   * @private
+   * @type {ol.webgl.shader.Vertex}
+   */
+  this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance();
 
-  // Mozilla reports the character code in the charCode field.
-  } else {
-    keyCode = be.keyCode || this.keyCode_;
-    charCode = be.charCode || 0;
-    if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
-      altKey = this.altKey_;
-    }
-    // On the Mac, shift-/ triggers a question mark char code and no key code
-    // (normalized to WIN_KEY), so we synthesize the latter.
-    if (goog.userAgent.MAC &&
-        charCode == goog.events.KeyCodes.QUESTION_MARK &&
-        keyCode == goog.events.KeyCodes.WIN_KEY) {
-      keyCode = goog.events.KeyCodes.SLASH;
-    }
-  }
+  /**
+   * @private
+   * @type {ol.renderer.webgl.tilelayer.shader.Locations}
+   */
+  this.locations_ = null;
 
-  keyCode = goog.events.KeyCodes.normalizeKeyCode(keyCode);
-  var key = keyCode;
-  var keyIdentifier = be.keyIdentifier;
+  /**
+   * @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
+  ]);
 
-  // Correct the key value for certain browser-specific quirks.
-  if (keyCode) {
-    if (keyCode >= 63232 && keyCode in goog.events.KeyHandler.safariKey_) {
-      // NOTE(nicksantos): Safari 3 has fixed this problem,
-      // this is only needed for Safari 2.
-      key = goog.events.KeyHandler.safariKey_[keyCode];
-    } else {
+  /**
+   * @private
+   * @type {ol.TileRange}
+   */
+  this.renderedTileRange_ = null;
 
-      // Safari returns 25 for Shift+Tab instead of 9.
-      if (keyCode == 25 && e.shiftKey) {
-        key = 9;
-      }
-    }
-  } else if (keyIdentifier &&
-             keyIdentifier in goog.events.KeyHandler.keyIdentifier_) {
-    // This is needed for Safari Windows because it currently doesn't give a
-    // keyCode/which for non printable keys.
-    key = goog.events.KeyHandler.keyIdentifier_[keyIdentifier];
-  }
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.renderedFramebufferExtent_ = null;
 
-  // If we get the same keycode as a keydown/keypress without having seen a
-  // keyup event, then this event was caused by key repeat.
-  var repeat = key == this.lastKey_;
-  this.lastKey_ = key;
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = -1;
 
-  var event = new goog.events.KeyEvent(key, charCode, repeat, be);
-  event.altKey = altKey;
-  this.dispatchEvent(event);
 };
+goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
 
 
 /**
- * Returns the element listened on for the real keyboard events.
- * @return {Element|Document|null} The element listened on for the real
- *     keyboard events.
+ * @inheritDoc
  */
-goog.events.KeyHandler.prototype.getElement = function() {
-  return this.element_;
+ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
+  var mapRenderer = this.getWebGLMapRenderer();
+  var context = mapRenderer.getContext();
+  context.deleteBuffer(this.renderArrayBuffer_);
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * Adds the proper key event listeners to the element.
- * @param {Element|Document} element The element to listen on.
- * @param {boolean=} opt_capture Whether to listen for browser events in
- *     capture phase (defaults to false).
+ * @inheritDoc
  */
-goog.events.KeyHandler.prototype.attach = function(element, opt_capture) {
-  if (this.keyUpKey_) {
-    this.detach();
-  }
+ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
+  goog.base(this, 'handleWebGLContextLost');
+  this.locations_ = null;
+};
 
-  this.element_ = element;
 
-  this.keyPressKey_ = goog.events.listen(this.element_,
-                                         goog.events.EventType.KEYPRESS,
-                                         this,
-                                         opt_capture);
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.prepareFrame =
+    function(frameState, layerState, context) {
 
-  // Most browsers (Safari 2 being the notable exception) doesn't include the
-  // keyCode in keypress events (IE has the char code in the keyCode field and
-  // Mozilla only included the keyCode if there's no charCode). Thus we have to
-  // listen for keydown to capture the keycode.
-  this.keyDownKey_ = goog.events.listen(this.element_,
-                                        goog.events.EventType.KEYDOWN,
-                                        this.handleKeyDown_,
-                                        opt_capture,
-                                        this);
+  var mapRenderer = this.getWebGLMapRenderer();
+  var gl = context.getGL();
 
+  var viewState = frameState.viewState;
+  var projection = viewState.projection;
 
-  this.keyUpKey_ = goog.events.listen(this.element_,
-                                      goog.events.EventType.KEYUP,
-                                      this.handleKeyup_,
-                                      opt_capture,
-                                      this);
-};
+  var tileLayer = this.getLayer();
+  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
+  var tileSource = tileLayer.getSource();
+  var tileGrid = tileSource.getTileGridForProjection(projection);
+  var z = tileGrid.getZForResolution(viewState.resolution);
+  var tileResolution = tileGrid.getResolution(z);
 
+  var tilePixelSize =
+      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
+  var pixelRatio = tilePixelSize / tileGrid.getTileSize(z);
+  var tilePixelResolution = tileResolution / pixelRatio;
+  var tileGutter = tileSource.getGutter();
 
-/**
- * Removes the listeners that may exist.
- */
-goog.events.KeyHandler.prototype.detach = function() {
-  if (this.keyPressKey_) {
-    goog.events.unlistenByKey(this.keyPressKey_);
-    goog.events.unlistenByKey(this.keyDownKey_);
-    goog.events.unlistenByKey(this.keyUpKey_);
-    this.keyPressKey_ = null;
-    this.keyDownKey_ = null;
-    this.keyUpKey_ = null;
+  var center = viewState.center;
+  var extent;
+  if (tileResolution == viewState.resolution) {
+    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
+    extent = ol.extent.getForViewAndSize(
+        center, tileResolution, viewState.rotation, frameState.size);
+  } else {
+    extent = frameState.extent;
   }
-  this.element_ = null;
-  this.lastKey_ = -1;
-  this.keyCode_ = -1;
-};
+  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
+      extent, tileResolution);
 
+  var framebufferExtent;
+  if (!goog.isNull(this.renderedTileRange_) &&
+      this.renderedTileRange_.equals(tileRange) &&
+      this.renderedRevision_ == tileSource.getRevision()) {
+    framebufferExtent = this.renderedFramebufferExtent_;
+  } else {
 
-/** @override */
-goog.events.KeyHandler.prototype.disposeInternal = function() {
-  goog.events.KeyHandler.superClass_.disposeInternal.call(this);
-  this.detach();
-};
+    var tileRangeSize = tileRange.getSize();
 
+    var maxDimension = Math.max(
+        tileRangeSize[0] * tilePixelSize, tileRangeSize[1] * tilePixelSize);
+    var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension);
+    var framebufferExtentDimension = tilePixelResolution * framebufferDimension;
+    var origin = tileGrid.getOrigin(z);
+    var minX = origin[0] + tileRange.minX * tilePixelSize * tilePixelResolution;
+    var minY = origin[1] + tileRange.minY * tilePixelSize * tilePixelResolution;
+    framebufferExtent = [
+      minX, minY,
+      minX + framebufferExtentDimension, minY + framebufferExtentDimension
+    ];
 
+    this.bindFramebuffer(frameState, framebufferDimension);
+    gl.viewport(0, 0, framebufferDimension, framebufferDimension);
 
-/**
- * This class is used for the goog.events.KeyHandler.EventType.KEY event and
- * it overrides the key code with the fixed key code.
- * @param {number} keyCode The adjusted key code.
- * @param {number} charCode The unicode character code.
- * @param {boolean} repeat Whether this event was generated by keyboard repeat.
- * @param {Event} browserEvent Browser event object.
- * @constructor
- * @extends {goog.events.BrowserEvent}
- * @final
- */
-goog.events.KeyEvent = function(keyCode, charCode, repeat, browserEvent) {
-  goog.events.BrowserEvent.call(this, browserEvent);
-  this.type = goog.events.KeyHandler.EventType.KEY;
+    gl.clearColor(0, 0, 0, 0);
+    gl.clear(goog.webgl.COLOR_BUFFER_BIT);
+    gl.disable(goog.webgl.BLEND);
 
-  /**
-   * Keycode of key press.
-   * @type {number}
-   */
-  this.keyCode = keyCode;
+    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
+    context.useProgram(program);
+    if (goog.isNull(this.locations_)) {
+      this.locations_ =
+          new ol.renderer.webgl.tilelayer.shader.Locations(gl, program);
+    }
 
-  /**
-   * Unicode character code.
-   * @type {number}
-   */
-  this.charCode = charCode;
+    context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.renderArrayBuffer_);
+    gl.enableVertexAttribArray(this.locations_.a_position);
+    gl.vertexAttribPointer(
+        this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0);
+    gl.enableVertexAttribArray(this.locations_.a_texCoord);
+    gl.vertexAttribPointer(
+        this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8);
+    gl.uniform1i(this.locations_.u_texture, 0);
 
-  /**
-   * True if this event was generated by keyboard auto-repeat (i.e., the user is
-   * holding the key down.)
-   * @type {boolean}
-   */
-  this.repeat = repeat;
-};
-goog.inherits(goog.events.KeyEvent, goog.events.BrowserEvent);
+    /**
+     * @type {Object.<number, Object.<string, ol.Tile>>}
+     */
+    var tilesToDrawByZ = {};
+    tilesToDrawByZ[z] = {};
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+    var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
+      return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED &&
+          mapRenderer.isTileTextureLoaded(tile);
+    }, tileSource, pixelRatio, projection);
+    var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
+        tilesToDrawByZ, getTileIfLoaded);
 
-/**
- * @fileoverview This event wrapper will dispatch an event when the user uses
- * the mouse wheel to scroll an element. You can get the direction by checking
- * the deltaX and deltaY properties of the event.
- *
- * This class aims to smooth out inconsistencies between browser platforms with
- * regards to mousewheel events, but we do not cover every possible
- * software/hardware combination out there, some of which occasionally produce
- * very large deltas in mousewheel events. If your application wants to guard
- * against extremely large deltas, use the setMaxDeltaX and setMaxDeltaY APIs
- * to set maximum values that make sense for your application.
- *
- * @author arv@google.com (Erik Arvidsson)
- * @see ../demos/mousewheelhandler.html
- */
+    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
+    if (!goog.isDef(useInterimTilesOnError)) {
+      useInterimTilesOnError = true;
+    }
 
-goog.provide('goog.events.MouseWheelEvent');
-goog.provide('goog.events.MouseWheelHandler');
-goog.provide('goog.events.MouseWheelHandler.EventType');
+    var allTilesLoaded = true;
+    var tmpExtent = ol.extent.createEmpty();
+    var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
+    var childTileRange, fullyLoaded, tile, tileState, x, y, tileExtent;
+    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
+      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
 
-goog.require('goog.dom');
+        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
+        if (goog.isDef(layerState.extent)) {
+          // ignore tiles outside layer extent
+          tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
+          if (!ol.extent.intersects(tileExtent, layerState.extent)) {
+            continue;
+          }
+        }
+        tileState = tile.getState();
+        if (tileState == ol.TileState.LOADED) {
+          if (mapRenderer.isTileTextureLoaded(tile)) {
+            tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
+            continue;
+          }
+        } else if (tileState == ol.TileState.EMPTY ||
+                   (tileState == ol.TileState.ERROR &&
+                    !useInterimTilesOnError)) {
+          continue;
+        }
+
+        allTilesLoaded = false;
+        fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
+            tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
+        if (!fullyLoaded) {
+          childTileRange = tileGrid.getTileCoordChildTileRange(
+              tile.tileCoord, tmpTileRange, tmpExtent);
+          if (!goog.isNull(childTileRange)) {
+            findLoadedTiles(z + 1, childTileRange);
+          }
+        }
+
+      }
+
+    }
+
+    /** @type {Array.<number>} */
+    var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
+    goog.array.sort(zs);
+    var u_tileOffset = goog.vec.Vec4.createFloat32();
+    var i, ii, sx, sy, tileKey, tilesToDraw, tx, ty;
+    for (i = 0, ii = zs.length; i < ii; ++i) {
+      tilesToDraw = tilesToDrawByZ[zs[i]];
+      for (tileKey in tilesToDraw) {
+        tile = tilesToDraw[tileKey];
+        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
+        sx = 2 * (tileExtent[2] - tileExtent[0]) /
+            framebufferExtentDimension;
+        sy = 2 * (tileExtent[3] - tileExtent[1]) /
+            framebufferExtentDimension;
+        tx = 2 * (tileExtent[0] - framebufferExtent[0]) /
+            framebufferExtentDimension - 1;
+        ty = 2 * (tileExtent[1] - framebufferExtent[1]) /
+            framebufferExtentDimension - 1;
+        goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty);
+        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
+        mapRenderer.bindTileTexture(tile, tilePixelSize,
+            tileGutter * pixelRatio, goog.webgl.LINEAR, goog.webgl.LINEAR);
+        gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
+      }
+    }
+
+    if (allTilesLoaded) {
+      this.renderedTileRange_ = tileRange;
+      this.renderedFramebufferExtent_ = framebufferExtent;
+      this.renderedRevision_ = tileSource.getRevision();
+    } else {
+      this.renderedTileRange_ = null;
+      this.renderedFramebufferExtent_ = null;
+      this.renderedRevision_ = -1;
+      frameState.animate = true;
+    }
+
+  }
+
+  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
+  var tileTextureQueue = mapRenderer.getTileTextureQueue();
+  this.manageTilePyramid(
+      frameState, tileSource, tileGrid, pixelRatio, projection, extent, z,
+      tileLayer.getPreload(),
+      /**
+       * @param {ol.Tile} tile Tile.
+       */
+      function(tile) {
+        if (tile.getState() == ol.TileState.LOADED &&
+            !mapRenderer.isTileTextureLoaded(tile) &&
+            !tileTextureQueue.isKeyQueued(tile.getKey())) {
+          tileTextureQueue.enqueue([
+            tile,
+            tileGrid.getTileCoordCenter(tile.tileCoord),
+            tileGrid.getResolution(tile.tileCoord[0]),
+            tilePixelSize, tileGutter * pixelRatio
+          ]);
+        }
+      }, this);
+  this.scheduleExpireCache(frameState, tileSource);
+  this.updateLogos(frameState, tileSource);
+
+  var texCoordMatrix = this.texCoordMatrix;
+  goog.vec.Mat4.makeIdentity(texCoordMatrix);
+  goog.vec.Mat4.translate(texCoordMatrix,
+      (center[0] - framebufferExtent[0]) /
+          (framebufferExtent[2] - framebufferExtent[0]),
+      (center[1] - framebufferExtent[1]) /
+          (framebufferExtent[3] - framebufferExtent[1]),
+      0);
+  if (viewState.rotation !== 0) {
+    goog.vec.Mat4.rotateZ(texCoordMatrix, viewState.rotation);
+  }
+  goog.vec.Mat4.scale(texCoordMatrix,
+      frameState.size[0] * viewState.resolution /
+          (framebufferExtent[2] - framebufferExtent[0]),
+      frameState.size[1] * viewState.resolution /
+          (framebufferExtent[3] - framebufferExtent[1]),
+      1);
+  goog.vec.Mat4.translate(texCoordMatrix,
+      -0.5,
+      -0.5,
+      0);
+
+  return true;
+};
+
+goog.provide('ol.renderer.webgl.VectorLayer');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
 goog.require('goog.events');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.EventTarget');
-goog.require('goog.math');
-goog.require('goog.style');
-goog.require('goog.userAgent');
+goog.require('ol.ViewHint');
+goog.require('ol.extent');
+goog.require('ol.layer.Vector');
+goog.require('ol.render.webgl.ReplayGroup');
+goog.require('ol.renderer.vector');
+goog.require('ol.renderer.webgl.Layer');
 
 
 
 /**
- * This event handler allows you to catch mouse wheel events in a consistent
- * manner.
- * @param {Element|Document} element The element to listen to the mouse wheel
- *     event on.
- * @param {boolean=} opt_capture Whether to handle the mouse wheel event in
- *     capture phase.
  * @constructor
- * @extends {goog.events.EventTarget}
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
  */
-goog.events.MouseWheelHandler = function(element, opt_capture) {
-  goog.events.EventTarget.call(this);
+ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
+
+  goog.base(this, mapRenderer, vectorLayer);
 
   /**
-   * This is the element that we will listen to the real mouse wheel events on.
-   * @type {Element|Document}
    * @private
+   * @type {boolean}
    */
-  this.element_ = element;
+  this.dirty_ = false;
 
-  var rtlElement = goog.dom.isElement(this.element_) ?
-      /** @type {Element} */ (this.element_) :
-      (this.element_ ? /** @type {Document} */ (this.element_).body : null);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = -1;
 
   /**
-   * True if the element exists and is RTL, false otherwise.
-   * @type {boolean}
    * @private
+   * @type {number}
    */
-  this.isRtl_ = !!rtlElement && goog.style.isRightToLeft(rtlElement);
+  this.renderedResolution_ = NaN;
 
-  var type = goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel';
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.renderedExtent_ = ol.extent.createEmpty();
 
   /**
-   * The key returned from the goog.events.listen.
-   * @type {goog.events.Key}
    * @private
+   * @type {function(ol.Feature, ol.Feature): number|null}
    */
-  this.listenKey_ = goog.events.listen(this.element_, type, this, opt_capture);
-};
-goog.inherits(goog.events.MouseWheelHandler, goog.events.EventTarget);
+  this.renderedRenderOrder_ = null;
 
+  /**
+   * @private
+   * @type {ol.render.webgl.ReplayGroup}
+   */
+  this.replayGroup_ = null;
 
-/**
- * Enum type for the events fired by the mouse wheel handler.
- * @enum {string}
- */
-goog.events.MouseWheelHandler.EventType = {
-  MOUSEWHEEL: 'mousewheel'
 };
+goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
 
 
 /**
- * Optional maximum magnitude for x delta on each mousewheel event.
- * @type {number|undefined}
- * @private
+ * @inheritDoc
  */
-goog.events.MouseWheelHandler.prototype.maxDeltaX_;
+ol.renderer.webgl.VectorLayer.prototype.composeFrame =
+    function(frameState, layerState, context) {
+  var viewState = frameState.viewState;
+  var replayGroup = this.replayGroup_;
+  if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) {
+    replayGroup.replay(context,
+        viewState.center, viewState.resolution, viewState.rotation,
+        frameState.size, frameState.pixelRatio, layerState.opacity,
+        layerState.brightness, layerState.contrast, layerState.hue,
+        layerState.saturation, frameState.skippedFeatureUids);
+  }
+
+};
 
 
 /**
- * Optional maximum magnitude for y delta on each mousewheel event.
- * @type {number|undefined}
- * @private
+ * @inheritDoc
  */
-goog.events.MouseWheelHandler.prototype.maxDeltaY_;
+ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
+  var replayGroup = this.replayGroup_;
+  if (!goog.isNull(replayGroup)) {
+    var mapRenderer = this.getWebGLMapRenderer();
+    var context = mapRenderer.getContext();
+    replayGroup.getDeleteResourcesFunction(context)();
+    this.replayGroup_ = null;
+  }
+  goog.base(this, 'disposeInternal');
+};
 
 
 /**
- * @param {number} maxDeltaX Maximum magnitude for x delta on each mousewheel
- *     event. Should be non-negative.
+ * @inheritDoc
  */
-goog.events.MouseWheelHandler.prototype.setMaxDeltaX = function(maxDeltaX) {
-  this.maxDeltaX_ = maxDeltaX;
+ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel =
+    function(coordinate, frameState, callback, thisArg) {
 };
 
 
 /**
- * @param {number} maxDeltaY Maximum magnitude for y delta on each mousewheel
- *     event. Should be non-negative.
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
  */
-goog.events.MouseWheelHandler.prototype.setMaxDeltaY = function(maxDeltaY) {
-  this.maxDeltaY_ = maxDeltaY;
+ol.renderer.webgl.VectorLayer.prototype.handleImageChange_ =
+    function(event) {
+  this.renderIfReadyAndVisible();
 };
 
 
 /**
- * Handles the events on the element.
- * @param {goog.events.BrowserEvent} e The underlying browser event.
+ * @inheritDoc
  */
-goog.events.MouseWheelHandler.prototype.handleEvent = function(e) {
-  var deltaX = 0;
-  var deltaY = 0;
-  var detail = 0;
-  var be = e.getBrowserEvent();
-  if (be.type == 'mousewheel') {
-    var wheelDeltaScaleFactor = 1;
-    if (goog.userAgent.IE ||
-        goog.userAgent.WEBKIT &&
-        (goog.userAgent.WINDOWS || goog.userAgent.isVersionOrHigher('532.0'))) {
-      // In IE we get a multiple of 120; we adjust to a multiple of 3 to
-      // represent number of lines scrolled (like Gecko).
-      // Newer versions of Webkit match IE behavior, and WebKit on
-      // Windows also matches IE behavior.
-      // See bug https://bugs.webkit.org/show_bug.cgi?id=24368
-      wheelDeltaScaleFactor = 40;
-    }
+ol.renderer.webgl.VectorLayer.prototype.prepareFrame =
+    function(frameState, layerState, context) {
 
-    detail = goog.events.MouseWheelHandler.smartScale_(
-        -be.wheelDelta, wheelDeltaScaleFactor);
-    if (goog.isDef(be.wheelDeltaX)) {
-      // Webkit has two properties to indicate directional scroll, and
-      // can scroll both directions at once.
-      deltaX = goog.events.MouseWheelHandler.smartScale_(
-          -be.wheelDeltaX, wheelDeltaScaleFactor);
-      deltaY = goog.events.MouseWheelHandler.smartScale_(
-          -be.wheelDeltaY, wheelDeltaScaleFactor);
-    } else {
-      deltaY = detail;
-    }
+  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+  goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
+  var vectorSource = vectorLayer.getSource();
 
-    // Historical note: Opera (pre 9.5) used to negate the detail value.
-  } else { // Gecko
-    // Gecko returns multiple of 3 (representing the number of lines scrolled)
-    detail = be.detail;
+  this.updateAttributions(
+      frameState.attributions, vectorSource.getAttributions());
+  this.updateLogos(frameState, vectorSource);
 
-    // Gecko sometimes returns really big values if the user changes settings to
-    // scroll a whole page per scroll
-    if (detail > 100) {
-      detail = 3;
-    } else if (detail < -100) {
-      detail = -3;
-    }
+  if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
+      frameState.viewHints[ol.ViewHint.INTERACTING])) {
+    return true;
+  }
 
-    // Firefox 3.1 adds an axis field to the event to indicate direction of
-    // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
-    if (goog.isDef(be.axis) && be.axis === be.HORIZONTAL_AXIS) {
-      deltaX = detail;
-    } else {
-      deltaY = detail;
-    }
+  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 (!goog.isDef(vectorLayerRenderOrder)) {
+    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
   }
 
-  if (goog.isNumber(this.maxDeltaX_)) {
-    deltaX = goog.math.clamp(deltaX, -this.maxDeltaX_, this.maxDeltaX_);
+  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 (goog.isNumber(this.maxDeltaY_)) {
-    deltaY = goog.math.clamp(deltaY, -this.maxDeltaY_, this.maxDeltaY_);
+
+  if (!goog.isNull(this.replayGroup_)) {
+    frameState.postRenderFunctions.push(
+        this.replayGroup_.getDeleteResourcesFunction(context));
   }
-  // Don't clamp 'detail', since it could be ambiguous which axis it refers to
-  // and because it's informally deprecated anyways.
 
-  // For horizontal scrolling we need to flip the value for RTL grids.
-  if (this.isRtl_) {
-    deltaX = -deltaX;
+  this.dirty_ = false;
+
+  var replayGroup = new ol.render.webgl.ReplayGroup(
+      ol.renderer.vector.getTolerance(resolution, pixelRatio),
+      extent);
+  vectorSource.loadFeatures(extent, resolution, projection);
+  var renderFeature =
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @this {ol.renderer.webgl.VectorLayer}
+       */
+      function(feature) {
+    var styles;
+    if (goog.isDef(feature.getStyleFunction())) {
+      styles = feature.getStyleFunction().call(feature, resolution);
+    } else if (goog.isDef(vectorLayer.getStyleFunction())) {
+      styles = vectorLayer.getStyleFunction()(feature, resolution);
+    }
+    if (goog.isDefAndNotNull(styles)) {
+      var dirty = this.renderFeature(
+          feature, resolution, pixelRatio, styles, replayGroup);
+      this.dirty_ = this.dirty_ || dirty;
+    }
+  };
+  if (!goog.isNull(vectorLayerRenderOrder)) {
+    /** @type {Array.<ol.Feature>} */
+    var features = [];
+    vectorSource.forEachFeatureInExtentAtResolution(extent, resolution,
+        /**
+         * @param {ol.Feature} feature Feature.
+         */
+        function(feature) {
+          features.push(feature);
+        }, this);
+    goog.array.sort(features, vectorLayerRenderOrder);
+    goog.array.forEach(features, renderFeature, this);
+  } else {
+    vectorSource.forEachFeatureInExtentAtResolution(
+        extent, resolution, renderFeature, this);
   }
-  var newEvent = new goog.events.MouseWheelEvent(detail, be, deltaX, deltaY);
-  this.dispatchEvent(newEvent);
+  replayGroup.finish(context);
+
+  this.renderedResolution_ = resolution;
+  this.renderedRevision_ = vectorLayerRevision;
+  this.renderedRenderOrder_ = vectorLayerRenderOrder;
+  this.renderedExtent_ = extent;
+  this.replayGroup_ = replayGroup;
+
+  return true;
 };
 
 
 /**
- * Helper for scaling down a mousewheel delta by a scale factor, if appropriate.
- * @param {number} mouseWheelDelta Delta from a mouse wheel event. Expected to
- *     be an integer.
- * @param {number} scaleFactor Factor to scale the delta down by. Expected to
- *     be an integer.
- * @return {number} Scaled-down delta value, or the original delta if the
- *     scaleFactor does not appear to be applicable.
- * @private
+ * @param {ol.Feature} feature Feature.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {Array.<ol.style.Style>} styles Array of styles
+ * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group.
+ * @return {boolean} `true` if an image is loading.
  */
-goog.events.MouseWheelHandler.smartScale_ = function(mouseWheelDelta,
-    scaleFactor) {
-  // The basic problem here is that in Webkit on Mac and Linux, we can get two
-  // very different types of mousewheel events: from continuous devices
-  // (touchpads, Mighty Mouse) or non-continuous devices (normal wheel mice).
-  //
-  // Non-continuous devices in Webkit get their wheel deltas scaled up to
-  // behave like IE. Continuous devices return much smaller unscaled values
-  // (which most of the time will not be cleanly divisible by the IE scale
-  // factor), so we should not try to normalize them down.
-  //
-  // Detailed discussion:
-  //   https://bugs.webkit.org/show_bug.cgi?id=29601
-  //   http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
-  if (goog.userAgent.WEBKIT &&
-      (goog.userAgent.MAC || goog.userAgent.LINUX) &&
-      (mouseWheelDelta % scaleFactor) != 0) {
-    return mouseWheelDelta;
-  } else {
-    return mouseWheelDelta / scaleFactor;
+ol.renderer.webgl.VectorLayer.prototype.renderFeature =
+    function(feature, resolution, pixelRatio, styles, replayGroup) {
+  if (!goog.isDefAndNotNull(styles)) {
+    return false;
+  }
+  var i, ii, loading = false;
+  for (i = 0, ii = styles.length; i < ii; ++i) {
+    loading = ol.renderer.vector.renderFeature(
+        replayGroup, feature, styles[i],
+        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+        this.handleImageChange_, this) || loading;
   }
+  return loading;
 };
 
+goog.provide('ol.structs.LRUCache');
 
-/** @override */
-goog.events.MouseWheelHandler.prototype.disposeInternal = function() {
-  goog.events.MouseWheelHandler.superClass_.disposeInternal.call(this);
-  goog.events.unlistenByKey(this.listenKey_);
-  this.listenKey_ = null;
-};
+goog.require('goog.asserts');
+goog.require('goog.object');
 
 
 
 /**
- * A base class for mouse wheel events. This is used with the
- * MouseWheelHandler.
- *
- * @param {number} detail The number of rows the user scrolled.
- * @param {Event} browserEvent Browser event object.
- * @param {number} deltaX The number of rows the user scrolled in the X
- *     direction.
- * @param {number} deltaY The number of rows the user scrolled in the Y
- *     direction.
+ * Implements a Least-Recently-Used cache where the keys do not conflict with
+ * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring
+ * items from the cache is the responsibility of the user.
  * @constructor
- * @extends {goog.events.BrowserEvent}
- * @final
+ * @struct
+ * @template T
  */
-goog.events.MouseWheelEvent = function(detail, browserEvent, deltaX, deltaY) {
-  goog.events.BrowserEvent.call(this, browserEvent);
-
-  this.type = goog.events.MouseWheelHandler.EventType.MOUSEWHEEL;
+ol.structs.LRUCache = function() {
 
   /**
-   * The number of lines the user scrolled
+   * @private
    * @type {number}
-   * NOTE: Informally deprecated. Use deltaX and deltaY instead, they provide
-   * more information.
    */
-  this.detail = detail;
+  this.count_ = 0;
 
   /**
-   * The number of "lines" scrolled in the X direction.
-   *
-   * Note that not all browsers provide enough information to distinguish
-   * horizontal and vertical scroll events, so for these unsupported browsers,
-   * we will always have a deltaX of 0, even if the user scrolled their mouse
-   * wheel or trackpad sideways.
-   *
-   * Currently supported browsers are Webkit and Firefox 3.1 or later.
-   *
-   * @type {number}
+   * @private
+   * @type {Object.<string, ol.structs.LRUCacheEntry>}
    */
-  this.deltaX = deltaX;
+  this.entries_ = {};
 
   /**
-   * The number of lines scrolled in the Y direction.
-   * @type {number}
+   * @private
+   * @type {?ol.structs.LRUCacheEntry}
    */
-  this.deltaY = deltaY;
-};
-goog.inherits(goog.events.MouseWheelEvent, goog.events.BrowserEvent);
+  this.oldest_ = null;
 
-goog.provide('ol.source.Source');
-goog.provide('ol.source.State');
+  /**
+   * @private
+   * @type {?ol.structs.LRUCacheEntry}
+   */
+  this.newest_ = null;
 
-goog.require('goog.events.EventType');
-goog.require('ol.Attribution');
-goog.require('ol.Extent');
-goog.require('ol.Observable');
-goog.require('ol.proj');
+};
 
 
 /**
- * State of the source, one of 'loading', 'ready' or 'error'.
- * @enum {string}
- * @api
+ * FIXME empty description for jsdoc
  */
-ol.source.State = {
-  LOADING: 'loading',
-  READY: 'ready',
-  ERROR: 'error'
+ol.structs.LRUCache.prototype.assertValid = function() {
+  if (this.count_ === 0) {
+    goog.asserts.assert(goog.object.isEmpty(this.entries_));
+    goog.asserts.assert(goog.isNull(this.oldest_));
+    goog.asserts.assert(goog.isNull(this.newest_));
+  } else {
+    goog.asserts.assert(goog.object.getCount(this.entries_) == this.count_);
+    goog.asserts.assert(!goog.isNull(this.oldest_));
+    goog.asserts.assert(goog.isNull(this.oldest_.older));
+    goog.asserts.assert(!goog.isNull(this.newest_));
+    goog.asserts.assert(goog.isNull(this.newest_.newer));
+    var i, entry;
+    var older = null;
+    i = 0;
+    for (entry = this.oldest_; !goog.isNull(entry); entry = entry.newer) {
+      goog.asserts.assert(entry.older === older);
+      older = entry;
+      ++i;
+    }
+    goog.asserts.assert(i == this.count_);
+    var newer = null;
+    i = 0;
+    for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) {
+      goog.asserts.assert(entry.newer === newer);
+      newer = entry;
+      ++i;
+    }
+    goog.asserts.assert(i == this.count_);
+  }
 };
 
 
 /**
- * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
- *            logo: (string|olx.LogoOptions|undefined),
- *            projection: ol.proj.ProjectionLike,
- *            state: (ol.source.State|undefined)}}
+ * FIXME empty description for jsdoc
  */
-ol.source.SourceOptions;
-
+ol.structs.LRUCache.prototype.clear = function() {
+  this.count_ = 0;
+  this.entries_ = {};
+  this.oldest_ = null;
+  this.newest_ = null;
+};
 
 
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for {@link ol.layer.Layer} sources.
- *
- * @constructor
- * @extends {ol.Observable}
- * @fires change Triggered when the state of the source changes.
- * @param {ol.source.SourceOptions} options Source options.
+ * @param {string} key Key.
+ * @return {boolean} Contains key.
  */
-ol.source.Source = function(options) {
-
-  goog.base(this);
-
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.projection_ = ol.proj.get(options.projection);
-
-  /**
-   * @private
-   * @type {Array.<ol.Attribution>}
-   */
-  this.attributions_ = goog.isDef(options.attributions) ?
-      options.attributions : null;
-
-  /**
-   * @private
-   * @type {string|olx.LogoOptions|undefined}
-   */
-  this.logo_ = options.logo;
-
-  /**
-   * @private
-   * @type {ol.source.State}
-   */
-  this.state_ = goog.isDef(options.state) ?
-      options.state : ol.source.State.READY;
-
+ol.structs.LRUCache.prototype.containsKey = function(key) {
+  return this.entries_.hasOwnProperty(key);
 };
-goog.inherits(ol.source.Source, ol.Observable);
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
- * @param {function(ol.Feature): T} callback Feature callback.
- * @return {T|undefined} Callback result.
- * @template T
+ * @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.source.Source.prototype.forEachFeatureAtPixel =
-    goog.nullFunction;
+ol.structs.LRUCache.prototype.forEach = function(f, opt_this) {
+  var entry = this.oldest_;
+  while (!goog.isNull(entry)) {
+    f.call(opt_this, entry.value_, entry.key_, this);
+    entry = entry.newer;
+  }
+};
 
 
 /**
- * @return {Array.<ol.Attribution>} Attributions.
- * @api stable
+ * @param {string} key Key.
+ * @return {T} Value.
  */
-ol.source.Source.prototype.getAttributions = function() {
-  return this.attributions_;
+ol.structs.LRUCache.prototype.get = function(key) {
+  var entry = this.entries_[key];
+  goog.asserts.assert(goog.isDef(entry));
+  if (entry === this.newest_) {
+    return entry.value_;
+  } else if (entry === this.oldest_) {
+    this.oldest_ = this.oldest_.newer;
+    this.oldest_.older = null;
+  } else {
+    entry.newer.older = entry.older;
+    entry.older.newer = entry.newer;
+  }
+  entry.newer = null;
+  entry.older = this.newest_;
+  this.newest_.newer = entry;
+  this.newest_ = entry;
+  return entry.value_;
 };
 
 
 /**
- * @return {string|olx.LogoOptions|undefined} Logo.
- * @api stable
+ * @return {number} Count.
  */
-ol.source.Source.prototype.getLogo = function() {
-  return this.logo_;
+ol.structs.LRUCache.prototype.getCount = function() {
+  return this.count_;
 };
 
 
 /**
- * @return {ol.proj.Projection} Projection.
- * @api
+ * @return {Array.<string>} Keys.
  */
-ol.source.Source.prototype.getProjection = function() {
-  return this.projection_;
+ol.structs.LRUCache.prototype.getKeys = function() {
+  var keys = new Array(this.count_);
+  var i = 0;
+  var entry;
+  for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) {
+    keys[i++] = entry.key_;
+  }
+  goog.asserts.assert(i == this.count_);
+  return keys;
 };
 
 
 /**
- * @return {Array.<number>|undefined} Resolutions.
+ * @return {Array.<T>} Values.
  */
-ol.source.Source.prototype.getResolutions = goog.abstractMethod;
+ol.structs.LRUCache.prototype.getValues = function() {
+  var values = new Array(this.count_);
+  var i = 0;
+  var entry;
+  for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) {
+    values[i++] = entry.value_;
+  }
+  goog.asserts.assert(i == this.count_);
+  return values;
+};
 
 
 /**
- * @return {ol.source.State} State.
- * @api
+ * @return {T} Last value.
  */
-ol.source.Source.prototype.getState = function() {
-  return this.state_;
+ol.structs.LRUCache.prototype.peekLast = function() {
+  goog.asserts.assert(!goog.isNull(this.oldest_));
+  return this.oldest_.value_;
 };
 
 
 /**
- * @param {Array.<ol.Attribution>} attributions Attributions.
+ * @return {string} Last key.
  */
-ol.source.Source.prototype.setAttributions = function(attributions) {
-  this.attributions_ = attributions;
+ol.structs.LRUCache.prototype.peekLastKey = function() {
+  goog.asserts.assert(!goog.isNull(this.oldest_));
+  return this.oldest_.key_;
 };
 
 
 /**
- * @param {string|olx.LogoOptions|undefined} logo Logo.
+ * @return {T} value Value.
  */
-ol.source.Source.prototype.setLogo = function(logo) {
-  this.logo_ = logo;
+ol.structs.LRUCache.prototype.pop = function() {
+  goog.asserts.assert(!goog.isNull(this.oldest_));
+  goog.asserts.assert(!goog.isNull(this.newest_));
+  var entry = this.oldest_;
+  goog.asserts.assert(entry.key_ in this.entries_);
+  delete this.entries_[entry.key_];
+  if (!goog.isNull(entry.newer)) {
+    entry.newer.older = null;
+  }
+  this.oldest_ = entry.newer;
+  if (goog.isNull(this.oldest_)) {
+    this.newest_ = null;
+  }
+  --this.count_;
+  return entry.value_;
 };
 
 
 /**
- * @param {ol.source.State} state State.
- * @protected
+ * @param {string} key Key.
+ * @param {T} value Value.
  */
-ol.source.Source.prototype.setState = function(state) {
-  this.state_ = state;
-  this.dispatchChangeEvent();
+ol.structs.LRUCache.prototype.set = function(key, value) {
+  goog.asserts.assert(!(key in {}));
+  goog.asserts.assert(!(key in this.entries_));
+  var entry = {
+    key_: key,
+    newer: null,
+    older: this.newest_,
+    value_: value
+  };
+  if (goog.isNull(this.newest_)) {
+    this.oldest_ = entry;
+  } else {
+    this.newest_.newer = entry;
+  }
+  this.newest_ = entry;
+  this.entries_[key] = entry;
+  ++this.count_;
 };
 
 
 /**
- * @param {ol.proj.Projection} projection Projetion.
+ * @typedef {{key_: string,
+ *            newer: ol.structs.LRUCacheEntry,
+ *            older: ol.structs.LRUCacheEntry,
+ *            value_: *}}
  */
-ol.source.Source.prototype.setProjection = function(projection) {
-  this.projection_ = projection;
-};
+ol.structs.LRUCacheEntry;
 
-goog.provide('ol.layer.Base');
-goog.provide('ol.layer.LayerProperty');
-goog.provide('ol.layer.LayerState');
+goog.provide('ol.webgl.Context');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
 goog.require('goog.events');
-goog.require('goog.math');
+goog.require('goog.log');
 goog.require('goog.object');
-goog.require('ol.Object');
-goog.require('ol.source.State');
-
-
-/**
- * @enum {string}
- */
-ol.layer.LayerProperty = {
-  BRIGHTNESS: 'brightness',
-  CONTRAST: 'contrast',
-  HUE: 'hue',
-  OPACITY: 'opacity',
-  SATURATION: 'saturation',
-  VISIBLE: 'visible',
-  EXTENT: 'extent',
-  MAX_RESOLUTION: 'maxResolution',
-  MIN_RESOLUTION: 'minResolution'
-};
+goog.require('ol');
+goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.WebGLContextEventType');
 
 
 /**
- * @typedef {{layer: ol.layer.Layer,
- *            brightness: number,
- *            contrast: number,
- *            hue: number,
- *            opacity: number,
- *            saturation: number,
- *            sourceState: ol.source.State,
- *            visible: boolean,
- *            extent: (ol.Extent|undefined),
- *            maxResolution: number,
- *            minResolution: number}}
+ * @typedef {{buf: ol.webgl.Buffer,
+ *            buffer: WebGLBuffer}}
  */
-ol.layer.LayerState;
+ol.webgl.BufferCacheEntry;
 
 
 
 /**
  * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Note that with `ol.layer.Base` and all its subclasses, any property set in
- * the options is set as a {@link ol.Object} property on the layer object, so
- * is observable, and has get/set accessors.
+ * A WebGL context for accessing low-level WebGL capabilities.
  *
  * @constructor
- * @extends {ol.Object}
- * @param {olx.layer.BaseOptions} options Layer options.
+ * @extends {goog.events.EventTarget}
+ * @param {HTMLCanvasElement} canvas Canvas.
+ * @param {WebGLRenderingContext} gl GL.
+ * @api
  */
-ol.layer.Base = function(options) {
+ol.webgl.Context = function(canvas, gl) {
 
-  goog.base(this);
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = canvas;
 
-  var properties = goog.object.clone(options);
+  /**
+   * @private
+   * @type {WebGLRenderingContext}
+   */
+  this.gl_ = gl;
 
-  /** @type {number} */
-  properties.brightness = goog.isDef(properties.brightness) ?
-      properties.brightness : 0;
-  /** @type {number} */
-  properties.contrast = goog.isDef(properties.contrast) ?
-      properties.contrast : 1;
-  /** @type {number} */
-  properties.hue = goog.isDef(properties.hue) ? properties.hue : 0;
-  /** @type {number} */
-  properties.opacity = goog.isDef(properties.opacity) ? properties.opacity : 1;
-  /** @type {number} */
-  properties.saturation = goog.isDef(properties.saturation) ?
-      properties.saturation : 1;
-  /** @type {boolean} */
-  properties.visible = goog.isDef(properties.visible) ?
-      properties.visible : true;
-  /** @type {number} */
-  properties.maxResolution = goog.isDef(properties.maxResolution) ?
-      properties.maxResolution : Infinity;
-  /** @type {number} */
-  properties.minResolution = goog.isDef(properties.minResolution) ?
-      properties.minResolution : 0;
+  /**
+   * @private
+   * @type {Object.<number, ol.webgl.BufferCacheEntry>}
+   */
+  this.bufferCache_ = {};
 
-  this.setProperties(properties);
-};
-goog.inherits(ol.layer.Base, ol.Object);
+  /**
+   * @private
+   * @type {Object.<number, WebGLShader>}
+   */
+  this.shaderCache_ = {};
 
+  /**
+   * @private
+   * @type {Object.<string, WebGLProgram>}
+   */
+  this.programCache_ = {};
 
-/**
- * @return {number|undefined} The brightness of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.getBrightness = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.BRIGHTNESS));
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getBrightness',
-    ol.layer.Base.prototype.getBrightness);
+  /**
+   * @private
+   * @type {WebGLProgram}
+   */
+  this.currentProgram_ = null;
 
+  /**
+   * @type {boolean}
+   */
+  this.hasOESElementIndexUint = goog.array.contains(
+      ol.WEBGL_EXTENSIONS, 'OES_element_index_uint');
 
-/**
- * @return {number|undefined} The contrast of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.getContrast = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.CONTRAST));
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getContrast',
-    ol.layer.Base.prototype.getContrast);
+  // use the OES_element_index_uint extension if available
+  if (this.hasOESElementIndexUint) {
+    var ext = gl.getExtension('OES_element_index_uint');
+    goog.asserts.assert(!goog.isNull(ext));
+  }
 
+  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST,
+      this.handleWebGLContextLost, false, this);
+  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED,
+      this.handleWebGLContextRestored, false, this);
 
-/**
- * @return {number|undefined} The hue of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.getHue = function() {
-  return /** @type {number|undefined} */ (this.get(ol.layer.LayerProperty.HUE));
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getHue',
-    ol.layer.Base.prototype.getHue);
 
 
 /**
- * @return {ol.layer.LayerState} Layer state.
+ * 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.layer.Base.prototype.getLayerState = function() {
-  var brightness = this.getBrightness();
-  var contrast = this.getContrast();
-  var hue = this.getHue();
-  var opacity = this.getOpacity();
-  var saturation = this.getSaturation();
-  var sourceState = this.getSourceState();
-  var visible = this.getVisible();
-  var extent = this.getExtent();
-  var maxResolution = this.getMaxResolution();
-  var minResolution = this.getMinResolution();
-  return {
-    layer: /** @type {ol.layer.Layer} */ (this),
-    brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0,
-    contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1,
-    hue: goog.isDef(hue) ? hue : 0,
-    opacity: goog.isDef(opacity) ? goog.math.clamp(opacity, 0, 1) : 1,
-    saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1,
-    sourceState: sourceState,
-    visible: goog.isDef(visible) ? !!visible : true,
-    extent: extent,
-    maxResolution: goog.isDef(maxResolution) ? maxResolution : Infinity,
-    minResolution: goog.isDef(minResolution) ? Math.max(minResolution, 0) : 0
-  };
+ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
+  var gl = this.getGL();
+  var arr = buf.getArray();
+  var bufferKey = goog.getUid(buf);
+  if (bufferKey in this.bufferCache_) {
+    var bufferCacheEntry = this.bufferCache_[bufferKey];
+    gl.bindBuffer(target, bufferCacheEntry.buffer);
+  } else {
+    var buffer = gl.createBuffer();
+    gl.bindBuffer(target, buffer);
+    goog.asserts.assert(target == goog.webgl.ARRAY_BUFFER ||
+        target == goog.webgl.ELEMENT_ARRAY_BUFFER);
+    var /** @type {ArrayBufferView} */ arrayBuffer;
+    if (target == goog.webgl.ARRAY_BUFFER) {
+      arrayBuffer = new Float32Array(arr);
+    } else if (target == goog.webgl.ELEMENT_ARRAY_BUFFER) {
+      arrayBuffer = this.hasOESElementIndexUint ?
+          new Uint32Array(arr) : new Uint16Array(arr);
+    } else {
+      goog.asserts.fail();
+    }
+    gl.bufferData(target, arrayBuffer, buf.getUsage());
+    this.bufferCache_[bufferKey] = {
+      buf: buf,
+      buffer: buffer
+    };
+  }
 };
 
 
 /**
- * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
- *     modified in place).
- * @return {Array.<ol.layer.Layer>} Array of layers.
+ * @param {ol.webgl.Buffer} buf Buffer.
  */
-ol.layer.Base.prototype.getLayersArray = goog.abstractMethod;
+ol.webgl.Context.prototype.deleteBuffer = function(buf) {
+  var gl = this.getGL();
+  var bufferKey = goog.getUid(buf);
+  goog.asserts.assert(bufferKey in this.bufferCache_);
+  var bufferCacheEntry = this.bufferCache_[bufferKey];
+  if (!gl.isContextLost()) {
+    gl.deleteBuffer(bufferCacheEntry.buffer);
+  }
+  delete this.bufferCache_[bufferKey];
+};
 
 
 /**
- * @param {Array.<ol.layer.LayerState>=} opt_states Optional list of layer
- *     states (to be modified in place).
- * @return {Array.<ol.layer.LayerState>} List of layer states.
+ * @inheritDoc
  */
-ol.layer.Base.prototype.getLayerStatesArray = goog.abstractMethod;
+ol.webgl.Context.prototype.disposeInternal = function() {
+  var gl = this.getGL();
+  if (!gl.isContextLost()) {
+    goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
+      gl.deleteBuffer(bufferCacheEntry.buffer);
+    });
+    goog.object.forEach(this.programCache_, function(program) {
+      gl.deleteProgram(program);
+    });
+    goog.object.forEach(this.shaderCache_, function(shader) {
+      gl.deleteShader(shader);
+    });
+  }
+};
 
 
 /**
- * @return {ol.Extent|undefined} The layer extent.
- * @observable
- * @api stable
+ * @return {HTMLCanvasElement} Canvas.
  */
-ol.layer.Base.prototype.getExtent = function() {
-  return /** @type {ol.Extent|undefined} */ (
-      this.get(ol.layer.LayerProperty.EXTENT));
+ol.webgl.Context.prototype.getCanvas = function() {
+  return this.canvas_;
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getExtent',
-    ol.layer.Base.prototype.getExtent);
 
 
 /**
- * @return {number|undefined} The maximum resolution of the layer.
- * @observable
- * @api stable
+ * @return {WebGLRenderingContext} GL.
+ * @api
  */
-ol.layer.Base.prototype.getMaxResolution = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
+ol.webgl.Context.prototype.getGL = function() {
+  return this.gl_;
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getMaxResolution',
-    ol.layer.Base.prototype.getMaxResolution);
 
 
 /**
- * @return {number|undefined} The minimum resolution of the layer.
- * @observable
- * @api stable
+ * 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.layer.Base.prototype.getMinResolution = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getMinResolution',
-    ol.layer.Base.prototype.getMinResolution);
-
-
-/**
- * @return {number|undefined} The opacity of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.getOpacity = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.OPACITY));
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getOpacity',
-    ol.layer.Base.prototype.getOpacity);
-
-
-/**
- * @return {number|undefined} The saturation of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.getSaturation = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.SATURATION));
+ol.webgl.Context.prototype.getShader = function(shaderObject) {
+  var shaderKey = goog.getUid(shaderObject);
+  if (shaderKey in this.shaderCache_) {
+    return this.shaderCache_[shaderKey];
+  } else {
+    var gl = this.getGL();
+    var shader = gl.createShader(shaderObject.getType());
+    gl.shaderSource(shader, shaderObject.getSource());
+    gl.compileShader(shader);
+    if (goog.DEBUG) {
+      if (!gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) &&
+          !gl.isContextLost()) {
+        goog.log.error(this.logger_, gl.getShaderInfoLog(shader));
+      }
+    }
+    goog.asserts.assert(
+        gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) ||
+        gl.isContextLost());
+    this.shaderCache_[shaderKey] = shader;
+    return shader;
+  }
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getSaturation',
-    ol.layer.Base.prototype.getSaturation);
 
 
 /**
- * @return {ol.source.State} Source state.
- */
-ol.layer.Base.prototype.getSourceState = goog.abstractMethod;
-
-
-/**
- * @return {boolean|undefined} The visiblity of the layer.
- * @observable
- * @api stable
+ * Get the program from the cache if it's in the cache. Otherwise create
+ * the WebGL program, attach the shaders to it, and add an entry to the
+ * cache.
+ * @param {ol.webgl.shader.Fragment} fragmentShaderObject Fragment shader.
+ * @param {ol.webgl.shader.Vertex} vertexShaderObject Vertex shader.
+ * @return {WebGLProgram} Program.
  */
-ol.layer.Base.prototype.getVisible = function() {
-  return /** @type {boolean|undefined} */ (
-      this.get(ol.layer.LayerProperty.VISIBLE));
+ol.webgl.Context.prototype.getProgram = function(
+    fragmentShaderObject, vertexShaderObject) {
+  var programKey =
+      goog.getUid(fragmentShaderObject) + '/' + goog.getUid(vertexShaderObject);
+  if (programKey in this.programCache_) {
+    return this.programCache_[programKey];
+  } else {
+    var gl = this.getGL();
+    var program = gl.createProgram();
+    gl.attachShader(program, this.getShader(fragmentShaderObject));
+    gl.attachShader(program, this.getShader(vertexShaderObject));
+    gl.linkProgram(program);
+    if (goog.DEBUG) {
+      if (!gl.getProgramParameter(program, goog.webgl.LINK_STATUS) &&
+          !gl.isContextLost()) {
+        goog.log.error(this.logger_, gl.getProgramInfoLog(program));
+      }
+    }
+    goog.asserts.assert(
+        gl.getProgramParameter(program, goog.webgl.LINK_STATUS) ||
+        gl.isContextLost());
+    this.programCache_[programKey] = program;
+    return program;
+  }
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getVisible',
-    ol.layer.Base.prototype.getVisible);
 
 
 /**
- * Adjust the layer brightness.  A value of -1 will render the layer completely
- * black.  A value of 0 will leave the brightness unchanged.  A value of 1 will
- * render the layer completely white.  Other values are linear multipliers on
- * the effect (values are clamped between -1 and 1).
- *
- * The filter effects draft [1] says the brightness function is supposed to
- * render 0 black, 1 unchanged, and all other values as a linear multiplier.
- *
- * The current WebKit implementation clamps values between -1 (black) and 1
- * (white) [2].  There is a bug open to change the filter effect spec [3].
- *
- * TODO: revisit this if the spec is still unmodified before we release
- *
- * [1] https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
- * [2] https://github.com/WebKit/webkit/commit/8f4765e569
- * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647
- *
- * @param {number|undefined} brightness The brightness of the layer.
- * @observable
- * @api
+ * FIXME empy description for jsdoc
  */
-ol.layer.Base.prototype.setBrightness = function(brightness) {
-  this.set(ol.layer.LayerProperty.BRIGHTNESS, brightness);
+ol.webgl.Context.prototype.handleWebGLContextLost = function() {
+  goog.object.clear(this.bufferCache_);
+  goog.object.clear(this.shaderCache_);
+  goog.object.clear(this.programCache_);
+  this.currentProgram_ = null;
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setBrightness',
-    ol.layer.Base.prototype.setBrightness);
 
 
 /**
- * Adjust the layer contrast.  A value of 0 will render the layer completely
- * grey.  A value of 1 will leave the contrast unchanged.  Other values are
- * linear multipliers on the effect (and values over 1 are permitted).
- *
- * @param {number|undefined} contrast The contrast of the layer.
- * @observable
- * @api
+ * FIXME empy description for jsdoc
  */
-ol.layer.Base.prototype.setContrast = function(contrast) {
-  this.set(ol.layer.LayerProperty.CONTRAST, contrast);
+ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setContrast',
-    ol.layer.Base.prototype.setContrast);
 
 
 /**
- * Apply a hue-rotation to the layer.  A value of 0 will leave the hue
- * unchanged.  Other values are radians around the color circle.
- * @param {number|undefined} hue The hue of the layer.
- * @observable
+ * Just return false if that program is used already. Other use
+ * that program (call `gl.useProgram`) and make it the "current
+ * program".
+ * @param {WebGLProgram} program Program.
+ * @return {boolean} Changed.
  * @api
  */
-ol.layer.Base.prototype.setHue = function(hue) {
-  this.set(ol.layer.LayerProperty.HUE, hue);
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setHue',
-    ol.layer.Base.prototype.setHue);
-
-
-/**
- * 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);
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setExtent',
-    ol.layer.Base.prototype.setExtent);
-
-
-/**
- * @param {number|undefined} 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);
+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;
+  }
 };
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setMaxResolution',
-    ol.layer.Base.prototype.setMaxResolution);
 
 
 /**
- * @param {number|undefined} minResolution The minimum resolution of the layer.
- * @observable
- * @api stable
+ * @private
+ * @type {goog.log.Logger}
  */
-ol.layer.Base.prototype.setMinResolution = function(minResolution) {
-  this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution);
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setMinResolution',
-    ol.layer.Base.prototype.setMinResolution);
-
+ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context');
 
-/**
- * @param {number|undefined} opacity The opacity of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.setOpacity = function(opacity) {
-  this.set(ol.layer.LayerProperty.OPACITY, opacity);
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setOpacity',
-    ol.layer.Base.prototype.setOpacity);
+// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)
 
+goog.provide('ol.renderer.webgl.Map');
 
-/**
- * Adjust layer saturation.  A value of 0 will render the layer completely
- * unsaturated.  A value of 1 will leave the saturation unchanged.  Other
- * values are linear multipliers of the effect (and values over 1 are
- * permitted).
- *
- * @param {number|undefined} saturation The saturation of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.setSaturation = function(saturation) {
-  this.set(ol.layer.LayerProperty.SATURATION, saturation);
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setSaturation',
-    ol.layer.Base.prototype.setSaturation);
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.log');
+goog.require('goog.log.Logger');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('goog.webgl');
+goog.require('ol');
+goog.require('ol.RendererType');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.layer.Image');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.render.Event');
+goog.require('ol.render.EventType');
+goog.require('ol.render.webgl.Immediate');
+goog.require('ol.render.webgl.ReplayGroup');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.vector');
+goog.require('ol.renderer.webgl.ImageLayer');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.renderer.webgl.TileLayer');
+goog.require('ol.renderer.webgl.VectorLayer');
+goog.require('ol.source.State');
+goog.require('ol.structs.LRUCache');
+goog.require('ol.structs.PriorityQueue');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Context');
+goog.require('ol.webgl.WebGLContextEventType');
 
 
 /**
- * @param {boolean|undefined} visible The visiblity of the layer.
- * @observable
- * @api stable
+ * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}}
  */
-ol.layer.Base.prototype.setVisible = function(visible) {
-  this.set(ol.layer.LayerProperty.VISIBLE, visible);
-};
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setVisible',
-    ol.layer.Base.prototype.setVisible);
-
-goog.provide('ol.layer.Layer');
-
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol.layer.Base');
-goog.require('ol.source.Source');
+ol.renderer.webgl.TextureCacheEntry;
 
 
 
 /**
- * @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.
- *
  * @constructor
- * @extends {ol.layer.Base}
- * @fires ol.render.Event
- * @fires change Triggered when the state of the source changes.
- * @param {olx.layer.LayerOptions} options Layer options.
- * @api stable
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.Map} map Map.
  */
-ol.layer.Layer = function(options) {
-
-  var baseOptions = goog.object.clone(options);
-  delete baseOptions.source;
+ol.renderer.webgl.Map = function(container, map) {
 
-  goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
+  goog.base(this, container, map);
 
   /**
    * @private
-   * @type {ol.source.Source}
+   * @type {HTMLCanvasElement}
    */
-  this.source_ = options.source;
-
-  goog.events.listen(this.source_, goog.events.EventType.CHANGE,
-      this.handleSourceChange_, false, this);
-
-};
-goog.inherits(ol.layer.Layer, ol.layer.Base);
-
-
-/**
- * Return `true` if the layer is visible, and if the passed resolution is
- * between the layer's minResolution and maxResolution. The comparison is
- * inclusive for `minResolution` and exclusive for `maxResolution`.
- * @param {ol.layer.LayerState} layerState Layer state.
- * @param {number} resolution Resolution.
- * @return {boolean} The layer is visible at the given resolution.
- */
-ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
-  return layerState.visible && resolution >= layerState.minResolution &&
-      resolution < layerState.maxResolution;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
-  var array = (goog.isDef(opt_array)) ? opt_array : [];
-  array.push(this);
-  return array;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
-  var states = (goog.isDef(opt_states)) ? opt_states : [];
-  states.push(this.getLayerState());
-  return states;
-};
-
-
-/**
- * @return {ol.source.Source} Source.
- * @api stable
- */
-ol.layer.Layer.prototype.getSource = function() {
-  return this.source_;
-};
-
-
-/**
-  * @inheritDoc
-  */
-ol.layer.Layer.prototype.getSourceState = function() {
-  return this.getSource().getState();
-};
-
-
-/**
- * @private
- */
-ol.layer.Layer.prototype.handleSourceChange_ = function() {
-  this.dispatchChangeEvent();
-};
-
-goog.provide('ol.tilegrid.TileGrid');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('ol');
-goog.require('ol.Coordinate');
-goog.require('ol.TileCoord');
-goog.require('ol.TileRange');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.extent.Corner');
-goog.require('ol.proj');
-goog.require('ol.proj.METERS_PER_UNIT');
-goog.require('ol.proj.Projection');
-goog.require('ol.proj.Units');
-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) {
+  this.canvas_ = /** @type {HTMLCanvasElement} */
+      (goog.dom.createElement(goog.dom.TagName.CANVAS));
+  this.canvas_.style.width = '100%';
+  this.canvas_.style.height = '100%';
+  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
+  goog.dom.insertChildAt(container, this.canvas_, 0);
 
   /**
-   * @protected
+   * @private
    * @type {number}
    */
-  this.minZoom = goog.isDef(options.minZoom) ? options.minZoom : 0;
+  this.clipTileCanvasSize_ = 0;
 
   /**
    * @private
-   * @type {!Array.<number>}
+   * @type {CanvasRenderingContext2D}
    */
-  this.resolutions_ = options.resolutions;
-  goog.asserts.assert(goog.array.isSorted(this.resolutions_, function(a, b) {
-    return b - a;
-  }, true));
+  this.clipTileContext_ = ol.dom.createCanvasContext2D();
 
   /**
-   * @protected
-   * @type {number}
+   * @private
+   * @type {boolean}
    */
-  this.maxZoom = this.resolutions_.length - 1;
+  this.renderedVisible_ = true;
 
   /**
    * @private
-   * @type {ol.Coordinate}
+   * @type {WebGLRenderingContext}
    */
-  this.origin_ = goog.isDef(options.origin) ? options.origin : null;
+  this.gl_ = ol.webgl.getContext(this.canvas_, {
+    antialias: true,
+    depth: false,
+    failIfMajorPerformanceCaveat: true,
+    preserveDrawingBuffer: false,
+    stencil: true
+  });
+  goog.asserts.assert(!goog.isNull(this.gl_));
 
   /**
    * @private
-   * @type {Array.<ol.Coordinate>}
+   * @type {ol.webgl.Context}
    */
-  this.origins_ = null;
-  if (goog.isDef(options.origins)) {
-    this.origins_ = options.origins;
-    goog.asserts.assert(this.origins_.length == this.maxZoom + 1);
-  }
-  goog.asserts.assert(
-      (goog.isNull(this.origin_) && !goog.isNull(this.origins_)) ||
-      (!goog.isNull(this.origin_) && goog.isNull(this.origins_)));
+  this.context_ = new ol.webgl.Context(this.canvas_, this.gl_);
+
+  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST,
+      this.handleWebGLContextLost, false, this);
+  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED,
+      this.handleWebGLContextRestored, false, this);
 
   /**
    * @private
-   * @type {Array.<number>}
+   * @type {ol.structs.LRUCache.<ol.renderer.webgl.TextureCacheEntry|null>}
    */
-  this.tileSizes_ = null;
-  if (goog.isDef(options.tileSizes)) {
-    this.tileSizes_ = options.tileSizes;
-    goog.asserts.assert(this.tileSizes_.length == this.maxZoom + 1);
-  }
+  this.textureCache_ = new ol.structs.LRUCache();
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {ol.Coordinate}
    */
-  this.tileSize_ = goog.isDef(options.tileSize) ?
-      options.tileSize :
-      goog.isNull(this.tileSizes_) ? ol.DEFAULT_TILE_SIZE : undefined;
-  goog.asserts.assert(
-      (!goog.isDef(this.tileSize_) && !goog.isNull(this.tileSizes_)) ||
-      (goog.isDef(this.tileSize_) && goog.isNull(this.tileSizes_)));
-
-};
-
-
-/**
- * @private
- * @type {ol.TileCoord}
- */
-ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];
+  this.focus_ = null;
 
+  /**
+   * @private
+   * @type {ol.structs.PriorityQueue.<Array>}
+   */
+  this.tileTextureQueue_ = new ol.structs.PriorityQueue(
+      goog.bind(
+          /**
+           * @param {Array.<*>} element Element.
+           * @return {number} Priority.
+           * @this {ol.renderer.webgl.Map}
+           */
+          function(element) {
+            var tileCenter = /** @type {ol.Coordinate} */ (element[1]);
+            var tileResolution = /** @type {number} */ (element[2]);
+            var deltaX = tileCenter[0] - this.focus_[0];
+            var deltaY = tileCenter[1] - this.focus_[1];
+            return 65536 * Math.log(tileResolution) +
+                Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
+          }, this),
+      /**
+       * @param {Array.<*>} element Element.
+       * @return {string} Key.
+       */
+      function(element) {
+        return /** @type {ol.Tile} */ (element[0]).getKey();
+      });
 
-/**
- * @param {{extent: (ol.Extent|undefined),
- *          wrapX: (boolean|undefined)}=} opt_options Options.
- * @return {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=):
- *     ol.TileCoord} Tile coordinate transform.
- */
-ol.tilegrid.TileGrid.prototype.createTileCoordTransform = goog.abstractMethod;
-
+  /**
+   * @private
+   * @type {ol.PostRenderFunction}
+   */
+  this.loadNextTileTexture_ = goog.bind(
+      function(map, frameState) {
+        if (!this.tileTextureQueue_.isEmpty()) {
+          this.tileTextureQueue_.reprioritize();
+          var element = this.tileTextureQueue_.dequeue();
+          var tile = /** @type {ol.Tile} */ (element[0]);
+          var tileSize = /** @type {number} */ (element[3]);
+          var tileGutter = /** @type {number} */ (element[4]);
+          this.bindTileTexture(
+              tile, tileSize, tileGutter, goog.webgl.LINEAR, goog.webgl.LINEAR);
+        }
+      }, this);
 
-/**
- * @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;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textureCacheFrameMarkerCount_ = 0;
 
+  this.initializeGL_();
 
-/**
- * @return {number} Max zoom.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
-  return this.maxZoom;
 };
+goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
 
 
 /**
- * @return {number} Min zoom.
- * @api
+ * @param {ol.Tile} tile Tile.
+ * @param {number} tileSize Tile size.
+ * @param {number} tileGutter Tile gutter.
+ * @param {number} magFilter Mag filter.
+ * @param {number} minFilter Min filter.
  */
-ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
-  return this.minZoom;
+ol.renderer.webgl.Map.prototype.bindTileTexture =
+    function(tile, tileSize, tileGutter, magFilter, minFilter) {
+  var gl = this.getGL();
+  var tileKey = tile.getKey();
+  if (this.textureCache_.containsKey(tileKey)) {
+    var textureCacheEntry = this.textureCache_.get(tileKey);
+    goog.asserts.assert(!goog.isNull(textureCacheEntry));
+    gl.bindTexture(goog.webgl.TEXTURE_2D, textureCacheEntry.texture);
+    if (textureCacheEntry.magFilter != magFilter) {
+      gl.texParameteri(
+          goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter);
+      textureCacheEntry.magFilter = magFilter;
+    }
+    if (textureCacheEntry.minFilter != minFilter) {
+      gl.texParameteri(
+          goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, minFilter);
+      textureCacheEntry.minFilter = minFilter;
+    }
+  } else {
+    var texture = gl.createTexture();
+    gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
+    if (tileGutter > 0) {
+      var clipTileCanvas = this.clipTileContext_.canvas;
+      var clipTileContext = this.clipTileContext_;
+      if (this.clipTileCanvasSize_ != tileSize) {
+        clipTileCanvas.width = tileSize;
+        clipTileCanvas.height = tileSize;
+        this.clipTileCanvasSize_ = tileSize;
+      } else {
+        clipTileContext.clearRect(0, 0, tileSize, tileSize);
+      }
+      clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter,
+          tileSize, tileSize, 0, 0, tileSize, tileSize);
+      gl.texImage2D(goog.webgl.TEXTURE_2D, 0,
+          goog.webgl.RGBA, goog.webgl.RGBA,
+          goog.webgl.UNSIGNED_BYTE, clipTileCanvas);
+    } else {
+      gl.texImage2D(goog.webgl.TEXTURE_2D, 0,
+          goog.webgl.RGBA, goog.webgl.RGBA,
+          goog.webgl.UNSIGNED_BYTE, tile.getImage());
+    }
+    gl.texParameteri(
+        goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter);
+    gl.texParameteri(
+        goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, minFilter);
+    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S,
+        goog.webgl.CLAMP_TO_EDGE);
+    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T,
+        goog.webgl.CLAMP_TO_EDGE);
+    this.textureCache_.set(tileKey, {
+      texture: texture,
+      magFilter: magFilter,
+      minFilter: minFilter
+    });
+  }
 };
 
 
 /**
- * @param {number} z Z.
- * @return {ol.Coordinate} Origin.
- * @api stable
+ * @inheritDoc
  */
-ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
-  if (!goog.isNull(this.origin_)) {
-    return this.origin_;
+ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) {
+  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
+    return new ol.renderer.webgl.ImageLayer(this, layer);
+  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
+    return new ol.renderer.webgl.TileLayer(this, layer);
+  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
+    return new ol.renderer.webgl.VectorLayer(this, layer);
   } else {
-    goog.asserts.assert(!goog.isNull(this.origins_));
-    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
-    return this.origins_[z];
+    goog.asserts.fail();
+    return null;
   }
 };
 
 
 /**
- * @param {number} z Z.
- * @return {number} Resolution.
- * @api stable
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-ol.tilegrid.TileGrid.prototype.getResolution = function(z) {
-  goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
-  return this.resolutions_[z];
-};
+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;
 
-/**
- * @return {Array.<number>} Resolutions.
- * @api stable
- */
-ol.tilegrid.TileGrid.prototype.getResolutions = function() {
-  return this.resolutions_;
-};
+    var resolution = viewState.resolution;
+    var center = viewState.center;
+    var rotation = viewState.rotation;
+    var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
 
+    var vectorContext = new ol.render.webgl.Immediate(context,
+        center, resolution, rotation, size, extent, pixelRatio);
+    var replayGroup = new ol.render.webgl.ReplayGroup(tolerance, extent);
+    var composeEvent = new ol.render.Event(type, map, vectorContext,
+        replayGroup, frameState, null, context);
+    map.dispatchEvent(composeEvent);
 
-/**
- * @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;
+    replayGroup.finish(context);
+    if (!replayGroup.isEmpty()) {
+      // use default color values
+      var opacity = 1;
+      var brightness = 0;
+      var contrast = 1;
+      var hue = 0;
+      var saturation = 1;
+      replayGroup.replay(context, center, resolution, rotation, size,
+          pixelRatio, opacity, brightness, contrast, hue, saturation, {});
+    }
+    replayGroup.getDeleteResourcesFunction(context)();
+
+    vectorContext.flush();
+    this.replayGroup = replayGroup;
   }
 };
 
 
 /**
- * @param {number} z Z.
- * @param {ol.TileRange} tileRange Tile range.
- * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
- * @return {ol.Extent} Extent.
+ * @inheritDoc
  */
-ol.tilegrid.TileGrid.prototype.getTileRangeExtent =
-    function(z, tileRange, opt_extent) {
-  var origin = this.getOrigin(z);
-  var resolution = this.getResolution(z);
-  var tileSize = this.getTileSize(z);
-  var minX = origin[0] + tileRange.minX * tileSize * resolution;
-  var maxX = origin[0] + (tileRange.maxX + 1) * tileSize * resolution;
-  var minY = origin[1] + tileRange.minY * tileSize * resolution;
-  var maxY = origin[1] + (tileRange.maxY + 1) * tileSize * resolution;
-  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
+ol.renderer.webgl.Map.prototype.disposeInternal = function() {
+  var gl = this.getGL();
+  if (!gl.isContextLost()) {
+    this.textureCache_.forEach(
+        /**
+         * @param {?ol.renderer.webgl.TextureCacheEntry} textureCacheEntry
+         *     Texture cache entry.
+         */
+        function(textureCacheEntry) {
+          if (!goog.isNull(textureCacheEntry)) {
+            gl.deleteTexture(textureCacheEntry.texture);
+          }
+        });
+  }
+  goog.dispose(this.context_);
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
- * @return {ol.TileRange} Tile range.
+ * @param {ol.Map} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-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);
+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 (goog.isNull(textureCacheEntry)) {
+      if (+this.textureCache_.peekLastKey() == frameState.index) {
+        break;
+      } else {
+        --this.textureCacheFrameMarkerCount_;
+      }
+    } else {
+      gl.deleteTexture(textureCacheEntry.texture);
+    }
+    this.textureCache_.pop();
+  }
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} z Z.
- * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
- * @return {ol.TileRange} Tile range.
+ * @return {ol.webgl.Context}
  */
-ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ =
-    function(extent, z, opt_tileRange) {
-  var resolution = this.getResolution(z);
-  return this.getTileRangeForExtentAndResolution(
-      extent, resolution, opt_tileRange);
+ol.renderer.webgl.Map.prototype.getContext = function() {
+  return this.context_;
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @return {ol.Coordinate} Tile center.
+ * @return {WebGLRenderingContext} GL.
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) {
-  var origin = this.getOrigin(tileCoord[0]);
-  var resolution = this.getResolution(tileCoord[0]);
-  var tileSize = this.getTileSize(tileCoord[0]);
-  return [
-    origin[0] + (tileCoord[1] + 0.5) * tileSize * resolution,
-    origin[1] + (tileCoord[2] + 0.5) * tileSize * resolution
-  ];
+ol.renderer.webgl.Map.prototype.getGL = function() {
+  return this.gl_;
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Extent=} opt_extent Temporary extent object.
- * @return {ol.Extent} Extent.
+ * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordExtent =
-    function(tileCoord, opt_extent) {
-  var origin = this.getOrigin(tileCoord[0]);
-  var resolution = this.getResolution(tileCoord[0]);
-  var tileSize = this.getTileSize(tileCoord[0]);
-  var minX = origin[0] + tileCoord[1] * tileSize * resolution;
-  var minY = origin[1] + tileCoord[2] * tileSize * resolution;
-  var maxX = minX + tileSize * resolution;
-  var maxY = minY + tileSize * resolution;
-  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
+ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
+  return this.tileTextureQueue_;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(
-    coordinate, resolution, opt_tileCoord) {
-  return this.getTileCoordForXYAndResolution_(
-      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
+ol.renderer.webgl.Map.prototype.getType = function() {
+  return ol.RendererType.WEBGL;
 };
 
 
 /**
- * @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
+ * @param {goog.events.Event} event Event.
+ * @protected
  */
-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 = this.getTileSize(z);
-
-  var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize);
-  var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize);
-
-  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);
+ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) {
+  event.preventDefault();
+  this.textureCache_.clear();
+  this.textureCacheFrameMarkerCount_ = 0;
+  goog.object.forEach(this.getLayerRenderers(),
+      /**
+       * @param {ol.renderer.Layer} layerRenderer Layer renderer.
+       * @param {string} key Key.
+       * @param {Object.<string, ol.renderer.Layer>} object Object.
+       */
+      function(layerRenderer, key, object) {
+        goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
+        var webGLLayerRenderer = /** @type {ol.renderer.webgl.Layer} */
+            (layerRenderer);
+        webGLLayerRenderer.handleWebGLContextLost();
+      });
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} z Z.
- * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
- * @return {ol.TileCoord} Tile coordinate.
+ * @protected
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ =
-    function(coordinate, z, opt_tileCoord) {
-  var resolution = this.getResolution(z);
-  return this.getTileCoordForXYAndResolution_(
-      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
+ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
+  this.initializeGL_();
+  this.getMap().render();
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @return {number} Tile resolution.
+ * @private
  */
-ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) {
-  goog.asserts.assert(
-      this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom);
-  return this.resolutions_[tileCoord[0]];
+ol.renderer.webgl.Map.prototype.initializeGL_ = function() {
+  var gl = this.gl_;
+  gl.activeTexture(goog.webgl.TEXTURE0);
+  gl.blendFuncSeparate(
+      goog.webgl.SRC_ALPHA, goog.webgl.ONE_MINUS_SRC_ALPHA,
+      goog.webgl.ONE, goog.webgl.ONE_MINUS_SRC_ALPHA);
+  gl.disable(goog.webgl.CULL_FACE);
+  gl.disable(goog.webgl.DEPTH_TEST);
+  gl.disable(goog.webgl.SCISSOR_TEST);
+  gl.disable(goog.webgl.STENCIL_TEST);
 };
 
 
 /**
- * @param {number} z Z.
- * @return {number} Tile size.
- * @api stable
+ * @param {ol.Tile} tile Tile.
+ * @return {boolean} Is tile texture loaded.
  */
-ol.tilegrid.TileGrid.prototype.getTileSize = function(z) {
-  if (goog.isDef(this.tileSize_)) {
-    return this.tileSize_;
-  } else {
-    goog.asserts.assert(!goog.isNull(this.tileSizes_));
-    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
-    return this.tileSizes_[z];
-  }
+ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) {
+  return this.textureCache_.containsKey(tile.getKey());
 };
 
 
 /**
- * @param {number} resolution Resolution.
- * @return {number} Z.
+ * @private
+ * @type {goog.log.Logger}
  */
-ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) {
-  return ol.array.linearFindNearest(this.resolutions_, resolution, 0);
-};
+ol.renderer.webgl.Map.prototype.logger_ =
+    goog.log.getLogger('ol.renderer.webgl.Map');
 
 
 /**
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection.
+ * @inheritDoc
  */
-ol.tilegrid.getForProjection = function(projection) {
-  var tileGrid = projection.getDefaultTileGrid();
-  if (goog.isNull(tileGrid)) {
-    tileGrid = ol.tilegrid.createForProjection(projection);
-    projection.setDefaultTileGrid(tileGrid);
-  }
-  return tileGrid;
-};
+ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
 
+  var context = this.getContext();
+  var gl = this.getGL();
 
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number=} opt_maxZoom Maximum zoom level (default is
- *     ol.DEFAULT_MAX_ZOOM).
- * @param {number=} 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.createForExtent =
-    function(extent, opt_maxZoom, opt_tileSize, opt_corner) {
-  var tileSize = goog.isDef(opt_tileSize) ?
-      opt_tileSize : ol.DEFAULT_TILE_SIZE;
+  if (gl.isContextLost()) {
+    return false;
+  }
 
-  var corner = goog.isDef(opt_corner) ?
-      opt_corner : ol.extent.Corner.BOTTOM_LEFT;
+  if (goog.isNull(frameState)) {
+    if (this.renderedVisible_) {
+      goog.style.setElementShown(this.canvas_, false);
+      this.renderedVisible_ = false;
+    }
+    return false;
+  }
 
-  var resolutions = ol.tilegrid.resolutionsFromExtent(
-      extent, opt_maxZoom, tileSize);
+  this.focus_ = frameState.focus;
 
-  return new ol.tilegrid.TileGrid({
-    origin: ol.extent.getCorner(extent, corner),
-    resolutions: resolutions,
-    tileSize: tileSize
-  });
-};
+  this.textureCache_.set((-frameState.index).toString(), null);
+  ++this.textureCacheFrameMarkerCount_;
 
+  /** @type {Array.<ol.layer.LayerState>} */
+  var layerStatesToDraw = [];
+  var layerStatesArray = frameState.layerStatesArray;
+  var viewResolution = frameState.viewState.resolution;
+  var i, ii, layerRenderer, layerState;
+  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
+    layerState = layerStatesArray[i];
+    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
+        layerState.sourceState == ol.source.State.READY) {
+      layerRenderer = this.getLayerRenderer(layerState.layer);
+      goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
+      if (layerRenderer.prepareFrame(frameState, layerState, context)) {
+        layerStatesToDraw.push(layerState);
+      }
+    }
+  }
 
-/**
- * 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=} 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 = goog.isDef(opt_maxZoom) ?
-      opt_maxZoom : ol.DEFAULT_MAX_ZOOM;
+  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;
+  }
 
-  var height = ol.extent.getHeight(extent);
-  var width = ol.extent.getWidth(extent);
+  gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, null);
 
-  var tileSize = goog.isDef(opt_tileSize) ?
-      opt_tileSize : ol.DEFAULT_TILE_SIZE;
-  var maxResolution = Math.max(
-      width / tileSize, height / tileSize);
+  gl.clearColor(0, 0, 0, 0);
+  gl.clear(goog.webgl.COLOR_BUFFER_BIT);
+  gl.enable(goog.webgl.BLEND);
+  gl.viewport(0, 0, this.canvas_.width, this.canvas_.height);
 
-  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;
-};
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
 
-
-/**
- * @param {ol.proj.ProjectionLike} projection Projection.
- * @param {number=} opt_maxZoom Maximum zoom level (default is
- *     ol.DEFAULT_MAX_ZOOM).
- * @param {number=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE).
- * @param {ol.extent.Corner=} opt_corner Extent corner (default is
- *     ol.extent.Corner.BOTTOM_LEFT).
- * @return {ol.tilegrid.TileGrid} TileGrid instance.
- */
-ol.tilegrid.createForProjection =
-    function(projection, opt_maxZoom, opt_tileSize, opt_corner) {
-  var extent = ol.tilegrid.extentFromProjection(projection);
-  return ol.tilegrid.createForExtent(
-      extent, opt_maxZoom, opt_tileSize, opt_corner);
-};
-
-
-/**
- * Generate a tile grid extent from a projection.  If the projection has an
- * extent, it is used.  If not, a global extent is assumed.
- * @param {ol.proj.ProjectionLike} projection Projection.
- * @return {ol.Extent} Extent.
- */
-ol.tilegrid.extentFromProjection = function(projection) {
-  projection = ol.proj.get(projection);
-  var extent = projection.getExtent();
-  if (goog.isNull(extent)) {
-    var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] /
-        projection.getMetersPerUnit();
-    extent = ol.extent.createOrUpdate(-half, -half, half, half);
+  for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) {
+    layerState = layerStatesToDraw[i];
+    layerRenderer = this.getLayerRenderer(layerState.layer);
+    goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
+    layerRenderer.composeFrame(frameState, layerState, context);
   }
-  return extent;
-};
-
-goog.provide('ol.source.Tile');
-goog.provide('ol.source.TileOptions');
-
-goog.require('goog.functions');
-goog.require('ol.Attribution');
-goog.require('ol.Extent');
-goog.require('ol.TileRange');
-goog.require('ol.source.Source');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.TileGrid');
 
+  if (!this.renderedVisible_) {
+    goog.style.setElementShown(this.canvas_, true);
+    this.renderedVisible_ = true;
+  }
 
-/**
- * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
- *            extent: (ol.Extent|undefined),
- *            logo: (string|olx.LogoOptions|undefined),
- *            opaque: (boolean|undefined),
- *            tilePixelRatio: (number|undefined),
- *            projection: ol.proj.ProjectionLike,
- *            state: (ol.source.State|undefined),
- *            tileGrid: (ol.tilegrid.TileGrid|undefined)}}
- */
-ol.source.TileOptions;
+  this.calculateMatrices2D(frameState);
 
+  if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
+      ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
+    frameState.postRenderFunctions.push(goog.bind(this.expireCache_, this));
+  }
 
+  if (!this.tileTextureQueue_.isEmpty()) {
+    frameState.postRenderFunctions.push(this.loadNextTileTexture_);
+    frameState.animate = true;
+  }
 
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for sources providing images divided into a tile grid.
- *
- * @constructor
- * @extends {ol.source.Source}
- * @param {ol.source.TileOptions} options Tile source options.
- */
-ol.source.Tile = function(options) {
+  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
 
-  goog.base(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    projection: options.projection,
-    state: options.state
-  });
+  this.scheduleRemoveUnusedLayerRenderers(frameState);
+  this.scheduleExpireIconCache(frameState);
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.opaque_ = goog.isDef(options.opaque) ? options.opaque : false;
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.tilePixelRatio_ = goog.isDef(options.tilePixelRatio) ?
-      options.tilePixelRatio : 1;
+// FIXME recheck layer/map projection compatability when projection changes
+// FIXME layer renderers should skip when they can't reproject
+// FIXME add tilt and height?
 
-  /**
-   * @protected
-   * @type {ol.tilegrid.TileGrid}
-   */
-  this.tileGrid = goog.isDef(options.tileGrid) ? options.tileGrid : null;
+goog.provide('ol.Map');
+goog.provide('ol.MapProperty');
 
-};
-goog.inherits(ol.source.Tile, ol.source.Source);
+goog.require('goog.Uri.QueryData');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.async.AnimationDelay');
+goog.require('goog.async.nextTick');
+goog.require('goog.debug.Console');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.ViewportSizeMonitor');
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyHandler');
+goog.require('goog.events.KeyHandler.EventType');
+goog.require('goog.events.MouseWheelHandler');
+goog.require('goog.events.MouseWheelHandler.EventType');
+goog.require('goog.log');
+goog.require('goog.log.Level');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('goog.vec.Mat4');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.MapBrowserEvent');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.MapBrowserEventHandler');
+goog.require('ol.MapEvent');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
+goog.require('ol.ObjectEvent');
+goog.require('ol.ObjectEventType');
+goog.require('ol.Pixel');
+goog.require('ol.PostRenderFunction');
+goog.require('ol.PreRenderFunction');
+goog.require('ol.RendererType');
+goog.require('ol.Size');
+goog.require('ol.TileQueue');
+goog.require('ol.View');
+goog.require('ol.ViewHint');
+goog.require('ol.control');
+goog.require('ol.extent');
+goog.require('ol.has');
+goog.require('ol.interaction');
+goog.require('ol.layer.Base');
+goog.require('ol.layer.Group');
+goog.require('ol.proj');
+goog.require('ol.proj.common');
+goog.require('ol.renderer.Map');
+goog.require('ol.renderer.canvas.Map');
+goog.require('ol.renderer.dom.Map');
+goog.require('ol.renderer.webgl.Map');
+goog.require('ol.structs.PriorityQueue');
+goog.require('ol.tilecoord');
+goog.require('ol.vec.Mat4');
 
 
 /**
- * @return {boolean} Can expire cache.
+ * @const
+ * @type {string}
  */
-ol.source.Tile.prototype.canExpireCache = goog.functions.FALSE;
+ol.OL3_URL = 'http://openlayers.org/';
 
 
 /**
- * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
+ * @const
+ * @type {string}
  */
-ol.source.Tile.prototype.expireCache = goog.abstractMethod;
+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';
 
 
 /**
- * Look for loaded tiles over a given tile range and zoom level.  Adds
- * properties to the provided lookup representing key/tile pairs for already
- * loaded tiles.
- *
- * @param {Object.<number, Object.<string, ol.Tile>>} loadedTilesByZ A lookup of
- *     loaded tiles by zoom level.
- * @param {function(number, number, number): ol.Tile} getTileIfLoaded A function
- *     that returns the tile only if it is fully loaded.
- * @param {number} z Zoom level.
- * @param {ol.TileRange} tileRange Tile range.
- * @return {boolean} The tile range is fully covered with loaded tiles.
+ * @type {Array.<ol.RendererType>}
  */
-ol.source.Tile.prototype.findLoadedTiles = function(loadedTilesByZ,
-    getTileIfLoaded, z, tileRange) {
-  // FIXME this could be more efficient about filling partial holes
-  var fullyCovered = true;
-  var tile, tileCoordKey, x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      tileCoordKey = this.getKeyZXY(z, x, y);
-      if (loadedTilesByZ[z] && loadedTilesByZ[z][tileCoordKey]) {
-        continue;
-      }
-      tile = getTileIfLoaded(z, x, y);
-      if (!goog.isNull(tile)) {
-        if (!loadedTilesByZ[z]) {
-          loadedTilesByZ[z] = {};
-        }
-        loadedTilesByZ[z][tileCoordKey] = tile;
-      } else {
-        fullyCovered = false;
-      }
-    }
-  }
-  return fullyCovered;
-};
+ol.DEFAULT_RENDERER_TYPES = [
+  ol.RendererType.CANVAS,
+  ol.RendererType.WEBGL,
+  ol.RendererType.DOM
+];
 
 
 /**
- * @return {number} Gutter.
+ * @enum {string}
  */
-ol.source.Tile.prototype.getGutter = function() {
-  return 0;
+ol.MapProperty = {
+  LAYERGROUP: 'layergroup',
+  SIZE: 'size',
+  TARGET: 'target',
+  VIEW: 'view'
 };
 
 
+
 /**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {string} Key.
- * @protected
+ * @classdesc
+ * The map is the core component of OpenLayers. For a map to render, a view,
+ * one or more layers, and a target container are needed:
+ *
+ *     var map = new ol.Map({
+ *       view: new ol.View({
+ *         center: [0, 0],
+ *         zoom: 1
+ *       }),
+ *       layers: [
+ *         new ol.layer.Tile({
+ *           source: new ol.source.MapQuest({layer: 'osm'})
+ *         })
+ *       ],
+ *       target: 'map'
+ *     });
+ *
+ * The above snippet creates a map using a {@link ol.layer.Tile} to display
+ * {@link ol.source.MapQuest} OSM data and render it to a DOM element with the
+ * id `map`.
+ *
+ * The constructor places a viewport container (with CSS class name
+ * `ol-viewport`) in the target element (see `getViewport()`), and then two
+ * further elements within the viewport: one with CSS class name
+ * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with
+ * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent`
+ * option of {@link ol.Overlay} for the difference). The map itself is placed in
+ * a further element within the viewport, either DOM or Canvas, depending on the
+ * renderer.
+ *
+ * @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.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
+ol.Map = function(options) {
 
+  goog.base(this);
 
-/**
- * @return {boolean} Opaque.
- */
-ol.source.Tile.prototype.getOpaque = function() {
-  return this.opaque_;
-};
+  var optionsInternal = ol.Map.createOptionsInternal(options);
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.pixelRatio_ = goog.isDef(options.pixelRatio) ?
+      options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;
 
-/**
- * @inheritDoc
- */
-ol.source.Tile.prototype.getResolutions = function() {
-  return this.tileGrid.getResolutions();
-};
+  /**
+   * @private
+   * @type {Object}
+   */
+  this.logos_ = optionsInternal.logos;
 
+  /**
+   * @private
+   * @type {goog.async.AnimationDelay}
+   */
+  this.animationDelay_ =
+      new goog.async.AnimationDelay(this.renderFrame_, undefined, this);
+  this.registerDisposable(this.animationDelay_);
 
-/**
- * @param {number} z Tile coordinate z.
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection=} opt_projection Projection.
- * @return {!ol.Tile} Tile.
- */
-ol.source.Tile.prototype.getTile = goog.abstractMethod;
+  /**
+   * @private
+   * @type {goog.vec.Mat4.Number}
+   */
+  this.coordinateToPixelMatrix_ = goog.vec.Mat4.createNumber();
 
+  /**
+   * @private
+   * @type {goog.vec.Mat4.Number}
+   */
+  this.pixelToCoordinateMatrix_ = goog.vec.Mat4.createNumber();
 
-/**
- * @return {ol.tilegrid.TileGrid} Tile grid.
- * @api stable
- */
-ol.source.Tile.prototype.getTileGrid = function() {
-  return this.tileGrid;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.frameIndex_ = 0;
 
+  /**
+   * @private
+   * @type {?olx.FrameState}
+   */
+  this.frameState_ = null;
 
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.tilegrid.TileGrid} Tile grid.
- */
-ol.source.Tile.prototype.getTileGridForProjection = function(projection) {
-  if (goog.isNull(this.tileGrid)) {
-    return ol.tilegrid.getForProjection(projection);
-  } else {
-    return this.tileGrid;
-  }
-};
+  /**
+   * The extent at the previous 'moveend' event.
+   * @private
+   * @type {ol.Extent}
+   */
+  this.previousExtent_ = ol.extent.createEmpty();
 
+  /**
+   * @private
+   * @type {goog.events.Key}
+   */
+  this.viewPropertyListenerKey_ = null;
 
-/**
- * @param {number} z Z.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {number} Tile size.
- */
-ol.source.Tile.prototype.getTilePixelSize =
-    function(z, pixelRatio, projection) {
-  var tileGrid = this.getTileGridForProjection(projection);
-  return tileGrid.getTileSize(z) * this.tilePixelRatio_;
-};
+  /**
+   * @private
+   * @type {Array.<goog.events.Key>}
+   */
+  this.layerGroupPropertyListenerKeys_ = null;
 
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.viewport_ = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-viewport');
+  this.viewport_.style.position = 'relative';
+  this.viewport_.style.overflow = 'hidden';
+  this.viewport_.style.width = '100%';
+  this.viewport_.style.height = '100%';
+  // prevent page zoom on IE >= 10 browsers
+  this.viewport_.style.msTouchAction = 'none';
+  if (ol.has.TOUCH) {
+    this.viewport_.className = 'ol-touch';
+  }
 
-/**
- * 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.
- */
-ol.source.Tile.prototype.useTile = goog.nullFunction;
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.overlayContainer_ = goog.dom.createDom(goog.dom.TagName.DIV,
+      'ol-overlaycontainer');
+  goog.dom.appendChild(this.viewport_, this.overlayContainer_);
 
-goog.provide('ol.renderer.Layer');
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.overlayContainerStopEvent_ = goog.dom.createDom(goog.dom.TagName.DIV,
+      'ol-overlaycontainer-stopevent');
+  goog.events.listen(this.overlayContainerStopEvent_, [
+    goog.events.EventType.CLICK,
+    goog.events.EventType.DBLCLICK,
+    goog.events.EventType.MOUSEDOWN,
+    goog.events.EventType.TOUCHSTART,
+    goog.events.EventType.MSPOINTERDOWN,
+    ol.MapBrowserEvent.EventType.POINTERDOWN,
+    goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel'
+  ], goog.events.Event.stopPropagation);
+  goog.dom.appendChild(this.viewport_, this.overlayContainerStopEvent_);
 
-goog.require('goog.Disposable');
-goog.require('goog.asserts');
-goog.require('ol.ImageState');
-goog.require('ol.TileRange');
-goog.require('ol.TileState');
-goog.require('ol.layer.Layer');
-goog.require('ol.layer.LayerState');
-goog.require('ol.source.Source');
-goog.require('ol.source.State');
-goog.require('ol.source.Tile');
-goog.require('ol.tilecoord');
+  var mapBrowserEventHandler = new ol.MapBrowserEventHandler(this);
+  goog.events.listen(mapBrowserEventHandler,
+      goog.object.getValues(ol.MapBrowserEvent.EventType),
+      this.handleMapBrowserEvent, false, this);
+  this.registerDisposable(mapBrowserEventHandler);
 
+  /**
+   * @private
+   * @type {Element|Document}
+   */
+  this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
 
+  /**
+   * @private
+   * @type {goog.events.KeyHandler}
+   */
+  this.keyHandler_ = new goog.events.KeyHandler();
+  goog.events.listen(this.keyHandler_, goog.events.KeyHandler.EventType.KEY,
+      this.handleBrowserEvent, false, this);
+  this.registerDisposable(this.keyHandler_);
 
-/**
- * @constructor
- * @extends {goog.Disposable}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Layer} layer Layer.
- * @suppress {checkStructDictInheritance}
- * @struct
- */
-ol.renderer.Layer = function(mapRenderer, layer) {
+  var mouseWheelHandler = new goog.events.MouseWheelHandler(this.viewport_);
+  goog.events.listen(mouseWheelHandler,
+      goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
+      this.handleBrowserEvent, false, this);
+  this.registerDisposable(mouseWheelHandler);
 
-  goog.base(this);
+  /**
+   * @type {ol.Collection.<ol.control.Control>}
+   * @private
+   */
+  this.controls_ = optionsInternal.controls;
 
   /**
+   * @type {olx.DeviceOptions}
    * @private
-   * @type {ol.renderer.Map}
    */
-  this.mapRenderer_ = mapRenderer;
+  this.deviceOptions_ = optionsInternal.deviceOptions;
 
   /**
+   * @type {ol.Collection.<ol.interaction.Interaction>}
    * @private
-   * @type {ol.layer.Layer}
    */
-  this.layer_ = layer;
+  this.interactions_ = optionsInternal.interactions;
 
+  /**
+   * @type {ol.Collection.<ol.Overlay>}
+   * @private
+   */
+  this.overlays_ = optionsInternal.overlays;
 
-};
-goog.inherits(ol.renderer.Layer, goog.Disposable);
+  /**
+   * @type {ol.renderer.Map}
+   * @private
+   */
+  this.renderer_ =
+      new optionsInternal.rendererConstructor(this.viewport_, this);
+  this.registerDisposable(this.renderer_);
 
+  /**
+   * @private
+   */
+  this.viewportSizeMonitor_ = new goog.dom.ViewportSizeMonitor();
+  this.registerDisposable(this.viewportSizeMonitor_);
 
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {olx.FrameState} frameState Frame state.
- * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T
- */
-ol.renderer.Layer.prototype.forEachFeatureAtPixel = goog.nullFunction;
+  goog.events.listen(this.viewportSizeMonitor_, goog.events.EventType.RESIZE,
+      this.updateSize, false, this);
+
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.focus_ = null;
+
+  /**
+   * @private
+   * @type {Array.<ol.PreRenderFunction>}
+   */
+  this.preRenderFunctions_ = [];
+
+  /**
+   * @private
+   * @type {Array.<ol.PostRenderFunction>}
+   */
+  this.postRenderFunctions_ = [];
+
+  /**
+   * @private
+   * @type {ol.TileQueue}
+   */
+  this.tileQueue_ = new ol.TileQueue(
+      goog.bind(this.getTilePriority, this),
+      goog.bind(this.handleTileChange_, this));
+
+  /**
+   * Uids of features to skip at rendering time.
+   * @type {Object.<string, boolean>}
+   * @private
+   */
+  this.skippedFeatureUids_ = {};
+
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP),
+      this.handleLayerGroupChanged_, false, this);
+  goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
+      this.handleViewChanged_, false, this);
+  goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE),
+      this.handleSizeChanged_, false, this);
+  goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET),
+      this.handleTargetChanged_, false, this);
+
+  // setProperties will trigger the rendering of the map if the map
+  // is "defined" already.
+  this.setProperties(optionsInternal.values);
+
+  this.controls_.forEach(
+      /**
+       * @param {ol.control.Control} control Control.
+       * @this {ol.Map}
+       */
+      function(control) {
+        control.setMap(this);
+      }, this);
+
+  goog.events.listen(this.controls_, ol.CollectionEventType.ADD,
+      /**
+       * @param {ol.CollectionEvent} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(this);
+      }, false, this);
+
+  goog.events.listen(this.controls_, ol.CollectionEventType.REMOVE,
+      /**
+       * @param {ol.CollectionEvent} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(null);
+      }, false, this);
+
+  this.interactions_.forEach(
+      /**
+       * @param {ol.interaction.Interaction} interaction Interaction.
+       * @this {ol.Map}
+       */
+      function(interaction) {
+        interaction.setMap(this);
+      }, this);
+
+  goog.events.listen(this.interactions_, ol.CollectionEventType.ADD,
+      /**
+       * @param {ol.CollectionEvent} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(this);
+      }, false, this);
+
+  goog.events.listen(this.interactions_, ol.CollectionEventType.REMOVE,
+      /**
+       * @param {ol.CollectionEvent} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(null);
+      }, false, this);
+
+  this.overlays_.forEach(
+      /**
+       * @param {ol.Overlay} overlay Overlay.
+       * @this {ol.Map}
+       */
+      function(overlay) {
+        overlay.setMap(this);
+      }, this);
+
+  goog.events.listen(this.overlays_, ol.CollectionEventType.ADD,
+      /**
+       * @param {ol.CollectionEvent} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(this);
+      }, false, this);
+
+  goog.events.listen(this.overlays_, ol.CollectionEventType.REMOVE,
+      /**
+       * @param {ol.CollectionEvent} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(null);
+      }, false, this);
+
+};
+goog.inherits(ol.Map, ol.Object);
 
 
 /**
- * @protected
- * @return {ol.layer.Layer} Layer.
+ * Add the given control to the map.
+ * @param {ol.control.Control} control Control.
+ * @api stable
  */
-ol.renderer.Layer.prototype.getLayer = function() {
-  return this.layer_;
+ol.Map.prototype.addControl = function(control) {
+  var controls = this.getControls();
+  goog.asserts.assert(goog.isDef(controls));
+  controls.push(control);
 };
 
 
 /**
- * @protected
- * @return {ol.Map} Map.
+ * Add the given interaction to the map.
+ * @param {ol.interaction.Interaction} interaction Interaction to add.
+ * @api stable
  */
-ol.renderer.Layer.prototype.getMap = function() {
-  return this.mapRenderer_.getMap();
+ol.Map.prototype.addInteraction = function(interaction) {
+  var interactions = this.getInteractions();
+  goog.asserts.assert(goog.isDef(interactions));
+  interactions.push(interaction);
 };
 
 
 /**
- * @protected
- * @return {ol.renderer.Map} Map renderer.
+ * Adds the given layer to the top of this map.
+ * @param {ol.layer.Base} layer Layer.
+ * @api stable
  */
-ol.renderer.Layer.prototype.getMapRenderer = function() {
-  return this.mapRenderer_;
+ol.Map.prototype.addLayer = function(layer) {
+  var layers = this.getLayerGroup().getLayers();
+  layers.push(layer);
 };
 
 
 /**
- * Handle changes in image state.
- * @param {goog.events.Event} event Image change event.
- * @protected
+ * Add the given overlay to the map.
+ * @param {ol.Overlay} overlay Overlay.
+ * @api stable
  */
-ol.renderer.Layer.prototype.handleImageChange = function(event) {
-  var image = /** @type {ol.Image} */ (event.target);
-  if (image.getState() === ol.ImageState.LOADED) {
-    this.renderIfReadyAndVisible();
-  }
+ol.Map.prototype.addOverlay = function(overlay) {
+  var overlays = this.getOverlays();
+  goog.asserts.assert(goog.isDef(overlays));
+  overlays.push(overlay);
 };
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.layer.LayerState} layerState Layer state.
- * @return {boolean} whether composeFrame should be called.
+ * 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.renderer.Layer.prototype.prepareFrame = goog.abstractMethod;
+ol.Map.prototype.beforeRender = function(var_args) {
+  this.render();
+  Array.prototype.push.apply(this.preRenderFunctions_, arguments);
+};
 
 
 /**
- * @protected
+ * @param {ol.PreRenderFunction} preRenderFunction Pre-render function.
+ * @return {boolean} Whether the preRenderFunction has been found and removed.
  */
-ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
-  var layer = this.getLayer();
-  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
-    this.getMap().render();
-  }
+ol.Map.prototype.removePreRenderFunction = function(preRenderFunction) {
+  return goog.array.remove(this.preRenderFunctions_, preRenderFunction);
 };
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Tile} tileSource Tile source.
- * @protected
+ *
+ * @inheritDoc
  */
-ol.renderer.Layer.prototype.scheduleExpireCache =
-    function(frameState, tileSource) {
-  if (tileSource.canExpireCache()) {
-    frameState.postRenderFunctions.push(
-        goog.partial(
-            /**
-             * @param {ol.source.Tile} tileSource Tile source.
-             * @param {ol.Map} map Map.
-             * @param {olx.FrameState} frameState Frame state.
-             */
-            function(tileSource, map, frameState) {
-              var tileSourceKey = goog.getUid(tileSource).toString();
-              tileSource.expireCache(frameState.usedTiles[tileSourceKey]);
-            }, tileSource));
-  }
+ol.Map.prototype.disposeInternal = function() {
+  goog.dom.removeNode(this.viewport_);
+  goog.base(this, 'disposeInternal');
 };
 
 
 /**
- * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
- *     set (target).
- * @param {Array.<ol.Attribution>} attributions Attributions (source).
- * @protected
+ * 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`. Feature overlays will always be
+ * included in the detection.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
+ *     callback. If the detected feature is not on a layer, but on a
+ *     {@link ol.FeatureOverlay}, then the 2nd argument to this function will
+ *     be `null`. To stop detection, callback functions can return a truthy
+ *     value.
+ * @param {S=} opt_this Value to use as `this` when executing `callback`.
+ * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
+ *     filter function, only layers which are visible and for which this
+ *     function returns `true` will be tested for features. By default, all
+ *     visible layers will be tested. Feature overlays will always be tested.
+ * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
+ * @return {T|undefined} Callback result, i.e. the return value of last
+ * callback execution, or the first truthy callback return value.
+ * @template S,T,U
+ * @api stable
  */
-ol.renderer.Layer.prototype.updateAttributions =
-    function(attributionsSet, attributions) {
-  if (goog.isDefAndNotNull(attributions)) {
-    var attribution, i, ii;
-    for (i = 0, ii = attributions.length; i < ii; ++i) {
-      attribution = attributions[i];
-      attributionsSet[goog.getUid(attribution).toString()] = attribution;
-    }
+ol.Map.prototype.forEachFeatureAtPixel =
+    function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
+  if (goog.isNull(this.frameState_)) {
+    return;
   }
+  var coordinate = this.getCoordinateFromPixel(pixel);
+  var thisArg = goog.isDef(opt_this) ? opt_this : null;
+  var layerFilter = goog.isDef(opt_layerFilter) ?
+      opt_layerFilter : goog.functions.TRUE;
+  var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null;
+  return this.renderer_.forEachFeatureAtPixel(
+      coordinate, this.frameState_, callback, thisArg,
+      layerFilter, thisArg2);
 };
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Source} source Source.
- * @protected
+ * Returns the geographical coordinate for a browser event.
+ * @param {Event} event Event.
+ * @return {ol.Coordinate} Coordinate.
+ * @api stable
  */
-ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
-  var logo = source.getLogo();
-  if (goog.isDef(logo)) {
-    if (goog.isString(logo)) {
-      frameState.logos[logo] = '';
-    } else if (goog.isObject(logo)) {
-      goog.asserts.assertString(logo.href);
-      goog.asserts.assertString(logo.src);
-      frameState.logos[logo.src] = logo.href;
-    }
-  }
+ol.Map.prototype.getEventCoordinate = function(event) {
+  return this.getCoordinateFromPixel(this.getEventPixel(event));
 };
 
 
 /**
- * @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
+ * Returns the map pixel position for a browser event relative to the viewport.
+ * @param {Event} event Event.
+ * @return {ol.Pixel} Pixel.
+ * @api stable
  */
-ol.renderer.Layer.prototype.updateUsedTiles =
-    function(usedTiles, tileSource, z, tileRange) {
-  // FIXME should we use tilesToDrawByZ instead?
-  var tileSourceKey = goog.getUid(tileSource).toString();
-  var zKey = z.toString();
-  if (tileSourceKey in usedTiles) {
-    if (zKey in usedTiles[tileSourceKey]) {
-      usedTiles[tileSourceKey][zKey].extend(tileRange);
-    } else {
-      usedTiles[tileSourceKey][zKey] = tileRange;
-    }
+ol.Map.prototype.getEventPixel = function(event) {
+  // Use the offsetX and offsetY values if available.
+  // See http://www.w3.org/TR/cssom-view/#dom-mouseevent-offsetx and
+  // http://www.w3.org/TR/cssom-view/#dom-mouseevent-offsety
+  if (goog.isDef(event.offsetX) && goog.isDef(event.offsetY)) {
+    return [event.offsetX, event.offsetY];
+  } else if (goog.isDef(event.changedTouches)) {
+    // offsetX and offsetY are not defined for Touch Event
+    //
+    // goog.style.getRelativePosition is based on event.targetTouches,
+    // but touchend and touchcancel events have no targetTouches when
+    // the last finger is removed from the screen.
+    // So we ourselves compute the position of touch events.
+    // See https://github.com/google/closure-library/pull/323
+    var touch = event.changedTouches[0];
+    var viewportPosition = goog.style.getClientPosition(this.viewport_);
+    return [
+      touch.clientX - viewportPosition.x,
+      touch.clientY - viewportPosition.y
+    ];
   } else {
-    usedTiles[tileSourceKey] = {};
-    usedTiles[tileSourceKey][zKey] = tileRange;
+    // Compute offsetX and offsetY values for browsers that don't implement
+    // cssom-view specification
+    var eventPosition = goog.style.getRelativePosition(event, this.viewport_);
+    return [eventPosition.x, eventPosition.y];
   }
 };
 
 
 /**
- * @param {function(ol.Tile): boolean} isLoadedFunction Function to
- *     determine if the tile is loaded.
- * @param {ol.source.Tile} tileSource Tile source.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @protected
- * @return {function(number, number, number): ol.Tile} Returns a tile if it is
- *     loaded.
+ * 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.renderer.Layer.prototype.createGetTileIfLoadedFunction =
-    function(isLoadedFunction, tileSource, pixelRatio, projection) {
-  return (
-      /**
-       * @param {number} z Z.
-       * @param {number} x X.
-       * @param {number} y Y.
-       * @return {ol.Tile} Tile.
-       */
-      function(z, x, y) {
-        var tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-        return isLoadedFunction(tile) ? tile : null;
-      });
+ol.Map.prototype.getTarget = function() {
+  return /** @type {Element|string|undefined} */ (
+      this.get(ol.MapProperty.TARGET));
 };
+goog.exportProperty(
+    ol.Map.prototype,
+    'getTarget',
+    ol.Map.prototype.getTarget);
 
 
 /**
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {ol.Size} size Size.
- * @protected
- * @return {ol.Coordinate} Snapped center.
+ * 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.renderer.Layer.prototype.snapCenterToPixel =
-    function(center, resolution, size) {
-  return [
-    resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2),
-    resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2)
-  ];
+ol.Map.prototype.getTargetElement = function() {
+  var target = this.getTarget();
+  return goog.isDef(target) ? goog.dom.getElement(target) : null;
 };
 
 
 /**
- * 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|undefined} 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
+ * @param {ol.Pixel} pixel Pixel.
+ * @return {ol.Coordinate} Coordinate.
+ * @api stable
  */
-ol.renderer.Layer.prototype.manageTilePyramid = function(
-    frameState, tileSource, tileGrid, pixelRatio, projection, extent,
-    currentZ, preload, opt_tileCallback, opt_this) {
-  var tileSourceKey = goog.getUid(tileSource).toString();
-  if (!(tileSourceKey in frameState.wantedTiles)) {
-    frameState.wantedTiles[tileSourceKey] = {};
-  }
-  var wantedTiles = frameState.wantedTiles[tileSourceKey];
-  var tileQueue = frameState.tileQueue;
-  var minZoom = tileGrid.getMinZoom();
-  var tile, tileRange, tileResolution, x, y, z;
-  if (!goog.isDef(preload)) {
-    preload = 0;
-  }
-  for (z = currentZ; z >= minZoom; --z) {
-    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
-    tileResolution = tileGrid.getResolution(z);
-    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-        if (currentZ - z <= preload) {
-          tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-          if (tile.getState() == ol.TileState.IDLE) {
-            wantedTiles[ol.tilecoord.toString(tile.tileCoord)] = true;
-            if (!tileQueue.isKeyQueued(tile.getKey())) {
-              tileQueue.enqueue([tile, tileSourceKey,
-                tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
-            }
-          }
-          if (goog.isDef(opt_tileCallback)) {
-            opt_tileCallback.call(opt_this, tile);
-          }
-        } else {
-          tileSource.useTile(z, x, y);
-        }
-      }
-    }
+ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
+  var frameState = this.frameState_;
+  if (goog.isNull(frameState)) {
+    return null;
+  } else {
+    var vec2 = pixel.slice();
+    return ol.vec.Mat4.multVec2(frameState.pixelToCoordinateMatrix, vec2, vec2);
   }
 };
 
-goog.provide('ol.vec.Mat4');
-
-goog.require('goog.vec.Mat4');
-
 
 /**
- * @param {!goog.vec.Mat4.Number} mat Matrix.
- * @param {number} translateX1 Translate X1.
- * @param {number} translateY1 Translate Y1.
- * @param {number} scaleX Scale X.
- * @param {number} scaleY Scale Y.
- * @param {number} rotation Rotation.
- * @param {number} translateX2 Translate X2.
- * @param {number} translateY2 Translate Y2.
- * @return {!goog.vec.Mat4.Number} Matrix.
+ * @return {ol.Collection.<ol.control.Control>} Controls.
+ * @api stable
  */
-ol.vec.Mat4.makeTransform2D = function(mat, translateX1, translateY1,
-    scaleX, scaleY, rotation, translateX2, translateY2) {
-  goog.vec.Mat4.makeIdentity(mat);
-  if (translateX1 !== 0 || translateY1 !== 0) {
-    goog.vec.Mat4.translate(mat, translateX1, translateY1, 0);
-  }
-  if (scaleX != 1 || scaleY != 1) {
-    goog.vec.Mat4.scale(mat, scaleX, scaleY, 1);
-  }
-  if (rotation !== 0) {
-    goog.vec.Mat4.rotateZ(mat, rotation);
-  }
-  if (translateX2 !== 0 || translateY2 !== 0) {
-    goog.vec.Mat4.translate(mat, translateX2, translateY2, 0);
-  }
-  return mat;
+ol.Map.prototype.getControls = function() {
+  return this.controls_;
 };
 
 
 /**
- * Returns true if mat1 and mat2 represent the same 2D transformation.
- * @param {goog.vec.Mat4.Number} mat1 Matrix 1.
- * @param {goog.vec.Mat4.Number} mat2 Matrix 2.
- * @return {boolean} Equal 2D.
+ * @return {ol.Collection.<ol.Overlay>} Overlays.
+ * @api stable
  */
-ol.vec.Mat4.equals2D = function(mat1, mat2) {
-  return (
-      goog.vec.Mat4.getElement(mat1, 0, 0) ==
-      goog.vec.Mat4.getElement(mat2, 0, 0) &&
-      goog.vec.Mat4.getElement(mat1, 1, 0) ==
-      goog.vec.Mat4.getElement(mat2, 1, 0) &&
-      goog.vec.Mat4.getElement(mat1, 0, 1) ==
-      goog.vec.Mat4.getElement(mat2, 0, 1) &&
-      goog.vec.Mat4.getElement(mat1, 1, 1) ==
-      goog.vec.Mat4.getElement(mat2, 1, 1) &&
-      goog.vec.Mat4.getElement(mat1, 0, 3) ==
-      goog.vec.Mat4.getElement(mat2, 0, 3) &&
-      goog.vec.Mat4.getElement(mat1, 1, 3) ==
-      goog.vec.Mat4.getElement(mat2, 1, 3));
+ol.Map.prototype.getOverlays = function() {
+  return this.overlays_;
 };
 
 
 /**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed vector into resultVec. The input vector is multiplied against the
- * upper 2x4 matrix omitting the projective component.
+ * Gets the collection of {@link ol.interaction.Interaction} instances
+ * associated with this map. Modifying this collection changes the interactions
+ * associated with the map.
  *
- * @param {goog.vec.Mat4.Number} mat The matrix supplying the transformation.
- * @param {Array.<number>} vec The 3 element vector to transform.
- * @param {Array.<number>} resultVec The 3 element vector to receive the results
- *     (may be vec).
- * @return {Array.<number>} return resultVec so that operations can be
- *     chained together.
+ * Interactions are used for e.g. pan, zoom and rotate.
+ * @return {ol.Collection.<ol.interaction.Interaction>} Interactions.
+ * @api stable
  */
-ol.vec.Mat4.multVec2 = function(mat, vec, resultVec) {
-  var m00 = goog.vec.Mat4.getElement(mat, 0, 0);
-  var m10 = goog.vec.Mat4.getElement(mat, 1, 0);
-  var m01 = goog.vec.Mat4.getElement(mat, 0, 1);
-  var m11 = goog.vec.Mat4.getElement(mat, 1, 1);
-  var m03 = goog.vec.Mat4.getElement(mat, 0, 3);
-  var m13 = goog.vec.Mat4.getElement(mat, 1, 3);
-  var x = vec[0], y = vec[1];
-  resultVec[0] = m00 * x + m01 * y + m03;
-  resultVec[1] = m10 * x + m11 * y + m13;
-  return resultVec;
+ol.Map.prototype.getInteractions = function() {
+  return this.interactions_;
 };
 
-goog.provide('ol.RendererType');
-goog.provide('ol.renderer.Map');
 
-goog.require('goog.Disposable');
-goog.require('goog.asserts');
-goog.require('goog.dispose');
-goog.require('goog.object');
-goog.require('goog.vec.Mat4');
-goog.require('ol.layer.Layer');
-goog.require('ol.renderer.Layer');
-goog.require('ol.style.IconImageCache');
-goog.require('ol.vec.Mat4');
+/**
+ * Get the layergroup associated with this map.
+ * @return {ol.layer.Group} A layer group containing the layers in this map.
+ * @observable
+ * @api stable
+ */
+ol.Map.prototype.getLayerGroup = function() {
+  return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
+};
+goog.exportProperty(
+    ol.Map.prototype,
+    'getLayerGroup',
+    ol.Map.prototype.getLayerGroup);
 
 
 /**
- * Available renderers: `'canvas'`, `'dom'` or `'webgl'`.
- * @enum {string}
+ * Get the collection of layers associated with this map.
+ * @return {!ol.Collection.<ol.layer.Base>} Layers.
  * @api stable
  */
-ol.RendererType = {
-  CANVAS: 'canvas',
-  DOM: 'dom',
-  WEBGL: 'webgl'
+ol.Map.prototype.getLayers = function() {
+  var layers = this.getLayerGroup().getLayers();
+  return layers;
 };
 
 
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {ol.Pixel} Pixel.
+ * @api stable
+ */
+ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
+  var frameState = this.frameState_;
+  if (goog.isNull(frameState)) {
+    return null;
+  } else {
+    var vec2 = coordinate.slice(0, 2);
+    return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2);
+  }
+};
+
 
 /**
- * @constructor
- * @extends {goog.Disposable}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
- * @suppress {checkStructDictInheritance}
- * @struct
+ * Get the map renderer.
+ * @return {ol.renderer.Map} Renderer
  */
-ol.renderer.Map = function(container, map) {
+ol.Map.prototype.getRenderer = function() {
+  return this.renderer_;
+};
 
-  goog.base(this);
 
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = map;
+/**
+ * Get the size of this map.
+ * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
+ * @observable
+ * @api stable
+ */
+ol.Map.prototype.getSize = function() {
+  return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
+};
+goog.exportProperty(
+    ol.Map.prototype,
+    'getSize',
+    ol.Map.prototype.getSize);
 
-  /**
-   * @protected
-   * @type {ol.render.IReplayGroup}
-   */
-  this.replayGroup = null;
 
-  /**
-   * @private
-   * @type {Object.<string, ol.renderer.Layer>}
-   */
-  this.layerRenderers_ = {};
+/**
+ * Get the view associated with this map. A view manages properties such as
+ * center and resolution.
+ * @return {ol.View} The view that controls this map.
+ * @observable
+ * @api stable
+ */
+ol.Map.prototype.getView = function() {
+  return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
+};
+goog.exportProperty(
+    ol.Map.prototype,
+    'getView',
+    ol.Map.prototype.getView);
 
+
+/**
+ * @return {Element} Viewport.
+ * @api stable
+ */
+ol.Map.prototype.getViewport = function() {
+  return this.viewport_;
 };
-goog.inherits(ol.renderer.Map, goog.Disposable);
 
 
 /**
- * @param {olx.FrameState} frameState FrameState.
- * @protected
+ * @return {Element} The map's overlay container. 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.
  */
-ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) {
-  var viewState = frameState.viewState;
-  var coordinateToPixelMatrix = frameState.coordinateToPixelMatrix;
-  goog.asserts.assert(!goog.isNull(coordinateToPixelMatrix));
-  ol.vec.Mat4.makeTransform2D(coordinateToPixelMatrix,
-      frameState.size[0] / 2, frameState.size[1] / 2,
-      1 / viewState.resolution, -1 / viewState.resolution,
-      -viewState.rotation,
-      -viewState.center[0], -viewState.center[1]);
-  var inverted = goog.vec.Mat4.invert(
-      coordinateToPixelMatrix, frameState.pixelToCoordinateMatrix);
-  goog.asserts.assert(inverted);
+ol.Map.prototype.getOverlayContainer = function() {
+  return this.overlayContainer_;
 };
 
 
 /**
- * @param {ol.layer.Layer} layer Layer.
- * @protected
- * @return {ol.renderer.Layer} layerRenderer Layer renderer.
+ * @return {Element} The map's overlay container. 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}.
  */
-ol.renderer.Map.prototype.createLayerRenderer = function(layer) {
-  return new ol.renderer.Layer(this, layer);
+ol.Map.prototype.getOverlayContainerStopEvent = function() {
+  return this.overlayContainerStopEvent_;
 };
 
 
 /**
- * @inheritDoc
+ * @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.renderer.Map.prototype.disposeInternal = function() {
-  goog.object.forEach(this.layerRenderers_, goog.dispose);
-  goog.base(this, 'disposeInternal');
+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 (goog.isNull(frameState) || !(tileSourceKey in frameState.wantedTiles)) {
+    return ol.structs.PriorityQueue.DROP;
+  }
+  var coordKey = ol.tilecoord.toString(tile.tileCoord);
+  if (!frameState.wantedTiles[tileSourceKey][coordKey]) {
+    return ol.structs.PriorityQueue.DROP;
+  }
+  // Prioritize the highest zoom level tiles closest to the focus.
+  // Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
+  // Within a zoom level, tiles are prioritized by the distance in pixels
+  // between the center of the tile and the focus.  The factor of 65536 means
+  // that the prioritization should behave as desired for tiles up to
+  // 65536 * Math.log(2) = 45426 pixels from the focus.
+  var deltaX = tileCenter[0] - frameState.focus[0];
+  var deltaY = tileCenter[1] - frameState.focus[1];
+  return 65536 * Math.log(tileResolution) +
+      Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
 };
 
 
 /**
- * @param {ol.Map} map Map.
- * @param {olx.FrameState} frameState Frame state.
- * @private
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @param {string=} opt_type Type.
  */
-ol.renderer.Map.expireIconCache_ = function(map, frameState) {
-  ol.style.IconImageCache.getInstance().expire();
+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.Coordinate} coordinate Coordinate.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
- *     function, only layers which are visible and for which this function
- *     returns `true` will be tested for features.  By default, all visible
- *     layers will be tested.
- * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
+ * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
  */
-ol.renderer.Map.prototype.forEachFeatureAtPixel =
-    function(coordinate, frameState, callback, thisArg,
-        layerFilter, thisArg2) {
-  var result;
-  var extent = frameState.extent;
-  var viewState = frameState.viewState;
-  var viewResolution = viewState.resolution;
-  var viewRotation = viewState.rotation;
-  if (!goog.isNull(this.replayGroup)) {
-    /** @type {Object.<string, boolean>} */
-    var features = {};
-    result = this.replayGroup.forEachGeometryAtPixel(extent, viewResolution,
-        viewRotation, coordinate, {},
-        /**
-         * @param {ol.geom.Geometry} geometry Geometry.
-         * @param {Object} data Data.
-         * @return {?} Callback result.
-         */
-        function(geometry, data) {
-          var feature = /** @type {ol.Feature} */ (data);
-          goog.asserts.assert(goog.isDef(feature));
-          var key = goog.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback.call(thisArg, feature, null);
-          }
-        });
-    if (result) {
-      return result;
-    }
+ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) {
+  if (goog.isNull(this.frameState_)) {
+    // With no view defined, we cannot translate pixels into geographical
+    // coordinates so interactions cannot be used.
+    return;
   }
-  var layerStates = this.map_.getLayerGroup().getLayerStatesArray();
-  var numLayers = layerStates.length;
+  this.focus_ = mapBrowserEvent.coordinate;
+  mapBrowserEvent.frameState = this.frameState_;
+  var interactions = this.getInteractions();
+  goog.asserts.assert(goog.isDef(interactions));
+  var interactionsArray = interactions.getArray();
   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.forEachFeatureAtPixel(
-          coordinate, frameState, callback, thisArg);
-      if (result) {
-        return result;
+  if (this.dispatchEvent(mapBrowserEvent) !== false) {
+    for (i = interactionsArray.length - 1; i >= 0; i--) {
+      var interaction = interactionsArray[i];
+      if (!interaction.getActive()) {
+        continue;
+      }
+      var cont = interaction.handleEvent(mapBrowserEvent);
+      if (!cont) {
+        break;
       }
     }
   }
-  return undefined;
 };
 
 
 /**
- * @param {ol.layer.Layer} layer Layer.
  * @protected
- * @return {ol.renderer.Layer} Layer renderer.
  */
-ol.renderer.Map.prototype.getLayerRenderer = function(layer) {
-  var layerKey = goog.getUid(layer).toString();
-  if (layerKey in this.layerRenderers_) {
-    return this.layerRenderers_[layerKey];
-  } else {
-    var layerRenderer = this.createLayerRenderer(layer);
-    this.layerRenderers_[layerKey] = layerRenderer;
-    return layerRenderer;
+ol.Map.prototype.handlePostRender = function() {
+
+  var frameState = this.frameState_;
+
+  // Manage the tile queue
+  // Image loads are expensive and a limited resource, so try to use them
+  // efficiently:
+  // * When the view is static we allow a large number of parallel tile loads
+  //   to complete the frame as quickly as possible.
+  // * When animating or interacting, image loads can cause janks, so we reduce
+  //   the maximum number of loads per frame and limit the number of parallel
+  //   tile loads to remain reactive to view changes and to reduce the chance of
+  //   loading tiles that will quickly disappear from view.
+  var tileQueue = this.tileQueue_;
+  if (!tileQueue.isEmpty()) {
+    var maxTotalLoading = 16;
+    var maxNewLoads = maxTotalLoading;
+    var tileSourceCount = 0;
+    if (!goog.isNull(frameState)) {
+      var hints = frameState.viewHints;
+      var deviceOptions = this.deviceOptions_;
+      if (hints[ol.ViewHint.ANIMATING]) {
+        maxTotalLoading = deviceOptions.loadTilesWhileAnimating === false ?
+            0 : 8;
+        maxNewLoads = 2;
+      }
+      if (hints[ol.ViewHint.INTERACTING]) {
+        maxTotalLoading = deviceOptions.loadTilesWhileInteracting === false ?
+            0 : 8;
+        maxNewLoads = 2;
+      }
+      tileSourceCount = goog.object.getCount(frameState.wantedTiles);
+    }
+    maxTotalLoading *= tileSourceCount;
+    maxNewLoads *= tileSourceCount;
+    if (tileQueue.getTilesLoading() < maxTotalLoading) {
+      tileQueue.reprioritize(); // FIXME only call if view has changed
+      tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
+    }
+  }
+
+  var postRenderFunctions = this.postRenderFunctions_;
+  var i, ii;
+  for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
+    postRenderFunctions[i](this, frameState);
   }
+  postRenderFunctions.length = 0;
 };
 
 
 /**
- * @param {string} layerKey Layer key.
- * @protected
- * @return {ol.renderer.Layer} Layer renderer.
+ * @private
  */
-ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) {
-  goog.asserts.assert(layerKey in this.layerRenderers_);
-  return this.layerRenderers_[layerKey];
+ol.Map.prototype.handleSizeChanged_ = function() {
+  this.render();
 };
 
 
 /**
- * @protected
- * @return {Object.<number, ol.renderer.Layer>} Layer renderers.
+ * @private
  */
-ol.renderer.Map.prototype.getLayerRenderers = function() {
-  return this.layerRenderers_;
+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 target = this.getTarget();
+
+  /**
+   * @type {Element}
+   */
+  var targetElement = goog.isDef(target) ?
+      goog.dom.getElement(target) : null;
+
+  this.keyHandler_.detach();
+
+  if (goog.isNull(targetElement)) {
+    goog.dom.removeNode(this.viewport_);
+  } else {
+    goog.dom.appendChild(targetElement, this.viewport_);
+
+    var keyboardEventTarget = goog.isNull(this.keyboardEventTarget_) ?
+        targetElement : this.keyboardEventTarget_;
+    this.keyHandler_.attach(keyboardEventTarget);
+  }
+
+  this.updateSize();
+  // updateSize calls setSize, so no need to call this.render
+  // ourselves here.
 };
 
 
 /**
- * @return {ol.Map} Map.
+ * @private
  */
-ol.renderer.Map.prototype.getMap = function() {
-  return this.map_;
+ol.Map.prototype.handleTileChange_ = function() {
+  this.render();
 };
 
 
 /**
- * @return {string} Type
+ * @private
  */
-ol.renderer.Map.prototype.getType = goog.abstractMethod;
+ol.Map.prototype.handleViewPropertyChanged_ = function() {
+  this.render();
+};
 
 
 /**
- * @param {string} layerKey Layer key.
- * @return {ol.renderer.Layer} Layer renderer.
  * @private
  */
-ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) {
-  goog.asserts.assert(layerKey in this.layerRenderers_);
-  var layerRenderer = this.layerRenderers_[layerKey];
-  delete this.layerRenderers_[layerKey];
-  return layerRenderer;
+ol.Map.prototype.handleViewChanged_ = function() {
+  if (!goog.isNull(this.viewPropertyListenerKey_)) {
+    goog.events.unlistenByKey(this.viewPropertyListenerKey_);
+    this.viewPropertyListenerKey_ = null;
+  }
+  var view = this.getView();
+  if (!goog.isNull(view)) {
+    this.viewPropertyListenerKey_ = goog.events.listen(
+        view, ol.ObjectEventType.PROPERTYCHANGE,
+        this.handleViewPropertyChanged_, false, this);
+  }
+  this.render();
 };
 
 
 /**
- * Render.
- * @param {?olx.FrameState} frameState Frame state.
+ * @param {goog.events.Event} event Event.
+ * @private
  */
-ol.renderer.Map.prototype.renderFrame = goog.nullFunction;
+ol.Map.prototype.handleLayerGroupMemberChanged_ = function(event) {
+  goog.asserts.assertInstanceof(event, goog.events.Event);
+  this.render();
+};
 
 
 /**
- * @param {ol.Map} map Map.
- * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.ObjectEvent} event Event.
  * @private
  */
-ol.renderer.Map.prototype.removeUnusedLayerRenderers_ =
-    function(map, frameState) {
-  var layerKey;
-  for (layerKey in this.layerRenderers_) {
-    if (goog.isNull(frameState) || !(layerKey in frameState.layerStates)) {
-      goog.dispose(this.removeLayerRendererByKey_(layerKey));
-    }
-  }
+ol.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) {
+  goog.asserts.assertInstanceof(event, ol.ObjectEvent);
+  this.render();
 };
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @protected
+ * @private
  */
-ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
-  frameState.postRenderFunctions.push(ol.renderer.Map.expireIconCache_);
+ol.Map.prototype.handleLayerGroupChanged_ = function() {
+  if (!goog.isNull(this.layerGroupPropertyListenerKeys_)) {
+    var length = this.layerGroupPropertyListenerKeys_.length;
+    for (var i = 0; i < length; ++i) {
+      goog.events.unlistenByKey(this.layerGroupPropertyListenerKeys_[i]);
+    }
+    this.layerGroupPropertyListenerKeys_ = null;
+  }
+  var layerGroup = this.getLayerGroup();
+  if (goog.isDefAndNotNull(layerGroup)) {
+    this.layerGroupPropertyListenerKeys_ = [
+      goog.events.listen(
+          layerGroup, ol.ObjectEventType.PROPERTYCHANGE,
+          this.handleLayerGroupPropertyChanged_, false, this),
+      goog.events.listen(
+          layerGroup, goog.events.EventType.CHANGE,
+          this.handleLayerGroupMemberChanged_, false, this)
+    ];
+  }
+  this.render();
 };
 
 
 /**
- * @param {!olx.FrameState} frameState Frame state.
- * @protected
+ * Returns `true` if the map is defined, `false` otherwise. The map is defined
+ * if it is contained in `document`, visible, has non-zero height and width, and
+ * has a defined view.
+ * @return {boolean} Is defined.
  */
-ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers =
-    function(frameState) {
-  var layerKey;
-  for (layerKey in this.layerRenderers_) {
-    if (!(layerKey in frameState.layerStates)) {
-      frameState.postRenderFunctions.push(
-          goog.bind(this.removeUnusedLayerRenderers_, this));
-      return;
-    }
+ol.Map.prototype.isDef = function() {
+  if (!goog.dom.contains(document, this.viewport_)) {
+    return false;
+  }
+  if (!goog.style.isElementShown(this.viewport_)) {
+    return false;
+  }
+  var size = this.getSize();
+  if (!goog.isDefAndNotNull(size) || size[0] <= 0 || size[1] <= 0) {
+    return false;
+  }
+  var view = this.getView();
+  if (goog.isNull(view) || !view.isDef()) {
+    return false;
   }
+  return true;
 };
 
-goog.provide('ol.structs.PriorityQueue');
-
-goog.require('goog.asserts');
-goog.require('goog.object');
 
+/**
+ * @return {boolean} Is rendered.
+ */
+ol.Map.prototype.isRendered = function() {
+  return !goog.isNull(this.frameState_);
+};
 
 
 /**
- * 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
+ * Requests an immediate render in a synchronous manner.
+ * @api stable
  */
-ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
+ol.Map.prototype.renderSync = function() {
+  this.animationDelay_.fire();
+};
 
-  /**
-   * @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_ = {};
 
+/**
+ * Requests a render frame; rendering will effectively occur at the next browser
+ * animation frame.
+ * @api stable
+ */
+ol.Map.prototype.render = function() {
+  if (!this.animationDelay_.isActive()) {
+    this.animationDelay_.start();
+  }
 };
 
 
 /**
- * @const
- * @type {number}
+ * 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.structs.PriorityQueue.DROP = Infinity;
+ol.Map.prototype.removeControl = function(control) {
+  var controls = this.getControls();
+  goog.asserts.assert(goog.isDef(controls));
+  if (goog.isDef(controls.remove(control))) {
+    return control;
+  }
+  return undefined;
+};
 
 
 /**
- * FIXME empty desciption for jsdoc
+ * 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.structs.PriorityQueue.prototype.assertValid = function() {
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var n = elements.length;
-  goog.asserts.assert(priorities.length == n);
-  var i, priority;
-  for (i = 0; i < (n >> 1) - 1; ++i) {
-    priority = priorities[i];
-    goog.asserts.assert(priority <= priorities[this.getLeftChildIndex_(i)]);
-    goog.asserts.assert(priority <= priorities[this.getRightChildIndex_(i)]);
+ol.Map.prototype.removeInteraction = function(interaction) {
+  var removed;
+  var interactions = this.getInteractions();
+  goog.asserts.assert(goog.isDef(interactions));
+  if (goog.isDef(interactions.remove(interaction))) {
+    removed = interaction;
   }
+  return removed;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * 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.structs.PriorityQueue.prototype.clear = function() {
-  this.elements_.length = 0;
-  this.priorities_.length = 0;
-  goog.object.clear(this.queuedElements_);
+ol.Map.prototype.removeLayer = function(layer) {
+  var layers = this.getLayerGroup().getLayers();
+  return layers.remove(layer);
 };
 
 
 /**
- * Remove and return the highest-priority element. O(log N).
- * @return {T} Element.
+ * 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.structs.PriorityQueue.prototype.dequeue = function() {
-  var elements = this.elements_;
-  goog.asserts.assert(elements.length > 0);
-  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);
+ol.Map.prototype.removeOverlay = function(overlay) {
+  var overlays = this.getOverlays();
+  goog.asserts.assert(goog.isDef(overlays));
+  if (goog.isDef(overlays.remove(overlay))) {
+    return overlay;
   }
-  var elementKey = this.keyFunction_(element);
-  goog.asserts.assert(elementKey in this.queuedElements_);
-  delete this.queuedElements_[elementKey];
-  return element;
+  return undefined;
 };
 
 
 /**
- * Enqueue an element. O(log N).
- * @param {T} element Element.
+ * @param {number} time Time.
+ * @private
  */
-ol.structs.PriorityQueue.prototype.enqueue = function(element) {
-  goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_));
-  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);
+ol.Map.prototype.renderFrame_ = function(time) {
+
+  var i, ii, viewState;
+
+  /**
+   * Check whether a size has non-zero width and height.  Note that this
+   * function is here because the compiler doesn't recognize that size is
+   * defined in the frameState assignment below when the same code is inline in
+   * the condition below.  The compiler inlines this function itself, so the
+   * resulting code is the same.
+   *
+   * @param {ol.Size} size The size to test.
+   * @return {boolean} Has non-zero width and height.
+   */
+  function hasArea(size) {
+    return size[0] > 0 && size[1] > 0;
   }
-};
 
+  var size = this.getSize();
+  var view = this.getView();
+  /** @type {?olx.FrameState} */
+  var frameState = null;
+  if (goog.isDef(size) && hasArea(size) &&
+      !goog.isNull(view) && view.isDef()) {
+    var viewHints = view.getHints();
+    var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
+    var layerStates = {};
+    for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
+      layerStates[goog.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
+    }
+    viewState = view.getState();
+    frameState = /** @type {olx.FrameState} */ ({
+      animate: false,
+      attributions: {},
+      coordinateToPixelMatrix: this.coordinateToPixelMatrix_,
+      extent: null,
+      focus: goog.isNull(this.focus_) ? viewState.center : this.focus_,
+      index: this.frameIndex_++,
+      layerStates: layerStates,
+      layerStatesArray: layerStatesArray,
+      logos: goog.object.clone(this.logos_),
+      pixelRatio: this.pixelRatio_,
+      pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_,
+      postRenderFunctions: [],
+      size: size,
+      skippedFeatureUids: this.skippedFeatureUids_,
+      tileQueue: this.tileQueue_,
+      time: time,
+      usedTiles: {},
+      viewState: viewState,
+      viewHints: viewHints,
+      wantedTiles: {}
+    });
+  }
 
-/**
- * @return {number} Count.
- */
-ol.structs.PriorityQueue.prototype.getCount = function() {
-  return this.elements_.length;
-};
+  if (!goog.isNull(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);
+  }
 
-/**
- * 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;
-};
+  this.frameState_ = frameState;
+  this.renderer_.renderFrame(frameState);
 
+  if (!goog.isNull(frameState)) {
+    if (frameState.animate) {
+      this.render();
+    }
+    Array.prototype.push.apply(
+        this.postRenderFunctions_, frameState.postRenderFunctions);
 
-/**
- * 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;
-};
+    var idle = this.preRenderFunctions_.length === 0 &&
+        !frameState.viewHints[ol.ViewHint.ANIMATING] &&
+        !frameState.viewHints[ol.ViewHint.INTERACTING] &&
+        !ol.extent.equals(frameState.extent, this.previousExtent_);
+
+    if (idle) {
+      this.dispatchEvent(
+          new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
+      ol.extent.clone(frameState.extent, this.previousExtent_);
+    }
+  }
 
+  this.dispatchEvent(
+      new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));
+
+  goog.async.nextTick(this.handlePostRender, this);
 
-/**
- * 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
+ * Sets the layergroup of this map.
+ * @param {ol.layer.Group} layerGroup A layer group containing the layers in
+ *     this map.
+ * @observable
+ * @api stable
  */
-ol.structs.PriorityQueue.prototype.heapify_ = function() {
-  var i;
-  for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
-    this.siftUp_(i);
-  }
+ol.Map.prototype.setLayerGroup = function(layerGroup) {
+  this.set(ol.MapProperty.LAYERGROUP, layerGroup);
 };
+goog.exportProperty(
+    ol.Map.prototype,
+    'setLayerGroup',
+    ol.Map.prototype.setLayerGroup);
 
 
 /**
- * @return {boolean} Is empty.
+ * Set the size of this map.
+ * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
+ * @observable
+ * @api
  */
-ol.structs.PriorityQueue.prototype.isEmpty = function() {
-  return this.elements_.length === 0;
+ol.Map.prototype.setSize = function(size) {
+  this.set(ol.MapProperty.SIZE, size);
 };
+goog.exportProperty(
+    ol.Map.prototype,
+    'setSize',
+    ol.Map.prototype.setSize);
 
 
 /**
- * @param {string} key Key.
- * @return {boolean} Is key queued.
+ * 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.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
-  return key in this.queuedElements_;
+ol.Map.prototype.setTarget = function(target) {
+  this.set(ol.MapProperty.TARGET, target);
 };
+goog.exportProperty(
+    ol.Map.prototype,
+    'setTarget',
+    ol.Map.prototype.setTarget);
 
 
 /**
- * @param {T} element Element.
- * @return {boolean} Is queued.
+ * Set the view for this map.
+ * @param {ol.View} view The view that controls this map.
+ * @observable
+ * @api stable
  */
-ol.structs.PriorityQueue.prototype.isQueued = function(element) {
-  return this.isKeyQueued(this.keyFunction_(element));
+ol.Map.prototype.setView = function(view) {
+  this.set(ol.MapProperty.VIEW, view);
 };
+goog.exportProperty(
+    ol.Map.prototype,
+    'setView',
+    ol.Map.prototype.setView);
 
 
 /**
- * @param {number} index The index of the node to move down.
- * @private
+ * @param {ol.Feature} feature Feature.
  */
-ol.structs.PriorityQueue.prototype.siftUp_ = function(index) {
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var count = elements.length;
-  var element = elements[index];
-  var priority = priorities[index];
-  var startIndex = index;
-
-  while (index < (count >> 1)) {
-    var lIndex = this.getLeftChildIndex_(index);
-    var rIndex = this.getRightChildIndex_(index);
-
-    var smallerChildIndex = rIndex < count &&
-        priorities[rIndex] < priorities[lIndex] ?
-        rIndex : lIndex;
-
-    elements[index] = elements[smallerChildIndex];
-    priorities[index] = priorities[smallerChildIndex];
-    index = smallerChildIndex;
-  }
-
-  elements[index] = element;
-  priorities[index] = priority;
-  this.siftDown_(startIndex, index);
+ol.Map.prototype.skipFeature = function(feature) {
+  var featureUid = goog.getUid(feature).toString();
+  this.skippedFeatureUids_[featureUid] = true;
+  this.render();
 };
 
 
 /**
- * @param {number} startIndex The index of the root.
- * @param {number} index The index of the node to move up.
- * @private
+ * Force a recalculation of the map viewport size.  This should be called when
+ * third-party code changes the size of the map viewport.
+ * @api stable
  */
-ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) {
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var element = elements[index];
-  var priority = priorities[index];
+ol.Map.prototype.updateSize = function() {
+  var target = this.getTarget();
 
-  while (index > startIndex) {
-    var parentIndex = this.getParentIndex_(index);
-    if (priorities[parentIndex] > priority) {
-      elements[index] = elements[parentIndex];
-      priorities[index] = priorities[parentIndex];
-      index = parentIndex;
-    } else {
-      break;
-    }
+  /**
+   * @type {Element}
+   */
+  var targetElement = goog.isDef(target) ? goog.dom.getElement(target) : null;
+
+  if (goog.isNull(targetElement)) {
+    this.setSize(undefined);
+  } else {
+    var size = goog.style.getContentBoxSize(targetElement);
+    this.setSize([size.width, size.height]);
   }
-  elements[index] = element;
-  priorities[index] = priority;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @param {ol.Feature} feature Feature.
  */
-ol.structs.PriorityQueue.prototype.reprioritize = function() {
-  var priorityFunction = this.priorityFunction_;
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var index = 0;
-  var n = elements.length;
-  var element, i, priority;
-  for (i = 0; i < n; ++i) {
-    element = elements[i];
-    priority = priorityFunction(element);
-    if (priority == ol.structs.PriorityQueue.DROP) {
-      delete this.queuedElements_[this.keyFunction_(element)];
-    } else {
-      priorities[index] = priority;
-      elements[index++] = element;
-    }
-  }
-  elements.length = index;
-  priorities.length = index;
-  this.heapify_();
+ol.Map.prototype.unskipFeature = function(feature) {
+  var featureUid = goog.getUid(feature).toString();
+  delete this.skippedFeatureUids_[featureUid];
+  this.render();
 };
 
-goog.provide('ol.TilePriorityFunction');
-goog.provide('ol.TileQueue');
-
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('ol.Coordinate');
-goog.require('ol.structs.PriorityQueue');
-
 
 /**
- * @typedef {function(ol.Tile, string, ol.Coordinate, number): number}
+ * @typedef {{controls: ol.Collection.<ol.control.Control>,
+ *            deviceOptions: olx.DeviceOptions,
+ *            interactions: ol.Collection.<ol.interaction.Interaction>,
+ *            keyboardEventTarget: (Element|Document),
+ *            logos: Object,
+ *            overlays: ol.Collection.<ol.Overlay>,
+ *            rendererConstructor:
+ *                function(new: ol.renderer.Map, Element, ol.Map),
+ *            values: Object.<string, *>}}
  */
-ol.TilePriorityFunction;
-
+ol.MapOptionsInternal;
 
 
 /**
- * @constructor
- * @extends {ol.structs.PriorityQueue.<Array>}
- * @param {ol.TilePriorityFunction} tilePriorityFunction
- *     Tile priority function.
- * @param {function(): ?} tileChangeCallback
- *     Function called on each tile change event.
- * @struct
+ * @param {olx.MapOptions} options Map options.
+ * @return {ol.MapOptionsInternal} Internal map options.
  */
-ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
-
-  goog.base(
-      this,
-      /**
-       * @param {Array} element Element.
-       * @return {number} Priority.
-       */
-      function(element) {
-        return tilePriorityFunction.apply(null, element);
-      },
-      /**
-       * @param {Array} element Element.
-       * @return {string} Key.
-       */
-      function(element) {
-        return /** @type {ol.Tile} */ (element[0]).getKey();
-      });
+ol.Map.createOptionsInternal = function(options) {
 
   /**
-   * @private
-   * @type {function(): ?}
+   * @type {Element|Document}
    */
-  this.tileChangeCallback_ = tileChangeCallback;
+  var keyboardEventTarget = null;
+  if (goog.isDef(options.keyboardEventTarget)) {
+    // cannot use goog.dom.getElement because its argument cannot be
+    // of type Document
+    keyboardEventTarget = goog.isString(options.keyboardEventTarget) ?
+        document.getElementById(options.keyboardEventTarget) :
+        options.keyboardEventTarget;
+  }
 
   /**
-   * @private
-   * @type {number}
+   * @type {Object.<string, *>}
    */
-  this.tilesLoading_ = 0;
+  var values = {};
 
-};
-goog.inherits(ol.TileQueue, ol.structs.PriorityQueue);
+  var logos = {};
+  if (!goog.isDef(options.logo) ||
+      (goog.isBoolean(options.logo) && options.logo)) {
+    logos[ol.OL3_LOGO_URL] = ol.OL3_URL;
+  } else {
+    var logo = options.logo;
+    if (goog.isString(logo)) {
+      logos[logo] = '';
+    } else if (goog.isObject(logo)) {
+      goog.asserts.assertString(logo.href);
+      goog.asserts.assertString(logo.src);
+      logos[logo.src] = logo.href;
+    }
+  }
 
+  var layerGroup = (options.layers instanceof ol.layer.Group) ?
+      options.layers : new ol.layer.Group({layers: options.layers});
+  values[ol.MapProperty.LAYERGROUP] = layerGroup;
 
-/**
- * @return {number} Number of tiles loading.
- */
-ol.TileQueue.prototype.getTilesLoading = function() {
-  return this.tilesLoading_;
-};
+  values[ol.MapProperty.TARGET] = options.target;
 
+  values[ol.MapProperty.VIEW] = goog.isDef(options.view) ?
+      options.view : new ol.View();
 
-/**
- * @protected
- */
-ol.TileQueue.prototype.handleTileChange = function() {
-  --this.tilesLoading_;
-  this.tileChangeCallback_();
-};
+  /**
+   * @type {function(new: ol.renderer.Map, Element, ol.Map)}
+   */
+  var rendererConstructor = ol.renderer.Map;
 
+  /**
+   * @type {Array.<ol.RendererType>}
+   */
+  var rendererTypes;
+  if (goog.isDef(options.renderer)) {
+    if (goog.isArray(options.renderer)) {
+      rendererTypes = options.renderer;
+    } else if (goog.isString(options.renderer)) {
+      rendererTypes = [options.renderer];
+    } else {
+      goog.asserts.fail('Incorrect format for renderer option');
+    }
+  } else {
+    rendererTypes = ol.DEFAULT_RENDERER_TYPES;
+  }
 
-/**
- * @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 = Math.min(
-      maxTotalLoading - this.getTilesLoading(), maxNewLoads, this.getCount());
-  var i, tile;
-  for (i = 0; i < newLoads; ++i) {
-    tile = /** @type {ol.Tile} */ (this.dequeue()[0]);
-    goog.events.listenOnce(tile, goog.events.EventType.CHANGE,
-        this.handleTileChange, false, this);
-    tile.load();
+  var i, ii;
+  for (i = 0, ii = rendererTypes.length; i < ii; ++i) {
+    /** @type {ol.RendererType} */
+    var rendererType = rendererTypes[i];
+    if (ol.ENABLE_CANVAS && rendererType == ol.RendererType.CANVAS) {
+      if (ol.has.CANVAS) {
+        rendererConstructor = ol.renderer.canvas.Map;
+        break;
+      }
+    } else if (ol.ENABLE_DOM && rendererType == ol.RendererType.DOM) {
+      if (ol.has.DOM) {
+        rendererConstructor = ol.renderer.dom.Map;
+        break;
+      }
+    } else if (ol.ENABLE_WEBGL && rendererType == ol.RendererType.WEBGL) {
+      if (ol.has.WEBGL) {
+        rendererConstructor = ol.renderer.webgl.Map;
+        break;
+      }
+    }
   }
-  this.tilesLoading_ += newLoads;
-};
-
-goog.provide('ol.interaction.KeyboardPan');
-
-goog.require('goog.asserts');
-goog.require('goog.events.KeyCodes');
-goog.require('goog.events.KeyHandler.EventType');
-goog.require('goog.functions');
-goog.require('ol');
-goog.require('ol.coordinate');
-goog.require('ol.events.ConditionType');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Interaction');
 
+  var controls;
+  if (goog.isDef(options.controls)) {
+    if (goog.isArray(options.controls)) {
+      controls = new ol.Collection(goog.array.clone(options.controls));
+    } else {
+      goog.asserts.assertInstanceof(options.controls, ol.Collection);
+      controls = options.controls;
+    }
+  } else {
+    controls = ol.control.defaults();
+  }
 
+  var deviceOptions = goog.isDef(options.deviceOptions) ?
+      options.deviceOptions : /** @type {olx.DeviceOptions} */ ({});
 
-/**
- * @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) {
+  var interactions;
+  if (goog.isDef(options.interactions)) {
+    if (goog.isArray(options.interactions)) {
+      interactions = new ol.Collection(goog.array.clone(options.interactions));
+    } else {
+      goog.asserts.assertInstanceof(options.interactions, ol.Collection);
+      interactions = options.interactions;
+    }
+  } else {
+    interactions = ol.interaction.defaults();
+  }
 
-  goog.base(this);
+  var overlays;
+  if (goog.isDef(options.overlays)) {
+    if (goog.isArray(options.overlays)) {
+      overlays = new ol.Collection(goog.array.clone(options.overlays));
+    } else {
+      goog.asserts.assertInstanceof(options.overlays, ol.Collection);
+      overlays = options.overlays;
+    }
+  } else {
+    overlays = new ol.Collection();
+  }
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  return {
+    controls: controls,
+    deviceOptions: deviceOptions,
+    interactions: interactions,
+    keyboardEventTarget: keyboardEventTarget,
+    logos: logos,
+    overlays: overlays,
+    rendererConstructor: rendererConstructor,
+    values: values
+  };
 
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.condition_ = goog.isDef(options.condition) ? options.condition :
-      goog.functions.and(ol.events.condition.noModifierKeys,
-          ol.events.condition.targetNotEditable);
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelDelta_ = goog.isDef(options.pixelDelta) ? options.pixelDelta : 128;
 
-};
-goog.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction);
+ol.proj.common.add();
 
 
-/**
- * @inheritDoc
- */
-ol.interaction.KeyboardPan.prototype.handleMapBrowserEvent =
-    function(mapBrowserEvent) {
-  var stopEvent = false;
-  if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) {
-    var keyEvent = /** @type {goog.events.KeyEvent} */
-        (mapBrowserEvent.browserEvent);
-    var keyCode = keyEvent.keyCode;
-    if (this.condition_(mapBrowserEvent) &&
-        (keyCode == goog.events.KeyCodes.DOWN ||
-        keyCode == goog.events.KeyCodes.LEFT ||
-        keyCode == goog.events.KeyCodes.RIGHT ||
-        keyCode == goog.events.KeyCodes.UP)) {
-      var map = mapBrowserEvent.map;
-      var view = map.getView();
-      goog.asserts.assert(goog.isDef(view));
-      var viewState = view.getState();
-      var mapUnitsDelta = viewState.resolution * this.pixelDelta_;
-      var deltaX = 0, deltaY = 0;
-      if (keyCode == goog.events.KeyCodes.DOWN) {
-        deltaY = -mapUnitsDelta;
-      } else if (keyCode == goog.events.KeyCodes.LEFT) {
-        deltaX = -mapUnitsDelta;
-      } else if (keyCode == goog.events.KeyCodes.RIGHT) {
-        deltaX = mapUnitsDelta;
-      } else {
-        deltaY = mapUnitsDelta;
-      }
-      var delta = [deltaX, deltaY];
-      ol.coordinate.rotate(delta, viewState.rotation);
-      ol.interaction.Interaction.pan(
-          map, view, delta, ol.KEYBOARD_PAN_DURATION);
-      mapBrowserEvent.preventDefault();
-      stopEvent = true;
-    }
-  }
-  return !stopEvent;
-};
+if (goog.DEBUG) {
+  (function() {
+    goog.debug.Console.autoInstall();
+    var logger = goog.log.getLogger('ol');
+    logger.setLevel(goog.log.Level.FINEST);
+  })();
+}
 
-goog.provide('ol.interaction.KeyboardZoom');
+goog.provide('ol.Overlay');
+goog.provide('ol.OverlayPositioning');
+goog.provide('ol.OverlayProperty');
 
 goog.require('goog.asserts');
-goog.require('goog.events.KeyHandler.EventType');
-goog.require('ol.events.ConditionType');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Interaction');
-
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.style');
+goog.require('ol.Coordinate');
+goog.require('ol.Map');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
 
 
 /**
- * @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
+ * @enum {string}
  */
-ol.interaction.KeyboardZoom = function(opt_options) {
-
-  goog.base(this);
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.condition_ = goog.isDef(options.condition) ? options.condition :
-          ol.events.condition.targetNotEditable;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = goog.isDef(options.delta) ? options.delta : 1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = goog.isDef(options.duration) ? options.duration : 100;
-
+ol.OverlayProperty = {
+  ELEMENT: 'element',
+  MAP: 'map',
+  OFFSET: 'offset',
+  POSITION: 'position',
+  POSITIONING: 'positioning'
 };
-goog.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction);
 
 
 /**
- * @inheritDoc
+ * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
+ * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
+ * `'top-center'`, `'top-right'`
+ * @enum {string}
+ * @api stable
  */
-ol.interaction.KeyboardZoom.prototype.handleMapBrowserEvent =
-    function(mapBrowserEvent) {
-  var stopEvent = false;
-  if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) {
-    var keyEvent = /** @type {goog.events.KeyEvent} */
-        (mapBrowserEvent.browserEvent);
-    var charCode = keyEvent.charCode;
-    if (this.condition_(mapBrowserEvent) &&
-        (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) {
-      var map = mapBrowserEvent.map;
-      var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_;
-      map.render();
-      var view = map.getView();
-      goog.asserts.assert(goog.isDef(view));
-      ol.interaction.Interaction.zoomByDelta(
-          map, view, delta, undefined, this.duration_);
-      mapBrowserEvent.preventDefault();
-      stopEvent = true;
-    }
-  }
-  return !stopEvent;
+ol.OverlayPositioning = {
+  BOTTOM_LEFT: 'bottom-left',
+  BOTTOM_CENTER: 'bottom-center',
+  BOTTOM_RIGHT: 'bottom-right',
+  CENTER_LEFT: 'center-left',
+  CENTER_CENTER: 'center-center',
+  CENTER_RIGHT: 'center-right',
+  TOP_LEFT: 'top-left',
+  TOP_CENTER: 'top-center',
+  TOP_RIGHT: 'top-right'
 };
 
-goog.provide('ol.interaction.MouseWheelZoom');
-
-goog.require('goog.asserts');
-goog.require('goog.events.MouseWheelEvent');
-goog.require('goog.events.MouseWheelHandler.EventType');
-goog.require('goog.math');
-goog.require('ol');
-goog.require('ol.Coordinate');
-goog.require('ol.interaction.Interaction');
-
 
 
 /**
  * @classdesc
- * Allows the user to zoom the map by scrolling the mouse wheel.
+ * 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.interaction.Interaction}
- * @param {olx.interaction.MouseWheelZoomOptions=} opt_options Options.
+ * @extends {ol.Object}
+ * @param {olx.OverlayOptions} options Overlay options.
  * @api stable
  */
-ol.interaction.MouseWheelZoom = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.Overlay = function(options) {
 
   goog.base(this);
 
   /**
    * @private
-   * @type {number}
+   * @type {boolean}
    */
-  this.delta_ = 0;
+  this.insertFirst_ = goog.isDef(options.insertFirst) ?
+      options.insertFirst : true;
 
   /**
    * @private
-   * @type {number}
+   * @type {boolean}
    */
-  this.duration_ = goog.isDef(options.duration) ? options.duration : 250;
+  this.stopEvent_ = goog.isDef(options.stopEvent) ? options.stopEvent : true;
 
   /**
    * @private
-   * @type {?ol.Coordinate}
+   * @type {Element}
    */
-  this.lastAnchor_ = null;
+  this.element_ = goog.dom.createElement(goog.dom.TagName.DIV);
+  this.element_.style.position = 'absolute';
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {{bottom_: string,
+   *         left_: string,
+   *         right_: string,
+   *         top_: string,
+   *         visible: boolean}}
    */
-  this.startTime_ = undefined;
+  this.rendered_ = {
+    bottom_: '',
+    left_: '',
+    right_: '',
+    top_: '',
+    visible: true
+  };
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {goog.events.Key}
    */
-  this.timeoutId_ = undefined;
+  this.mapPostrenderListenerKey_ = null;
 
-};
-goog.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction);
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.OverlayProperty.ELEMENT),
+      this.handleElementChanged, false, this);
 
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.OverlayProperty.MAP),
+      this.handleMapChanged, false, this);
 
-/**
- * @inheritDoc
- */
-ol.interaction.MouseWheelZoom.prototype.handleMapBrowserEvent =
-    function(mapBrowserEvent) {
-  var stopEvent = false;
-  if (mapBrowserEvent.type ==
-      goog.events.MouseWheelHandler.EventType.MOUSEWHEEL) {
-    var map = mapBrowserEvent.map;
-    var mouseWheelEvent = mapBrowserEvent.browserEvent;
-    goog.asserts.assertInstanceof(mouseWheelEvent, goog.events.MouseWheelEvent);
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.OverlayProperty.OFFSET),
+      this.handleOffsetChanged, false, this);
 
-    this.lastAnchor_ = mapBrowserEvent.coordinate;
-    this.delta_ += mouseWheelEvent.deltaY;
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITION),
+      this.handlePositionChanged, false, this);
 
-    if (!goog.isDef(this.startTime_)) {
-      this.startTime_ = goog.now();
-    }
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITIONING),
+      this.handlePositioningChanged, false, this);
 
-    var duration = ol.MOUSEWHEELZOOM_TIMEOUT_DURATION;
-    var timeLeft = Math.max(duration - (goog.now() - this.startTime_), 0);
+  if (goog.isDef(options.element)) {
+    this.setElement(options.element);
+  }
 
-    goog.global.clearTimeout(this.timeoutId_);
-    this.timeoutId_ = goog.global.setTimeout(
-        goog.bind(this.doZoom_, this, map), timeLeft);
+  this.setOffset(goog.isDef(options.offset) ? options.offset : [0, 0]);
 
-    mapBrowserEvent.preventDefault();
-    stopEvent = true;
+  this.setPositioning(goog.isDef(options.positioning) ?
+      /** @type {ol.OverlayPositioning} */ (options.positioning) :
+      ol.OverlayPositioning.TOP_LEFT);
+
+  if (goog.isDef(options.position)) {
+    this.setPosition(options.position);
   }
-  return !stopEvent;
+
 };
+goog.inherits(ol.Overlay, ol.Object);
 
 
 /**
- * @private
- * @param {ol.Map} map Map.
+ * Get the DOM element of this overlay.
+ * @return {Element|undefined} The Element containing the overlay.
+ * @observable
+ * @api stable
  */
-ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) {
-  var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA;
-  var delta = goog.math.clamp(this.delta_, -maxDelta, maxDelta);
-
-  var view = map.getView();
-  goog.asserts.assert(goog.isDef(view));
+ol.Overlay.prototype.getElement = function() {
+  return /** @type {Element|undefined} */ (
+      this.get(ol.OverlayProperty.ELEMENT));
+};
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'getElement',
+    ol.Overlay.prototype.getElement);
 
-  map.render();
-  ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_,
-      this.duration_);
 
-  this.delta_ = 0;
-  this.lastAnchor_ = null;
-  this.startTime_ = undefined;
-  this.timeoutId_ = undefined;
+/**
+ * Get the map associated with this overlay.
+ * @return {ol.Map|undefined} The map that the overlay is part of.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.getMap = function() {
+  return /** @type {ol.Map|undefined} */ (
+      this.get(ol.OverlayProperty.MAP));
 };
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'getMap',
+    ol.Overlay.prototype.getMap);
 
-goog.provide('ol.interaction.PinchRotate');
-
-goog.require('goog.asserts');
-goog.require('goog.style');
-goog.require('ol');
-goog.require('ol.Coordinate');
-goog.require('ol.ViewHint');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
 
+/**
+ * Get the offset of this overlay.
+ * @return {Array.<number>} The offset.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.getOffset = function() {
+  return /** @type {Array.<number>} */ (
+      this.get(ol.OverlayProperty.OFFSET));
+};
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'getOffset',
+    ol.Overlay.prototype.getOffset);
 
 
 /**
- * @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.
+ * Get the current position of this overlay.
+ * @return {ol.Coordinate|undefined} The spatial point that the overlay is
+ *     anchored at.
+ * @observable
  * @api stable
  */
-ol.interaction.PinchRotate = function(opt_options) {
+ol.Overlay.prototype.getPosition = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+      this.get(ol.OverlayProperty.POSITION));
+};
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'getPosition',
+    ol.Overlay.prototype.getPosition);
 
-  goog.base(this);
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+/**
+ * Get the current positioning of this overlay.
+ * @return {ol.OverlayPositioning} How the overlay is positioned
+ *     relative to its point on the map.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.getPositioning = function() {
+  return /** @type {ol.OverlayPositioning} */ (
+      this.get(ol.OverlayProperty.POSITIONING));
+};
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'getPositioning',
+    ol.Overlay.prototype.getPositioning);
 
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.anchor_ = null;
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastAngle_ = undefined;
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleElementChanged = function() {
+  goog.dom.removeChildren(this.element_);
+  var element = this.getElement();
+  if (goog.isDefAndNotNull(element)) {
+    goog.dom.append(/** @type {!Node} */ (this.element_), element);
+  }
+};
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.rotating_ = false;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.rotationDelta_ = 0.0;
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleMapChanged = function() {
+  if (!goog.isNull(this.mapPostrenderListenerKey_)) {
+    goog.dom.removeNode(this.element_);
+    goog.events.unlistenByKey(this.mapPostrenderListenerKey_);
+    this.mapPostrenderListenerKey_ = null;
+  }
+  var map = this.getMap();
+  if (goog.isDefAndNotNull(map)) {
+    this.mapPostrenderListenerKey_ = goog.events.listen(map,
+        ol.MapEventType.POSTRENDER, this.render, false, this);
+    this.updatePixelPosition_();
+    var container = this.stopEvent_ ?
+        map.getOverlayContainerStopEvent() : map.getOverlayContainer();
+    if (this.insertFirst_) {
+      goog.dom.insertChildAt(/** @type {!Element} */ (
+          container), this.element_, 0);
+    } else {
+      goog.dom.append(/** @type {!Node} */ (container), this.element_);
+    }
+  }
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.threshold_ = goog.isDef(options.threshold) ? options.threshold : 0.3;
 
+/**
+ * @protected
+ */
+ol.Overlay.prototype.render = function() {
+  this.updatePixelPosition_();
 };
-goog.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);
 
 
 /**
- * @inheritDoc
+ * @protected
  */
-ol.interaction.PinchRotate.prototype.handlePointerDrag =
-    function(mapBrowserEvent) {
-  goog.asserts.assert(this.targetPointers.length >= 2);
-  var rotationDelta = 0.0;
+ol.Overlay.prototype.handleOffsetChanged = function() {
+  this.updatePixelPosition_();
+};
 
-  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);
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handlePositionChanged = function() {
+  this.updatePixelPosition_();
+};
 
-  if (goog.isDef(this.lastAngle_)) {
-    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;
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handlePositioningChanged = function() {
+  this.updatePixelPosition_();
+};
 
-  // rotate anchor point.
-  // FIXME: should be the intersection point between the lines:
-  //     touch0,touch1 and previousTouch0,previousTouch1
-  var viewportPosition = goog.style.getClientPosition(map.getViewport());
-  var centroid =
-      ol.interaction.Pointer.centroid(this.targetPointers);
-  centroid[0] -= viewportPosition.x;
-  centroid[1] -= viewportPosition.y;
-  this.anchor_ = map.getCoordinateFromPixel(centroid);
 
-  // rotate
-  if (this.rotating_) {
-    var view = map.getView();
-    var viewState = view.getState();
-    map.render();
-    ol.interaction.Interaction.rotateWithoutConstraints(map, view,
-        viewState.rotation + rotationDelta, this.anchor_);
-  }
+/**
+ * Set the DOM element to be associated with this overlay.
+ * @param {Element|undefined} element The Element containing the overlay.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.setElement = function(element) {
+  this.set(ol.OverlayProperty.ELEMENT, element);
 };
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'setElement',
+    ol.Overlay.prototype.setElement);
 
 
 /**
- * @inheritDoc
+ * 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.interaction.PinchRotate.prototype.handlePointerUp =
-    function(mapBrowserEvent) {
-  if (this.targetPointers.length < 2) {
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    view.setHint(ol.ViewHint.INTERACTING, -1);
-    if (this.rotating_) {
-      var viewState = view.getState();
-      ol.interaction.Interaction.rotate(
-          map, view, viewState.rotation, this.anchor_,
-          ol.ROTATE_ANIMATION_DURATION);
-    }
-    return false;
-  } else {
-    return true;
-  }
+ol.Overlay.prototype.setMap = function(map) {
+  this.set(ol.OverlayProperty.MAP, map);
 };
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'setMap',
+    ol.Overlay.prototype.setMap);
 
 
 /**
- * @inheritDoc
+ * Set the offset for this overlay.
+ * @param {Array.<number>} offset Offset.
+ * @observable
+ * @api stable
  */
-ol.interaction.PinchRotate.prototype.handlePointerDown =
-    function(mapBrowserEvent) {
-  if (this.targetPointers.length >= 2) {
-    var map = mapBrowserEvent.map;
-    this.anchor_ = null;
-    this.lastAngle_ = undefined;
-    this.rotating_ = false;
-    this.rotationDelta_ = 0.0;
-    if (!this.handlingDownUpSequence) {
-      map.getView().setHint(ol.ViewHint.INTERACTING, 1);
+ol.Overlay.prototype.setOffset = function(offset) {
+  this.set(ol.OverlayProperty.OFFSET, offset);
+};
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'setOffset',
+    ol.Overlay.prototype.setOffset);
+
+
+/**
+ * Set the position for this overlay.
+ * @param {ol.Coordinate|undefined} position The spatial point that the overlay
+ *     is anchored at.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.setPosition = function(position) {
+  this.set(ol.OverlayProperty.POSITION, position);
+};
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'setPosition',
+    ol.Overlay.prototype.setPosition);
+
+
+/**
+ * Set the positioning for this overlay.
+ * @param {ol.OverlayPositioning} positioning how the overlay is
+ *     positioned relative to its point on the map.
+ * @observable
+ * @api stable
+ */
+ol.Overlay.prototype.setPositioning = function(positioning) {
+  this.set(ol.OverlayProperty.POSITIONING, positioning);
+};
+goog.exportProperty(
+    ol.Overlay.prototype,
+    'setPositioning',
+    ol.Overlay.prototype.setPositioning);
+
+
+/**
+ * @private
+ */
+ol.Overlay.prototype.updatePixelPosition_ = function() {
+
+  var map = this.getMap();
+  var position = this.getPosition();
+  if (!goog.isDef(map) || !map.isRendered() || !goog.isDef(position)) {
+    if (this.rendered_.visible) {
+      goog.style.setElementShown(this.element_, false);
+      this.rendered_.visible = false;
+    }
+    return;
+  }
+
+  var pixel = map.getPixelFromCoordinate(position);
+  goog.asserts.assert(!goog.isNull(pixel));
+  var mapSize = map.getSize();
+  goog.asserts.assert(goog.isDef(mapSize));
+  var style = this.element_.style;
+  var offset = this.getOffset();
+  goog.asserts.assert(goog.isArray(offset));
+  var positioning = this.getPositioning();
+  goog.asserts.assert(goog.isDef(positioning));
+
+  var offsetX = offset[0];
+  var offsetY = offset[1];
+  if (positioning == ol.OverlayPositioning.BOTTOM_RIGHT ||
+      positioning == ol.OverlayPositioning.CENTER_RIGHT ||
+      positioning == ol.OverlayPositioning.TOP_RIGHT) {
+    if (this.rendered_.left_ !== '') {
+      this.rendered_.left_ = style.left = '';
+    }
+    var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px';
+    if (this.rendered_.right_ != right) {
+      this.rendered_.right_ = style.right = right;
     }
-    map.render();
-    return true;
   } else {
-    return false;
+    if (this.rendered_.right_ !== '') {
+      this.rendered_.right_ = style.right = '';
+    }
+    if (positioning == ol.OverlayPositioning.BOTTOM_CENTER ||
+        positioning == ol.OverlayPositioning.CENTER_CENTER ||
+        positioning == ol.OverlayPositioning.TOP_CENTER) {
+      offsetX -= goog.style.getSize(this.element_).width / 2;
+    }
+    var left = Math.round(pixel[0] + offsetX) + 'px';
+    if (this.rendered_.left_ != left) {
+      this.rendered_.left_ = style.left = left;
+    }
+  }
+  if (positioning == ol.OverlayPositioning.BOTTOM_LEFT ||
+      positioning == ol.OverlayPositioning.BOTTOM_CENTER ||
+      positioning == ol.OverlayPositioning.BOTTOM_RIGHT) {
+    if (this.rendered_.top_ !== '') {
+      this.rendered_.top_ = style.top = '';
+    }
+    var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px';
+    if (this.rendered_.bottom_ != bottom) {
+      this.rendered_.bottom_ = style.bottom = bottom;
+    }
+  } else {
+    if (this.rendered_.bottom_ !== '') {
+      this.rendered_.bottom_ = style.bottom = '';
+    }
+    if (positioning == ol.OverlayPositioning.CENTER_LEFT ||
+        positioning == ol.OverlayPositioning.CENTER_CENTER ||
+        positioning == ol.OverlayPositioning.CENTER_RIGHT) {
+      offsetY -= goog.style.getSize(this.element_).height / 2;
+    }
+    var top = Math.round(pixel[1] + offsetY) + 'px';
+    if (this.rendered_.top_ != top) {
+      this.rendered_.top_ = style.top = top;
+    }
   }
+
+  if (!this.rendered_.visible) {
+    goog.style.setElementShown(this.element_, true);
+    this.rendered_.visible = true;
+  }
+
 };
 
-goog.provide('ol.interaction.PinchZoom');
+goog.provide('ol.control.OverviewMap');
 
 goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.math.Size');
 goog.require('goog.style');
-goog.require('ol.Coordinate');
-goog.require('ol.ViewHint');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
+goog.require('ol.Collection');
+goog.require('ol.Map');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
+goog.require('ol.Overlay');
+goog.require('ol.OverlayPositioning');
+goog.require('ol.View');
+goog.require('ol.control.Control');
+goog.require('ol.coordinate');
+goog.require('ol.css');
+goog.require('ol.extent');
 
 
 
 /**
- * @classdesc
- * Allows the user to zoom the map by pinching with two fingers
- * on a touch screen.
- *
+ * Create a new control with a map acting as an overview map for an other
+ * defined map.
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.PinchZoomOptions=} opt_options Options.
- * @api stable
+ * @extends {ol.control.Control}
+ * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options.
+ * @api
  */
-ol.interaction.PinchZoom = function(opt_options) {
+ol.control.OverviewMap = function(opt_options) {
 
   var options = goog.isDef(opt_options) ? opt_options : {};
 
-  goog.base(this);
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.collapsed_ = goog.isDef(options.collapsed) ? options.collapsed : true;
 
   /**
    * @private
-   * @type {ol.Coordinate}
+   * @type {boolean}
    */
-  this.anchor_ = null;
+  this.collapsible_ = goog.isDef(options.collapsible) ?
+      options.collapsible : true;
+
+  if (!this.collapsible_) {
+    this.collapsed_ = false;
+  }
+
+  var className = goog.isDef(options.className) ?
+      options.className : 'ol-overviewmap';
+
+  var tipLabel = goog.isDef(options.tipLabel) ?
+      options.tipLabel : 'Overview map';
 
   /**
    * @private
-   * @type {number}
+   * @type {string}
    */
-  this.duration_ = goog.isDef(options.duration) ? options.duration : 400;
+  this.collapseLabel_ = goog.isDef(options.collapseLabel) ?
+      options.collapseLabel : '\u00AB';
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {string}
    */
-  this.lastDistance_ = undefined;
+  this.label_ = goog.isDef(options.label) ? options.label : '\u00BB';
+  var label = goog.dom.createDom(goog.dom.TagName.SPAN, {},
+      (this.collapsible_ && !this.collapsed_) ?
+      this.collapseLabel_ : this.label_);
 
   /**
    * @private
-   * @type {number}
+   * @type {Element}
    */
-  this.lastScaleDelta_ = 1;
+  this.labelSpan_ = label;
+  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
+    'type': 'button',
+    'title': tipLabel
+  }, this.labelSpan_);
+
+  goog.events.listen(button, goog.events.EventType.CLICK,
+      this.handleClick_, false, this);
+
+  goog.events.listen(button, [
+    goog.events.EventType.MOUSEOUT,
+    goog.events.EventType.FOCUSOUT
+  ], function() {
+    this.blur();
+  }, false);
+
+  var ovmapDiv = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-map');
+
+  /**
+   * @type {ol.Map}
+   * @private
+   */
+  this.ovmap_ = new ol.Map({
+    controls: new ol.Collection(),
+    interactions: new ol.Collection(),
+    target: ovmapDiv
+  });
+  var ovmap = this.ovmap_;
+
+  if (goog.isDef(options.layers)) {
+    options.layers.forEach(
+        /**
+       * @param {ol.layer.Layer} layer Layer.
+       */
+        function(layer) {
+          ovmap.addLayer(layer);
+        }, this);
+  }
 
+  var box = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-box');
+
+  /**
+   * @type {ol.Overlay}
+   * @private
+   */
+  this.boxOverlay_ = new ol.Overlay({
+    position: [0, 0],
+    positioning: ol.OverlayPositioning.BOTTOM_LEFT,
+    element: box
+  });
+  this.ovmap_.addOverlay(this.boxOverlay_);
+
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL +
+      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
+      (this.collapsible_ ? '' : ' ol-uncollapsible');
+  var element = goog.dom.createDom(goog.dom.TagName.DIV,
+      cssClasses, ovmapDiv, button);
+
+  var render = goog.isDef(options.render) ?
+      options.render : ol.control.OverviewMap.render;
+
+  goog.base(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
 };
-goog.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer);
+goog.inherits(ol.control.OverviewMap, ol.control.Control);
 
 
 /**
  * @inheritDoc
+ * @api
  */
-ol.interaction.PinchZoom.prototype.handlePointerDrag =
-    function(mapBrowserEvent) {
-  goog.asserts.assert(this.targetPointers.length >= 2);
-  var scaleDelta = 1.0;
+ol.control.OverviewMap.prototype.setMap = function(map) {
+  var currentMap = this.getMap();
 
-  var touch0 = this.targetPointers[0];
-  var touch1 = this.targetPointers[1];
-  var dx = touch0.clientX - touch1.clientX;
-  var dy = touch0.clientY - touch1.clientY;
+  if (goog.isNull(map) && !goog.isNull(currentMap)) {
+    goog.events.unlisten(
+        currentMap, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
+        this.handleViewChanged_, false, this);
+  }
 
-  // distance between touches
-  var distance = Math.sqrt(dx * dx + dy * dy);
+  goog.base(this, 'setMap', map);
 
-  if (goog.isDef(this.lastDistance_)) {
-    scaleDelta = this.lastDistance_ / distance;
-  }
-  this.lastDistance_ = distance;
-  if (scaleDelta != 1.0) {
-    this.lastScaleDelta_ = scaleDelta;
-  }
+  if (!goog.isNull(map)) {
 
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  var viewState = view.getState();
+    // if no layers were set for the overviewmap map, then bind with
+    // those in the main map
+    if (this.ovmap_.getLayers().getLength() === 0) {
+      this.ovmap_.bindTo(ol.MapProperty.LAYERGROUP, map);
+    }
 
-  // scale anchor point.
-  var viewportPosition = goog.style.getClientPosition(map.getViewport());
-  var centroid =
-      ol.interaction.Pointer.centroid(this.targetPointers);
-  centroid[0] -= viewportPosition.x;
-  centroid[1] -= viewportPosition.y;
-  this.anchor_ = map.getCoordinateFromPixel(centroid);
+    // bind current map view, or any new one
+    this.bindView_();
 
-  // scale, bypass the resolution constraint
-  map.render();
-  ol.interaction.Interaction.zoomWithoutConstraints(
-      map, view, viewState.resolution * scaleDelta, this.anchor_);
+    goog.events.listen(
+        map, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
+        this.handleViewChanged_, false, this);
 
+    this.ovmap_.updateSize();
+    this.resetExtent_();
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Bind some actions to the main map view.
+ * @private
  */
-ol.interaction.PinchZoom.prototype.handlePointerUp =
-    function(mapBrowserEvent) {
-  if (this.targetPointers.length < 2) {
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    view.setHint(ol.ViewHint.INTERACTING, -1);
-    var viewState = view.getState();
-    // Zoom to final resolution, with an animation, and provide a
-    // direction not to zoom out/in if user was pinching in/out.
-    // Direction is > 0 if pinching out, and < 0 if pinching in.
-    var direction = this.lastScaleDelta_ - 1;
-    ol.interaction.Interaction.zoom(map, view, viewState.resolution,
-        this.anchor_, this.duration_, direction);
-    return false;
-  } else {
-    return true;
+ol.control.OverviewMap.prototype.bindView_ = function() {
+  var map = this.getMap();
+  var view = map.getView();
+
+  // if the map does not have a view, we can't act upon it
+  if (goog.isNull(view)) {
+    return;
   }
+
+  // FIXME - the overviewmap view rotation currently follows the one used
+  // by the main map view.  We could support box rotation instead.  The choice
+  // between the 2 modes would be made in a single option
+  this.ovmap_.getView().bindTo(ol.ViewProperty.ROTATION, view);
 };
 
 
 /**
- * @inheritDoc
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.OverviewMap}
+ * @api
  */
-ol.interaction.PinchZoom.prototype.handlePointerDown =
-    function(mapBrowserEvent) {
-  if (this.targetPointers.length >= 2) {
-    var map = mapBrowserEvent.map;
-    this.anchor_ = null;
-    this.lastDistance_ = undefined;
-    this.lastScaleDelta_ = 1;
-    if (!this.handlingDownUpSequence) {
-      map.getView().setHint(ol.ViewHint.INTERACTING, 1);
-    }
-    map.render();
-    return true;
-  } else {
-    return false;
-  }
+ol.control.OverviewMap.render = function(mapEvent) {
+  this.validateExtent_();
+  this.updateBox_();
 };
 
-goog.provide('ol.interaction');
 
-goog.require('ol.Collection');
-goog.require('ol.Kinetic');
-goog.require('ol.interaction.DoubleClickZoom');
-goog.require('ol.interaction.DragPan');
-goog.require('ol.interaction.DragRotate');
-goog.require('ol.interaction.DragZoom');
-goog.require('ol.interaction.KeyboardPan');
-goog.require('ol.interaction.KeyboardZoom');
-goog.require('ol.interaction.MouseWheelZoom');
-goog.require('ol.interaction.PinchRotate');
-goog.require('ol.interaction.PinchZoom');
+/**
+ * Called on main map view changed.
+ * @param {goog.events.Event} event Event.
+ * @private
+ */
+ol.control.OverviewMap.prototype.handleViewChanged_ = function(event) {
+  this.bindView_();
+};
 
 
 /**
- * Set of interactions included in maps by default. Specific interactions can be
- * excluded by setting the appropriate option to false in the constructor
- * options, but the order of the interactions is fixed.  If you want to specify
- * a different order for interactions, you will need to create your own
- * {@link ol.interaction.Interaction} instances and insert them into a
- * {@link ol.Collection} in the order you want before creating your
- * {@link ol.Map} instance. The default set of interactions, in sequence, is:
- * * {@link ol.interaction.DragRotate}
- * * {@link ol.interaction.DoubleClickZoom}
- * * {@link ol.interaction.DragPan}
- * * {@link ol.interaction.PinchRotate}
- * * {@link ol.interaction.PinchZoom}
- * * {@link ol.interaction.KeyboardPan}
- * * {@link ol.interaction.KeyboardZoom}
- * * {@link ol.interaction.MouseWheelZoom}
- * * {@link ol.interaction.DragZoom}
- *
- * Note that DragZoom renders a box as a vector polygon, so this interaction
- * should be excluded if you want a build with no vector support.
+ * 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.
  *
- * @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
+ * 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.interaction.defaults = function(opt_options) {
+ol.control.OverviewMap.prototype.validateExtent_ = function() {
+  var map = this.getMap();
+  var ovmap = this.ovmap_;
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  if (!map.isRendered() || !ovmap.isRendered()) {
+    return;
+  }
 
-  var interactions = new ol.Collection();
+  var mapSize = map.getSize();
+  goog.asserts.assertArray(mapSize);
 
-  var kinetic = new ol.Kinetic(-0.005, 0.05, 100);
+  var view = map.getView();
+  goog.asserts.assert(goog.isDef(view));
+  var extent = view.calculateExtent(mapSize);
 
-  var altShiftDragRotate = goog.isDef(options.altShiftDragRotate) ?
-      options.altShiftDragRotate : true;
-  if (altShiftDragRotate) {
-    interactions.push(new ol.interaction.DragRotate());
-  }
+  var ovmapSize = ovmap.getSize();
+  goog.asserts.assertArray(ovmapSize);
 
-  var doubleClickZoom = goog.isDef(options.doubleClickZoom) ?
-      options.doubleClickZoom : true;
-  if (doubleClickZoom) {
-    interactions.push(new ol.interaction.DoubleClickZoom({
-      delta: options.zoomDelta,
-      duration: options.zoomDuration
-    }));
+  var ovview = ovmap.getView();
+  goog.asserts.assert(goog.isDef(ovview));
+  var ovextent = ovview.calculateExtent(ovmapSize);
+
+  var topLeftPixel =
+      ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent));
+  var bottomRightPixel =
+      ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent));
+  var boxSize = new goog.math.Size(
+      Math.abs(topLeftPixel[0] - bottomRightPixel[0]),
+      Math.abs(topLeftPixel[1] - bottomRightPixel[1]));
+
+  var ovmapWidth = ovmapSize[0];
+  var ovmapHeight = ovmapSize[1];
+
+  if (boxSize.width < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO ||
+      boxSize.height < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO ||
+      boxSize.width > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO ||
+      boxSize.height > ovmapHeight * ol.OVERVIEWMAP_MAX_RATIO) {
+    this.resetExtent_();
+  } else if (!ol.extent.containsExtent(ovextent, extent)) {
+    this.recenter_();
   }
+};
 
-  var dragPan = goog.isDef(options.dragPan) ?
-      options.dragPan : true;
-  if (dragPan) {
-    interactions.push(new ol.interaction.DragPan({
-      kinetic: kinetic
-    }));
-  }
 
-  var pinchRotate = goog.isDef(options.pinchRotate) ?
-      options.pinchRotate : true;
-  if (pinchRotate) {
-    interactions.push(new ol.interaction.PinchRotate());
+/**
+ * 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 pinchZoom = goog.isDef(options.pinchZoom) ?
-      options.pinchZoom : true;
-  if (pinchZoom) {
-    interactions.push(new ol.interaction.PinchZoom({
-      duration: options.zoomDuration
-    }));
-  }
+  var map = this.getMap();
+  var ovmap = this.ovmap_;
 
-  var keyboard = goog.isDef(options.keyboard) ?
-      options.keyboard : true;
-  if (keyboard) {
-    interactions.push(new ol.interaction.KeyboardPan());
-    interactions.push(new ol.interaction.KeyboardZoom({
-      delta: options.zoomDelta,
-      duration: options.zoomDuration
-    }));
-  }
+  var mapSize = map.getSize();
+  goog.asserts.assertArray(mapSize);
 
-  var mouseWheelZoom = goog.isDef(options.mouseWheelZoom) ?
-      options.mouseWheelZoom : true;
-  if (mouseWheelZoom) {
-    interactions.push(new ol.interaction.MouseWheelZoom({
-      duration: options.zoomDuration
-    }));
-  }
+  var view = map.getView();
+  goog.asserts.assert(goog.isDef(view));
+  var extent = view.calculateExtent(mapSize);
 
-  var shiftDragZoom = goog.isDef(options.shiftDragZoom) ?
-      options.shiftDragZoom : true;
-  if (shiftDragZoom) {
-    interactions.push(new ol.interaction.DragZoom());
-  }
+  var ovmapSize = ovmap.getSize();
+  goog.asserts.assertArray(ovmapSize);
 
-  return interactions;
+  var ovview = ovmap.getView();
+  goog.asserts.assert(goog.isDef(ovview));
 
+  // 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.fitExtent(extent, ovmapSize);
 };
 
-goog.provide('ol.layer.Group');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.math');
-goog.require('goog.object');
-goog.require('ol.Collection');
-goog.require('ol.CollectionEvent');
-goog.require('ol.CollectionEventType');
-goog.require('ol.Object');
-goog.require('ol.ObjectEventType');
-goog.require('ol.extent');
-goog.require('ol.layer.Base');
-goog.require('ol.source.State');
-
 
 /**
- * @enum {string}
+ * Set the center of the overview map to the map center without changing its
+ * resolution.
+ * @private
  */
-ol.layer.GroupProperty = {
-  LAYERS: 'layers'
-};
+ol.control.OverviewMap.prototype.recenter_ = function() {
+  var map = this.getMap();
+  var ovmap = this.ovmap_;
+
+  var view = map.getView();
+  goog.asserts.assert(goog.isDef(view));
 
+  var ovview = ovmap.getView();
+  goog.asserts.assert(goog.isDef(ovview));
+
+  ovview.setCenter(view.getCenter());
+};
 
 
 /**
- * @classdesc
- * A {@link ol.Collection} of layers that are handled together.
- *
- * @constructor
- * @extends {ol.layer.Base}
- * @fires change Triggered when the group/Collection changes.
- * @param {olx.layer.GroupOptions=} opt_options Layer options.
- * @api stable
+ * Update the box using the main map extent
+ * @private
  */
-ol.layer.Group = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-  var baseOptions = /** @type {olx.layer.GroupOptions} */
-      (goog.object.clone(options));
-  delete baseOptions.layers;
+ol.control.OverviewMap.prototype.updateBox_ = function() {
+  var map = this.getMap();
+  var ovmap = this.ovmap_;
 
-  var layers = options.layers;
+  if (!map.isRendered() || !ovmap.isRendered()) {
+    return;
+  }
 
-  goog.base(this, baseOptions);
+  var mapSize = map.getSize();
+  goog.asserts.assertArray(mapSize);
 
-  /**
-   * @private
-   * @type {Object.<string, goog.events.Key>}
-   */
-  this.listenerKeys_ = null;
+  var view = map.getView();
+  goog.asserts.assert(goog.isDef(view));
 
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.GroupProperty.LAYERS),
-      this.handleLayersChanged_, false, this);
+  var ovview = ovmap.getView();
+  goog.asserts.assert(goog.isDef(ovview));
 
-  if (goog.isDef(layers)) {
-    if (goog.isArray(layers)) {
-      layers = new ol.Collection(goog.array.clone(layers));
-    } else {
-      goog.asserts.assertInstanceof(layers, ol.Collection);
-      layers = layers;
-    }
-  } else {
-    layers = new ol.Collection();
-  }
+  var ovmapSize = ovmap.getSize();
+  goog.asserts.assertArray(ovmapSize);
 
-  this.setLayers(layers);
+  var rotation = view.getRotation();
+  goog.asserts.assert(goog.isDef(rotation));
 
-};
-goog.inherits(ol.layer.Group, ol.layer.Base);
+  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);
 
-/**
- * @private
- */
-ol.layer.Group.prototype.handleLayerChange_ = function() {
-  if (this.getVisible()) {
-    this.dispatchChangeEvent();
+  // set box size calculated from map extent size and overview map resolution
+  if (goog.isDefAndNotNull(box)) {
+    var boxWidth = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution);
+    var boxHeight = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution);
+    goog.style.setBorderBoxSize(box, new goog.math.Size(
+        boxWidth, boxHeight));
   }
 };
 
 
 /**
- * @param {goog.events.Event} event Event.
+ * @param {number} rotation Target rotation.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor.
  * @private
  */
-ol.layer.Group.prototype.handleLayersChanged_ = function(event) {
-  if (!goog.isNull(this.listenerKeys_)) {
-    goog.array.forEach(
-        goog.object.getValues(this.listenerKeys_), goog.events.unlistenByKey);
-    this.listenerKeys_ = null;
-  }
+ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function(
+    rotation, coordinate) {
+  var coordinateRotate;
 
-  var layers = this.getLayers();
-  if (goog.isDefAndNotNull(layers)) {
-    this.listenerKeys_ = {
-      'add': goog.events.listen(layers, ol.CollectionEventType.ADD,
-          this.handleLayersAdd_, false, this),
-      'remove': goog.events.listen(layers, ol.CollectionEventType.REMOVE,
-          this.handleLayersRemove_, false, this)
-    };
+  var map = this.getMap();
+  var view = map.getView();
+  goog.asserts.assert(goog.isDef(view));
 
-    var layersArray = layers.getArray();
-    var i, ii, layer;
-    for (i = 0, ii = layersArray.length; i < ii; i++) {
-      layer = layersArray[i];
-      this.listenerKeys_[goog.getUid(layer).toString()] =
-          goog.events.listen(layer,
-              [ol.ObjectEventType.PROPERTYCHANGE, goog.events.EventType.CHANGE],
-              this.handleLayerChange_, false, this);
-    }
-  }
+  var currentCenter = view.getCenter();
 
-  this.dispatchChangeEvent();
+  if (goog.isDef(currentCenter)) {
+    coordinateRotate = [
+      coordinate[0] - currentCenter[0],
+      coordinate[1] - currentCenter[1]
+    ];
+    ol.coordinate.rotate(coordinateRotate, rotation);
+    ol.coordinate.add(coordinateRotate, currentCenter);
+  }
+  return coordinateRotate;
 };
 
 
 /**
- * @param {ol.CollectionEvent} collectionEvent Collection event.
+ * @param {goog.events.BrowserEvent} event The event to handle
  * @private
  */
-ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) {
-  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
-  this.listenerKeys_[goog.getUid(layer).toString()] = goog.events.listen(
-      layer, [ol.ObjectEventType.PROPERTYCHANGE, goog.events.EventType.CHANGE],
-      this.handleLayerChange_, false, this);
-  this.dispatchChangeEvent();
+ol.control.OverviewMap.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleToggle_();
 };
 
 
 /**
- * @param {ol.CollectionEvent} collectionEvent Collection event.
  * @private
  */
-ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) {
-  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
-  var key = goog.getUid(layer).toString();
-  goog.events.unlistenByKey(this.listenerKeys_[key]);
-  delete this.listenerKeys_[key];
-  this.dispatchChangeEvent();
+ol.control.OverviewMap.prototype.handleToggle_ = function() {
+  goog.dom.classlist.toggle(this.element, 'ol-collapsed');
+  goog.dom.setTextContent(this.labelSpan_,
+      (this.collapsed_) ? this.collapseLabel_ : this.label_);
+  this.collapsed_ = !this.collapsed_;
+
+  // manage overview map if it had not been rendered before and control
+  // is expanded
+  var ovmap = this.ovmap_;
+  if (!this.collapsed_ && !ovmap.isRendered()) {
+    ovmap.updateSize();
+    this.resetExtent_();
+    goog.events.listenOnce(ovmap, ol.MapEventType.POSTRENDER,
+        function(event) {
+          this.updateBox_();
+        },
+        false, this);
+  }
 };
 
 
 /**
- * @return {ol.Collection.<ol.layer.Base>|undefined} Collection of
- * {@link ol.layer.Layer layers} that are part of this group.
- * @observable
+ * @return {boolean} True if the widget is collapsible.
  * @api stable
  */
-ol.layer.Group.prototype.getLayers = function() {
-  return /** @type {ol.Collection.<ol.layer.Base>|undefined} */ (this.get(
-      ol.layer.GroupProperty.LAYERS));
+ol.control.OverviewMap.prototype.getCollapsible = function() {
+  return this.collapsible_;
 };
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getLayers',
-    ol.layer.Group.prototype.getLayers);
 
 
 /**
- * @param {ol.Collection.<ol.layer.Base>|undefined} layers Collection of
- * {@link ol.layer.Layer layers} that are part of this group.
- * @observable
+ * @param {boolean} collapsible True if the widget is collapsible.
  * @api stable
  */
-ol.layer.Group.prototype.setLayers = function(layers) {
-  this.set(ol.layer.GroupProperty.LAYERS, layers);
+ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) {
+  if (this.collapsible_ === collapsible) {
+    return;
+  }
+  this.collapsible_ = collapsible;
+  goog.dom.classlist.toggle(this.element, 'ol-uncollapsible');
+  if (!collapsible && this.collapsed_) {
+    this.handleToggle_();
+  }
 };
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setLayers',
-    ol.layer.Group.prototype.setLayers);
 
 
 /**
- * @inheritDoc
+ * @param {boolean} collapsed True if the widget is collapsed.
+ * @api stable
  */
-ol.layer.Group.prototype.getLayersArray = function(opt_array) {
-  var array = (goog.isDef(opt_array)) ? opt_array : [];
-  this.getLayers().forEach(function(layer) {
-    layer.getLayersArray(array);
-  });
-  return array;
+ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) {
+  if (!this.collapsible_ || this.collapsed_ === collapsed) {
+    return;
+  }
+  this.handleToggle_();
 };
 
 
 /**
- * @inheritDoc
+ * @return {boolean} True if the widget is collapsed.
+ * @api stable
  */
-ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) {
-  var states = (goog.isDef(opt_states)) ? opt_states : [];
+ol.control.OverviewMap.prototype.getCollapsed = function() {
+  return this.collapsed_;
+};
 
-  var pos = states.length;
+goog.provide('ol.control.ScaleLine');
+goog.provide('ol.control.ScaleLineProperty');
+goog.provide('ol.control.ScaleLineUnits');
 
-  this.getLayers().forEach(function(layer) {
-    layer.getLayerStatesArray(states);
-  });
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.math');
+goog.require('goog.style');
+goog.require('ol.Object');
+goog.require('ol.TransformFunction');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
+goog.require('ol.sphere.NORMAL');
 
-  var ownLayerState = this.getLayerState();
-  var i, ii, layerState;
-  for (i = pos, ii = states.length; i < ii; i++) {
-    layerState = states[i];
-    layerState.brightness = goog.math.clamp(
-        layerState.brightness + ownLayerState.brightness, -1, 1);
-    layerState.contrast *= ownLayerState.contrast;
-    layerState.hue += ownLayerState.hue;
-    layerState.opacity *= ownLayerState.opacity;
-    layerState.saturation *= ownLayerState.saturation;
-    layerState.visible = layerState.visible && ownLayerState.visible;
-    layerState.maxResolution = Math.min(
-        layerState.maxResolution, ownLayerState.maxResolution);
-    layerState.minResolution = Math.max(
-        layerState.minResolution, ownLayerState.minResolution);
-    if (goog.isDef(ownLayerState.extent) && goog.isDef(layerState.extent)) {
-      layerState.extent = ol.extent.getIntersection(
-          layerState.extent, ownLayerState.extent);
-    }
-  }
 
-  return states;
+/**
+ * @enum {string}
+ */
+ol.control.ScaleLineProperty = {
+  UNITS: 'units'
 };
 
 
 /**
- * @inheritDoc
+ * Units for the scale line. Supported values are `'degrees'`, `'imperial'`,
+ * `'nautical'`, `'metric'`, `'us'`.
+ * @enum {string}
+ * @api stable
  */
-ol.layer.Group.prototype.getSourceState = function() {
-  return ol.source.State.READY;
+ol.control.ScaleLineUnits = {
+  DEGREES: 'degrees',
+  IMPERIAL: 'imperial',
+  NAUTICAL: 'nautical',
+  METRIC: 'metric',
+  US: 'us'
 };
 
-goog.provide('ol.proj.EPSG3857');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('ol.math');
-goog.require('ol.proj');
-goog.require('ol.proj.Projection');
-goog.require('ol.proj.Units');
-
 
 
 /**
  * @classdesc
- * Projection object for web/spherical Mercator (EPSG:3857).
+ * A control displaying rough x-axis distances, calculated for the center of the
+ * viewport.
+ * No scale line will be shown when the x-axis distance cannot be calculated in
+ * the view projection (e.g. at or beyond the poles in EPSG:4326).
+ * By default the scale line will show in the bottom left portion of the map,
+ * but this can be changed by using the css selector `.ol-scale-line`.
  *
  * @constructor
- * @extends {ol.proj.Projection}
- * @param {string} code Code.
- * @private
+ * @extends {ol.control.Control}
+ * @param {olx.control.ScaleLineOptions=} opt_options Scale line options.
+ * @api stable
  */
-ol.proj.EPSG3857_ = function(code) {
+ol.control.ScaleLine = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  var className = goog.isDef(options.className) ?
+      options.className : 'ol-scale-line';
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.innerElement_ = goog.dom.createDom(goog.dom.TagName.DIV,
+      className + '-inner');
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.element_ = goog.dom.createDom(goog.dom.TagName.DIV,
+      className + ' ' + ol.css.CLASS_UNSELECTABLE, this.innerElement_);
+
+  /**
+   * @private
+   * @type {?olx.ViewState}
+   */
+  this.viewState_ = null;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.minWidth_ = goog.isDef(options.minWidth) ? options.minWidth : 64;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.renderedVisible_ = false;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.renderedWidth_ = undefined;
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.renderedHTML_ = '';
+
+  /**
+   * @private
+   * @type {?ol.TransformFunction}
+   */
+  this.toEPSG4326_ = null;
+
+  var render = goog.isDef(options.render) ?
+      options.render : ol.control.ScaleLine.render;
+
   goog.base(this, {
-    code: code,
-    units: ol.proj.Units.METERS,
-    extent: ol.proj.EPSG3857.EXTENT,
-    global: true,
-    worldExtent: ol.proj.EPSG3857.WORLD_EXTENT
+    element: this.element_,
+    render: render,
+    target: options.target
   });
-};
-goog.inherits(ol.proj.EPSG3857_, ol.proj.Projection);
 
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.control.ScaleLineProperty.UNITS),
+      this.handleUnitsChanged_, false, this);
+
+  this.setUnits(/** @type {ol.control.ScaleLineUnits} */ (options.units) ||
+      ol.control.ScaleLineUnits.METRIC);
 
-/**
- * @inheritDoc
- */
-ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) {
-  return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS);
 };
+goog.inherits(ol.control.ScaleLine, ol.control.Control);
 
 
 /**
  * @const
- * @type {number}
+ * @type {Array.<number>}
  */
-ol.proj.EPSG3857.RADIUS = 6378137;
+ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5];
 
 
 /**
- * @const
- * @type {number}
+ * @return {ol.control.ScaleLineUnits|undefined} The units to use in the scale
+ *     line.
+ * @observable
+ * @api stable
  */
-ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS;
+ol.control.ScaleLine.prototype.getUnits = function() {
+  return /** @type {ol.control.ScaleLineUnits|undefined} */ (
+      this.get(ol.control.ScaleLineProperty.UNITS));
+};
+goog.exportProperty(
+    ol.control.ScaleLine.prototype,
+    'getUnits',
+    ol.control.ScaleLine.prototype.getUnits);
 
 
 /**
- * @const
- * @type {ol.Extent}
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.ScaleLine}
+ * @api
  */
-ol.proj.EPSG3857.EXTENT = [
-  -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE,
-  ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE
-];
+ol.control.ScaleLine.render = function(mapEvent) {
+  var frameState = mapEvent.frameState;
+  if (goog.isNull(frameState)) {
+    this.viewState_ = null;
+  } else {
+    this.viewState_ = frameState.viewState;
+  }
+  this.updateElement_();
+};
 
 
 /**
- * @const
- * @type {ol.Extent}
+ * @private
  */
-ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85];
+ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() {
+  this.updateElement_();
+};
 
 
 /**
- * Lists several projection codes with the same meaning as EPSG:3857.
- *
- * @type {Array.<string>}
+ * @param {ol.control.ScaleLineUnits} units The units to use in the scale line.
+ * @observable
+ * @api stable
  */
-ol.proj.EPSG3857.CODES = [
-  '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'
-];
+ol.control.ScaleLine.prototype.setUnits = function(units) {
+  this.set(ol.control.ScaleLineProperty.UNITS, units);
+};
+goog.exportProperty(
+    ol.control.ScaleLine.prototype,
+    'setUnits',
+    ol.control.ScaleLine.prototype.setUnits);
 
 
 /**
- * Projections equal to EPSG:3857.
- *
- * @const
- * @type {Array.<ol.proj.Projection>}
+ * @private
  */
-ol.proj.EPSG3857.PROJECTIONS = goog.array.map(
-    ol.proj.EPSG3857.CODES,
-    function(code) {
-      return new ol.proj.EPSG3857_(code);
-    });
-
+ol.control.ScaleLine.prototype.updateElement_ = function() {
+  var viewState = this.viewState_;
 
-/**
- * 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 (!goog.isDef(output)) {
-    if (dimension > 2) {
-      // preserve values beyond second dimension
-      output = input.slice();
-    } else {
-      output = new Array(length);
+  if (goog.isNull(viewState)) {
+    if (this.renderedVisible_) {
+      goog.style.setElementShown(this.element_, false);
+      this.renderedVisible_ = false;
     }
+    return;
   }
-  goog.asserts.assert(output.length % dimension === 0);
-  for (var i = 0; i < length; i += dimension) {
-    output[i] = ol.proj.EPSG3857.RADIUS * Math.PI * input[i] / 180;
-    output[i + 1] = ol.proj.EPSG3857.RADIUS *
-        Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360));
-  }
-  return output;
-};
 
+  var center = viewState.center;
+  var projection = viewState.projection;
+  var pointResolution =
+      projection.getPointResolution(viewState.resolution, center);
+  var projectionUnits = projection.getUnits();
 
-/**
- * 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 (!goog.isDef(output)) {
-    if (dimension > 2) {
-      // preserve values beyond second dimension
-      output = input.slice();
-    } else {
-      output = new Array(length);
-    }
-  }
-  goog.asserts.assert(output.length % dimension === 0);
-  for (var i = 0; i < length; i += dimension) {
-    output[i] = 180 * input[i] / (ol.proj.EPSG3857.RADIUS * Math.PI);
-    output[i + 1] = 360 * Math.atan(
-        Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90;
+  var cosLatitude;
+  var units = this.getUnits();
+  if (projectionUnits == ol.proj.Units.DEGREES &&
+      (units == ol.control.ScaleLineUnits.METRIC ||
+       units == ol.control.ScaleLineUnits.IMPERIAL ||
+       units == ol.control.ScaleLineUnits.US ||
+       units == ol.control.ScaleLineUnits.NAUTICAL)) {
+
+    // Convert pointResolution from degrees to meters
+    this.toEPSG4326_ = null;
+    cosLatitude = Math.cos(goog.math.toRadians(center[1]));
+    pointResolution *= Math.PI * cosLatitude * ol.sphere.NORMAL.radius / 180;
+    projectionUnits = ol.proj.Units.METERS;
+
+  } else if ((projectionUnits == ol.proj.Units.FEET ||
+      projectionUnits == ol.proj.Units.METERS) &&
+      units == ol.control.ScaleLineUnits.DEGREES) {
+
+    // Convert pointResolution from meters or feet to degrees
+    if (goog.isNull(this.toEPSG4326_)) {
+      this.toEPSG4326_ = ol.proj.getTransformFromProjections(
+          projection, ol.proj.get('EPSG:4326'));
+    }
+    cosLatitude = Math.cos(goog.math.toRadians(this.toEPSG4326_(center)[1]));
+    var radius = ol.sphere.NORMAL.radius;
+    if (projectionUnits == ol.proj.Units.FEET) {
+      radius /= 0.3048;
+    }
+    pointResolution *= 180 / (Math.PI * cosLatitude * radius);
+    projectionUnits = ol.proj.Units.DEGREES;
+
+  } else {
+    this.toEPSG4326_ = null;
   }
-  return output;
-};
 
-goog.provide('ol.proj.EPSG4326');
+  goog.asserts.assert(
+      ((units == ol.control.ScaleLineUnits.METRIC ||
+        units == ol.control.ScaleLineUnits.IMPERIAL ||
+        units == ol.control.ScaleLineUnits.US ||
+        units == ol.control.ScaleLineUnits.NAUTICAL) &&
+       projectionUnits == ol.proj.Units.METERS) ||
+      (units == ol.control.ScaleLineUnits.DEGREES &&
+       projectionUnits == ol.proj.Units.DEGREES));
 
-goog.require('ol.proj');
-goog.require('ol.proj.Projection');
-goog.require('ol.proj.Units');
+  var nominalCount = this.minWidth_ * pointResolution;
+  var suffix = '';
+  if (units == ol.control.ScaleLineUnits.DEGREES) {
+    if (nominalCount < 1 / 60) {
+      suffix = '\u2033'; // seconds
+      pointResolution *= 3600;
+    } else if (nominalCount < 1) {
+      suffix = '\u2032'; // minutes
+      pointResolution *= 60;
+    } else {
+      suffix = '\u00b0'; // degrees
+    }
+  } else if (units == ol.control.ScaleLineUnits.IMPERIAL) {
+    if (nominalCount < 0.9144) {
+      suffix = 'in';
+      pointResolution /= 0.0254;
+    } else if (nominalCount < 1609.344) {
+      suffix = 'ft';
+      pointResolution /= 0.3048;
+    } else {
+      suffix = 'mi';
+      pointResolution /= 1609.344;
+    }
+  } else if (units == ol.control.ScaleLineUnits.NAUTICAL) {
+    pointResolution /= 1852;
+    suffix = 'nm';
+  } else if (units == ol.control.ScaleLineUnits.METRIC) {
+    if (nominalCount < 1) {
+      suffix = 'mm';
+      pointResolution *= 1000;
+    } else if (nominalCount < 1000) {
+      suffix = 'm';
+    } else {
+      suffix = 'km';
+      pointResolution /= 1000;
+    }
+  } else if (units == ol.control.ScaleLineUnits.US) {
+    if (nominalCount < 0.9144) {
+      suffix = 'in';
+      pointResolution *= 39.37;
+    } else if (nominalCount < 1609.344) {
+      suffix = 'ft';
+      pointResolution /= 0.30480061;
+    } else {
+      suffix = 'mi';
+      pointResolution /= 1609.3472;
+    }
+  } else {
+    goog.asserts.fail();
+  }
 
+  var i = 3 * Math.floor(
+      Math.log(this.minWidth_ * pointResolution) / Math.log(10));
+  var count, width;
+  while (true) {
+    count = ol.control.ScaleLine.LEADING_DIGITS[i % 3] *
+        Math.pow(10, Math.floor(i / 3));
+    width = Math.round(count / pointResolution);
+    if (isNaN(width)) {
+      goog.style.setElementShown(this.element_, false);
+      this.renderedVisible_ = false;
+      return;
+    } else if (width >= this.minWidth_) {
+      break;
+    }
+    ++i;
+  }
 
+  var html = count + suffix;
+  if (this.renderedHTML_ != html) {
+    this.innerElement_.innerHTML = html;
+    this.renderedHTML_ = html;
+  }
 
-/**
- * @classdesc
- * Projection object for WGS84 geographic coordinates (EPSG:4326).
- *
- * Note that OpenLayers does not strictly comply with the EPSG definition.
- * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x).
- * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates.
- *
- * @constructor
- * @extends {ol.proj.Projection}
- * @param {string} code Code.
- * @param {string=} opt_axisOrientation Axis orientation.
- * @private
- */
-ol.proj.EPSG4326_ = function(code, opt_axisOrientation) {
-  goog.base(this, {
-    code: code,
-    units: ol.proj.Units.DEGREES,
-    extent: ol.proj.EPSG4326.EXTENT,
-    axisOrientation: opt_axisOrientation,
-    global: true,
-    worldExtent: ol.proj.EPSG4326.EXTENT
-  });
-};
-goog.inherits(ol.proj.EPSG4326_, ol.proj.Projection);
+  if (this.renderedWidth_ != width) {
+    this.innerElement_.style.width = width + 'px';
+    this.renderedWidth_ = width;
+  }
 
+  if (!this.renderedVisible_) {
+    goog.style.setElementShown(this.element_, true);
+    this.renderedVisible_ = true;
+  }
 
-/**
- * @inheritDoc
- */
-ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) {
-  return resolution;
 };
 
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Extent of the EPSG:4326 projection which is the whole world.
+ * @fileoverview Class to create objects which want to handle multiple events
+ * and have their listeners easily cleaned up via a dispose method.
  *
- * @const
- * @type {ol.Extent}
- */
-ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90];
-
-
-/**
- * Projections equal to EPSG:4326.
+ * Example:
+ * <pre>
+ * function Something() {
+ *   Something.base(this);
+ *
+ *   ... set up object ...
+ *
+ *   // Add event listeners
+ *   this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);
+ *   this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);
+ *   this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);
+ *   this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);
+ *   this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);
+ * }
+ * goog.inherits(Something, goog.events.EventHandler);
+ *
+ * Something.prototype.disposeInternal = function() {
+ *   Something.base(this, 'disposeInternal');
+ *   goog.dom.removeNode(this.container);
+ * };
+ *
+ *
+ * // Then elsewhere:
+ *
+ * var activeSomething = null;
+ * function openSomething() {
+ *   activeSomething = new Something();
+ * }
+ *
+ * function closeSomething() {
+ *   if (activeSomething) {
+ *     activeSomething.dispose();  // Remove event listeners
+ *     activeSomething = null;
+ *   }
+ * }
+ * </pre>
  *
- * @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: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('goog.events.EventHandler');
 
-goog.provide('ol.layer.Image');
+goog.require('goog.Disposable');
+goog.require('goog.events');
+goog.require('goog.object');
 
-goog.require('ol.layer.Layer');
+goog.forwardDeclare('goog.events.EventWrapper');
 
 
 
 /**
- * @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.
- *
+ * Super class for objects that want to easily manage a number of event
+ * listeners.  It allows a short cut to listen and also provides a quick way
+ * to remove all events listeners belonging to this object.
+ * @param {SCOPE=} opt_scope Object in whose scope to call the listeners.
  * @constructor
- * @extends {ol.layer.Layer}
- * @fires ol.render.Event
- * @param {olx.layer.ImageOptions} options Layer options.
- * @api stable
+ * @extends {goog.Disposable}
+ * @template SCOPE
  */
-ol.layer.Image = function(options) {
-  goog.base(this,  /** @type {olx.layer.LayerOptions} */ (options));
+goog.events.EventHandler = function(opt_scope) {
+  goog.Disposable.call(this);
+  // TODO(mknichel): Rename this to this.scope_ and fix the classes in google3
+  // that access this private variable. :(
+  this.handler_ = opt_scope;
+
+  /**
+   * Keys for events that are being listened to.
+   * @type {!Object<!goog.events.Key>}
+   * @private
+   */
+  this.keys_ = {};
 };
-goog.inherits(ol.layer.Image, ol.layer.Layer);
+goog.inherits(goog.events.EventHandler, goog.Disposable);
 
 
 /**
- * @function
- * @return {ol.source.Image} Source.
- * @api stable
+ * Utility array used to unify the cases of listening for an array of types
+ * and listening for a single event, without using recursion or allocating
+ * an array each time.
+ * @type {!Array<string>}
+ * @const
+ * @private
  */
-ol.layer.Image.prototype.getSource;
-
-goog.provide('ol.layer.Tile');
-
-goog.require('ol.layer.Layer');
+goog.events.EventHandler.typeArray_ = [];
 
 
 /**
- * @enum {string}
+ * Listen to an event on a Listenable.  If the function is omitted then the
+ * EventHandler's handleEvent method will be used.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type to listen for or array of event types.
+ * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
+ *     opt_fn Optional callback function to be used as the listener or an object
+ *     with handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @template EVENTOBJ
  */
-ol.layer.TileProperty = {
-  PRELOAD: 'preload',
-  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
+goog.events.EventHandler.prototype.listen = function(
+    src, type, opt_fn, opt_capture) {
+  return this.listen_(src, type, opt_fn, opt_capture);
 };
 
 
-
 /**
- * @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} options Tile layer options.
- * @api stable
+ * Listen to an event on a Listenable.  If the function is omitted then the
+ * EventHandler's handleEvent method will be used.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type to listen for or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
+ *     null|undefined} fn Optional callback function to be used as the
+ *     listener or an object with handleEvent function.
+ * @param {boolean|undefined} capture Optional whether to use capture phase.
+ * @param {T} scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @template T,EVENTOBJ
  */
-ol.layer.Tile = function(options) {
-  goog.base(this,  /** @type {olx.layer.LayerOptions} */ (options));
+goog.events.EventHandler.prototype.listenWithScope = function(
+    src, type, fn, capture, scope) {
+  // TODO(mknichel): Deprecate this function.
+  return this.listen_(src, type, fn, capture, scope);
 };
-goog.inherits(ol.layer.Tile, ol.layer.Layer);
 
 
 /**
- * @return {number|undefined} The level to preload tiles up to.
- * @observable
- * @api
+ * Listen to an event on a Listenable.  If the function is omitted then the
+ * EventHandler's handleEvent method will be used.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type to listen for or array of event types.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ *     Optional callback function to be used as the listener or an object with
+ *     handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @param {Object=} opt_scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @template EVENTOBJ
+ * @private
  */
-ol.layer.Tile.prototype.getPreload = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.TileProperty.PRELOAD));
-};
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getPreload',
-    ol.layer.Tile.prototype.getPreload);
+goog.events.EventHandler.prototype.listen_ = function(src, type, opt_fn,
+                                                      opt_capture,
+                                                      opt_scope) {
+  if (!goog.isArray(type)) {
+    if (type) {
+      goog.events.EventHandler.typeArray_[0] = type.toString();
+    }
+    type = goog.events.EventHandler.typeArray_;
+  }
+  for (var i = 0; i < type.length; i++) {
+    var listenerObj = goog.events.listen(
+        src, type[i], opt_fn || this.handleEvent,
+        opt_capture || false,
+        opt_scope || this.handler_ || this);
 
+    if (!listenerObj) {
+      // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
+      // (goog.events.CaptureSimulationMode) in IE8-, it will return null
+      // value.
+      return this;
+    }
 
-/**
- * @function
- * @return {ol.source.Tile} Source.
- * @api stable
- */
-ol.layer.Tile.prototype.getSource;
+    var key = listenerObj.key;
+    this.keys_[key] = listenerObj;
+  }
+
+  return this;
+};
 
 
 /**
- * @param {number} preload The level to preload tiles up to.
- * @observable
- * @api
+ * Listen to an event on a Listenable.  If the function is omitted, then the
+ * EventHandler's handleEvent method will be used. After the event has fired the
+ * event listener is removed from the target. If an array of event types is
+ * provided, each event type will be listened to once.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type to listen for or array of event types.
+ * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ *    Optional callback function to be used as the listener or an object with
+ *    handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @template EVENTOBJ
  */
-ol.layer.Tile.prototype.setPreload = function(preload) {
-  this.set(ol.layer.TileProperty.PRELOAD, preload);
+goog.events.EventHandler.prototype.listenOnce = function(
+    src, type, opt_fn, opt_capture) {
+  return this.listenOnce_(src, type, opt_fn, opt_capture);
 };
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setPreload',
-    ol.layer.Tile.prototype.setPreload);
 
 
 /**
- * @return {boolean|undefined} Use interim tiles on error.
- * @observable
- * @api
+ * Listen to an event on a Listenable.  If the function is omitted, then the
+ * EventHandler's handleEvent method will be used. After the event has fired the
+ * event listener is removed from the target. If an array of event types is
+ * provided, each event type will be listened to once.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type to listen for or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
+ *     null|undefined} fn Optional callback function to be used as the
+ *     listener or an object with handleEvent function.
+ * @param {boolean|undefined} capture Optional whether to use capture phase.
+ * @param {T} scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @template T,EVENTOBJ
  */
-ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
-  return /** @type {boolean|undefined} */ (
-      this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
+goog.events.EventHandler.prototype.listenOnceWithScope = function(
+    src, type, fn, capture, scope) {
+  // TODO(mknichel): Deprecate this function.
+  return this.listenOnce_(src, type, fn, capture, scope);
 };
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getUseInterimTilesOnError',
-    ol.layer.Tile.prototype.getUseInterimTilesOnError);
 
 
 /**
- * @param {boolean|undefined} useInterimTilesOnError Use interim tiles on error.
- * @observable
- * @api
+ * Listen to an event on a Listenable.  If the function is omitted, then the
+ * EventHandler's handleEvent method will be used. After the event has fired
+ * the event listener is removed from the target. If an array of event types is
+ * provided, each event type will be listened to once.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type to listen for or array of event types.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ *    Optional callback function to be used as the listener or an object with
+ *    handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @param {Object=} opt_scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @template EVENTOBJ
+ * @private
  */
-ol.layer.Tile.prototype.setUseInterimTilesOnError =
-    function(useInterimTilesOnError) {
-  this.set(
-      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
-};
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setUseInterimTilesOnError',
-    ol.layer.Tile.prototype.setUseInterimTilesOnError);
+goog.events.EventHandler.prototype.listenOnce_ = function(
+    src, type, opt_fn, opt_capture, opt_scope) {
+  if (goog.isArray(type)) {
+    for (var i = 0; i < type.length; i++) {
+      this.listenOnce_(src, type[i], opt_fn, opt_capture, opt_scope);
+    }
+  } else {
+    var listenerObj = goog.events.listenOnce(
+        src, type, opt_fn || this.handleEvent, opt_capture,
+        opt_scope || this.handler_ || this);
+    if (!listenerObj) {
+      // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
+      // (goog.events.CaptureSimulationMode) in IE8-, it will return null
+      // value.
+      return this;
+    }
 
-goog.provide('ol.layer.Vector');
+    var key = listenerObj.key;
+    this.keys_[key] = listenerObj;
+  }
 
-goog.require('goog.object');
-goog.require('ol.layer.Layer');
-goog.require('ol.style.Style');
+  return this;
+};
 
 
 /**
- * @enum {string}
- */
-ol.layer.VectorProperty = {
-  RENDER_ORDER: 'renderOrder'
-};
-
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.EventTarget}. A listener can
+ * only be added once to an object.
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The node to listen to
+ *     events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener
+ *     Callback method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ *     false).
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ */
+goog.events.EventHandler.prototype.listenWithWrapper = function(
+    src, wrapper, listener, opt_capt) {
+  // TODO(mknichel): Remove the opt_scope from this function and then
+  // templatize it.
+  return this.listenWithWrapper_(src, wrapper, listener, opt_capt);
+};
 
 
 /**
- * @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.
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.EventTarget}. A listener can
+ * only be added once to an object.
  *
- * @constructor
- * @extends {ol.layer.Layer}
- * @fires ol.render.Event
- * @param {olx.layer.VectorOptions=} opt_options Options.
- * @api stable
+ * @param {EventTarget|goog.events.EventTarget} src The node to listen to
+ *     events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null}
+ *     listener Optional callback function to be used as the
+ *     listener or an object with handleEvent function.
+ * @param {boolean|undefined} capture Optional whether to use capture phase.
+ * @param {T} scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @template T
  */
-ol.layer.Vector = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ?
-      opt_options : /** @type {olx.layer.VectorOptions} */ ({});
-
-  var baseOptions = goog.object.clone(options);
-
-  delete baseOptions.style;
-  goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
-
-  /**
-   * User provided style.
-   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
-   * @private
-   */
-  this.style_ = null;
-
-  /**
-   * Style function for use within the library.
-   * @type {ol.style.StyleFunction|undefined}
-   * @private
-   */
-  this.styleFunction_ = undefined;
+goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(
+    src, wrapper, listener, capture, scope) {
+  // TODO(mknichel): Deprecate this function.
+  return this.listenWithWrapper_(src, wrapper, listener, capture, scope);
+};
 
-  this.setStyle(options.style);
 
+/**
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.EventTarget}. A listener can
+ * only be added once to an object.
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The node to listen to
+ *     events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
+ *     method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ *     false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for
+ *     chaining of calls.
+ * @private
+ */
+goog.events.EventHandler.prototype.listenWithWrapper_ = function(
+    src, wrapper, listener, opt_capt, opt_scope) {
+  wrapper.listen(src, listener, opt_capt, opt_scope || this.handler_ || this,
+                 this);
+  return this;
 };
-goog.inherits(ol.layer.Vector, ol.layer.Layer);
 
 
 /**
- * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
- *     order.
+ * @return {number} Number of listeners registered by this handler.
  */
-ol.layer.Vector.prototype.getRenderOrder = function() {
-  return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ (
-      this.get(ol.layer.VectorProperty.RENDER_ORDER));
+goog.events.EventHandler.prototype.getListenerCount = function() {
+  var count = 0;
+  for (var key in this.keys_) {
+    if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {
+      count++;
+    }
+  }
+  return count;
 };
 
 
 /**
- * @function
- * @return {ol.source.Vector} Source.
- * @api stable
+ * Unlistens on an event.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array<string>|
+ *     !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
+ *     type Event type or array of event types to unlisten to.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ *     Optional callback function to be used as the listener or an object with
+ *     handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @param {Object=} opt_scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler} This object, allowing for chaining of
+ *     calls.
+ * @template EVENTOBJ
  */
-ol.layer.Vector.prototype.getSource;
+goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn,
+                                                       opt_capture,
+                                                       opt_scope) {
+  if (goog.isArray(type)) {
+    for (var i = 0; i < type.length; i++) {
+      this.unlisten(src, type[i], opt_fn, opt_capture, opt_scope);
+    }
+  } else {
+    var listener = goog.events.getListener(src, type,
+        opt_fn || this.handleEvent,
+        opt_capture, opt_scope || this.handler_ || this);
+
+    if (listener) {
+      goog.events.unlistenByKey(listener);
+      delete this.keys_[listener.key];
+    }
+  }
+
+  return this;
+};
 
 
 /**
- * Get the style for features.  This returns whatever was passed to the `style`
- * option at construction or to the `setStyle` method.
- * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
- *     Layer style.
- * @api stable
+ * Removes an event listener which was added with listenWithWrapper().
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The target to stop
+ *     listening to events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
+ *     listener function to remove.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ *     whether the listener is fired during the capture or bubble phase of the
+ *     event.
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @return {!goog.events.EventHandler} This object, allowing for chaining of
+ *     calls.
  */
-ol.layer.Vector.prototype.getStyle = function() {
-  return this.style_;
+goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper,
+    listener, opt_capt, opt_scope) {
+  wrapper.unlisten(src, listener, opt_capt,
+                   opt_scope || this.handler_ || this, this);
+  return this;
 };
 
 
 /**
- * Get the style function.
- * @return {ol.style.StyleFunction|undefined} Layer style function.
- * @api stable
+ * Unlistens to all events.
  */
-ol.layer.Vector.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
+goog.events.EventHandler.prototype.removeAll = function() {
+  goog.object.forEach(this.keys_, goog.events.unlistenByKey);
+  this.keys_ = {};
 };
 
 
 /**
- * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
- *     Render order.
+ * Disposes of this EventHandler and removes all listeners that it registered.
+ * @override
+ * @protected
  */
-ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
-  this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder);
+goog.events.EventHandler.prototype.disposeInternal = function() {
+  goog.events.EventHandler.superClass_.disposeInternal.call(this);
+  this.removeAll();
 };
 
 
 /**
- * Set the style for features.  This can be a single style object, an array
- * of styles, or a function that takes a feature and resolution and returns
- * an array of styles. If it is `undefined` the default style is used. If
- * it is `null` the layer has no style (a `null` style), so only features
- * that have their own styles will be rendered in the layer. See
- * {@link ol.style} for information on the default style.
- * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined}
- *     style Layer style.
- * @api stable
+ * Default event handler
+ * @param {goog.events.Event} e Event object.
  */
-ol.layer.Vector.prototype.setStyle = function(style) {
-  this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction;
-  this.styleFunction_ = goog.isNull(style) ?
-      undefined : ol.style.createStyleFunction(this.style_);
-  this.dispatchChangeEvent();
+goog.events.EventHandler.prototype.handleEvent = function(e) {
+  throw Error('EventHandler.handleEvent not implemented');
 };
 
-// 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?
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-goog.provide('ol.render.canvas.Immediate');
+/**
+ * @fileoverview Bidi utility functions.
+ *
+ */
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('goog.vec.Mat4');
-goog.require('ol.color');
-goog.require('ol.extent');
-goog.require('ol.geom.flat.transform');
-goog.require('ol.has');
-goog.require('ol.render.IVectorContext');
-goog.require('ol.render.canvas');
-goog.require('ol.vec.Mat4');
+goog.provide('goog.style.bidi');
 
+goog.require('goog.dom');
+goog.require('goog.style');
+goog.require('goog.userAgent');
 
 
 /**
- * @classdesc
- * A concrete subclass of {@link ol.render.IVectorContext} 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
- * @implements {ol.render.IVectorContext}
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Extent} extent Extent.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {number} viewRotation View rotation.
- * @struct
+ * Returns the normalized scrollLeft position for a scrolled element.
+ * @param {Element} element The scrolled element.
+ * @return {number} The number of pixels the element is scrolled. 0 indicates
+ *     that the element is not scrolled at all (which, in general, is the
+ *     left-most position in ltr and the right-most position in rtl).
  */
-ol.render.canvas.Immediate =
-    function(context, pixelRatio, extent, transform, viewRotation) {
+goog.style.bidi.getScrollLeft = function(element) {
+  var isRtl = goog.style.isRightToLeft(element);
+  if (isRtl && goog.userAgent.GECKO) {
+    // ScrollLeft starts at 0 and then goes negative as the element is scrolled
+    // towards the left.
+    return -element.scrollLeft;
+  } else if (isRtl &&
+             !(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) {
+    // ScrollLeft starts at the maximum positive value and decreases towards
+    // 0 as the element is scrolled towards the left. However, for overflow
+    // visible, there is no scrollLeft and the value always stays correctly at 0
+    var overflowX = goog.style.getComputedOverflowX(element);
+    if (overflowX == 'visible') {
+      return element.scrollLeft;
+    } else {
+      return element.scrollWidth - element.clientWidth - element.scrollLeft;
+    }
+  }
+  // ScrollLeft behavior is identical in rtl and ltr, it starts at 0 and
+  // increases as the element is scrolled away from the start.
+  return element.scrollLeft;
+};
 
-  /**
-   * @private
-   * @type {Object.<string,
-   *        Array.<function(ol.render.canvas.Immediate)>>}
-   */
-  this.callbacksByZIndex_ = {};
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = context;
+/**
+ * Returns the "offsetStart" of an element, analagous to offsetLeft but
+ * normalized for right-to-left environments and various browser
+ * inconsistencies. This value returned can always be passed to setScrollOffset
+ * to scroll to an element's left edge in a left-to-right offsetParent or
+ * right edge in a right-to-left offsetParent.
+ *
+ * For example, here offsetStart is 10px in an LTR environment and 5px in RTL:
+ *
+ * <pre>
+ * |          xxxxxxxxxx     |
+ *  ^^^^^^^^^^   ^^^^   ^^^^^
+ *     10px      elem    5px
+ * </pre>
+ *
+ * If an element is positioned before the start of its offsetParent, the
+ * startOffset may be negative.  This can be used with setScrollOffset to
+ * reliably scroll to an element:
+ *
+ * <pre>
+ * var scrollOffset = goog.style.bidi.getOffsetStart(element);
+ * goog.style.bidi.setScrollOffset(element.offsetParent, scrollOffset);
+ * </pre>
+ *
+ * @see setScrollOffset
+ *
+ * @param {Element} element The element for which we need to determine the
+ *     offsetStart position.
+ * @return {number} The offsetStart for that element.
+ */
+goog.style.bidi.getOffsetStart = function(element) {
+  var offsetLeftForReal = element.offsetLeft;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = pixelRatio;
+  // The element might not have an offsetParent.
+  // For example, the node might not be attached to the DOM tree,
+  // and position:fixed children do not have an offset parent.
+  // Just try to do the best we can with what we have.
+  var bestParent = element.offsetParent;
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = extent;
+  if (!bestParent && goog.style.getComputedPosition(element) == 'fixed') {
+    bestParent = goog.dom.getOwnerDocument(element).documentElement;
+  }
 
-  /**
-   * @private
-   * @type {goog.vec.Mat4.Number}
-   */
-  this.transform_ = transform;
+  // Just give up in this case.
+  if (!bestParent) {
+    return offsetLeftForReal;
+  }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.viewRotation_ = viewRotation;
+  if (goog.userAgent.GECKO) {
+    // When calculating an element's offsetLeft, Firefox erroneously subtracts
+    // the border width from the actual distance.  So we need to add it back.
+    var borderWidths = goog.style.getBorderBox(bestParent);
+    offsetLeftForReal += borderWidths.left;
+  } else if (goog.userAgent.isDocumentModeOrHigher(8) &&
+             !goog.userAgent.isDocumentModeOrHigher(9)) {
+    // When calculating an element's offsetLeft, IE8/9-Standards Mode
+    // erroneously adds the border width to the actual distance.  So we need to
+    // subtract it.
+    var borderWidths = goog.style.getBorderBox(bestParent);
+    offsetLeftForReal -= borderWidths.left;
+  }
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.FillState}
-   */
-  this.contextFillState_ = null;
+  if (goog.style.isRightToLeft(bestParent)) {
+    // Right edge of the element relative to the left edge of its parent.
+    var elementRightOffset = offsetLeftForReal + element.offsetWidth;
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.StrokeState}
-   */
-  this.contextStrokeState_ = null;
+    // Distance from the parent's right edge to the element's right edge.
+    return bestParent.clientWidth - elementRightOffset;
+  }
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.TextState}
-   */
-  this.contextTextState_ = null;
+  return offsetLeftForReal;
+};
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.FillState}
-   */
-  this.fillState_ = null;
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.StrokeState}
-   */
-  this.strokeState_ = null;
+/**
+ * Sets the element's scrollLeft attribute so it is correctly scrolled by
+ * offsetStart pixels.  This takes into account whether the element is RTL and
+ * the nuances of different browsers.  To scroll to the "beginning" of an
+ * element use getOffsetStart to obtain the element's offsetStart value and then
+ * pass the value to setScrollOffset.
+ * @see getOffsetStart
+ * @param {Element} element The element to set scrollLeft on.
+ * @param {number} offsetStart The number of pixels to scroll the element.
+ *     If this value is < 0, 0 is used.
+ */
+goog.style.bidi.setScrollOffset = function(element, offsetStart) {
+  offsetStart = Math.max(offsetStart, 0);
+  // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to
+  // the number of pixels to scroll.
+  // Otherwise, in RTL, we need to account for different browser behavior.
+  if (!goog.style.isRightToLeft(element)) {
+    element.scrollLeft = offsetStart;
+  } else if (goog.userAgent.GECKO) {
+    // Negative scroll-left positions in RTL.
+    element.scrollLeft = -offsetStart;
+  } else if (!(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) {
+    // Take the current scrollLeft value and move to the right by the
+    // offsetStart to get to the left edge of the element, and then by
+    // the clientWidth of the element to get to the right edge.
+    element.scrollLeft =
+        element.scrollWidth - offsetStart - element.clientWidth;
+  } else {
+    element.scrollLeft = offsetStart;
+  }
+};
 
-  /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
-   */
-  this.image_ = null;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageAnchorX_ = 0;
+/**
+ * Sets the element's left style attribute in LTR or right style attribute in
+ * RTL.  Also clears the left attribute in RTL and the right attribute in LTR.
+ * @param {Element} elem The element to position.
+ * @param {number} left The left position in LTR; will be set as right in RTL.
+ * @param {?number} top The top position.  If null only the left/right is set.
+ * @param {boolean} isRtl Whether we are in RTL mode.
+ */
+goog.style.bidi.setPosition = function(elem, left, top, isRtl) {
+  if (!goog.isNull(top)) {
+    elem.style.top = top + 'px';
+  }
+  if (isRtl) {
+    elem.style.right = left + 'px';
+    elem.style.left = '';
+  } else {
+    elem.style.left = left + 'px';
+    elem.style.right = '';
+  }
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageAnchorY_ = 0;
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageHeight_ = 0;
+/**
+ * @fileoverview Drag Utilities.
+ *
+ * Provides extensible functionality for drag & drop behaviour.
+ *
+ * @see ../demos/drag.html
+ * @see ../demos/dragger.html
+ */
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageOpacity_ = 0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageOriginX_ = 0;
+goog.provide('goog.fx.DragEvent');
+goog.provide('goog.fx.Dragger');
+goog.provide('goog.fx.Dragger.EventType');
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageOriginY_ = 0;
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Rect');
+goog.require('goog.style');
+goog.require('goog.style.bidi');
+goog.require('goog.userAgent');
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.imageRotateWithView_ = false;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageRotation_ = 0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageScale_ = 0;
+/**
+ * A class that allows mouse or touch-based dragging (moving) of an element
+ *
+ * @param {Element} target The element that will be dragged.
+ * @param {Element=} opt_handle An optional handle to control the drag, if null
+ *     the target is used.
+ * @param {goog.math.Rect=} opt_limits Object containing left, top, width,
+ *     and height.
+ *
+ * @extends {goog.events.EventTarget}
+ * @constructor
+ */
+goog.fx.Dragger = function(target, opt_handle, opt_limits) {
+  goog.events.EventTarget.call(this);
+  this.target = target;
+  this.handle = opt_handle || target;
+  this.limits = opt_limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.imageSnapToPixel_ = false;
+  this.document_ = goog.dom.getOwnerDocument(target);
+  this.eventHandler_ = new goog.events.EventHandler(this);
+  this.registerDisposable(this.eventHandler_);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageWidth_ = 0;
+  // Add listener. Do not use the event handler here since the event handler is
+  // used for listeners added and removed during the drag operation.
+  goog.events.listen(this.handle,
+      [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN],
+      this.startDrag, false, this);
+};
+goog.inherits(goog.fx.Dragger, goog.events.EventTarget);
+// Dragger is meant to be extended, but defines most properties on its
+// prototype, thus making it unsuitable for sealing.
+goog.tagUnsealableClass(goog.fx.Dragger);
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.text_ = '';
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetX_ = 0;
+/**
+ * Whether setCapture is supported by the browser.
+ * @type {boolean}
+ * @private
+ */
+goog.fx.Dragger.HAS_SET_CAPTURE_ =
+    // IE and Gecko after 1.9.3 has setCapture
+    // WebKit does not yet: https://bugs.webkit.org/show_bug.cgi?id=27330
+    goog.userAgent.IE ||
+    goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.3');
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetY_ = 0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textRotation_ = 0;
+/**
+ * Creates copy of node being dragged.  This is a utility function to be used
+ * wherever it is inappropriate for the original source to follow the mouse
+ * cursor itself.
+ *
+ * @param {Element} sourceEl Element to copy.
+ * @return {!Element} The clone of {@code sourceEl}.
+ */
+goog.fx.Dragger.cloneNode = function(sourceEl) {
+  var clonedEl = /** @type {Element} */ (sourceEl.cloneNode(true)),
+      origTexts = sourceEl.getElementsByTagName('textarea'),
+      dragTexts = clonedEl.getElementsByTagName('textarea');
+  // Cloning does not copy the current value of textarea elements, so correct
+  // this manually.
+  for (var i = 0; i < origTexts.length; i++) {
+    dragTexts[i].value = origTexts[i].value;
+  }
+  switch (sourceEl.tagName.toLowerCase()) {
+    case 'tr':
+      return goog.dom.createDom(
+          'table', null, goog.dom.createDom('tbody', null, clonedEl));
+    case 'td':
+    case 'th':
+      return goog.dom.createDom(
+          'table', null, goog.dom.createDom('tbody', null, goog.dom.createDom(
+          'tr', null, clonedEl)));
+    case 'textarea':
+      clonedEl.value = sourceEl.value;
+    default:
+      return clonedEl;
+  }
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textScale_ = 0;
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.FillState}
-   */
-  this.textFillState_ = null;
+/**
+ * Constants for event names.
+ * @enum {string}
+ */
+goog.fx.Dragger.EventType = {
+  // The drag action was canceled before the START event. Possible reasons:
+  // disabled dragger, dragging with the right mouse button or releasing the
+  // button before reaching the hysteresis distance.
+  EARLY_CANCEL: 'earlycancel',
+  START: 'start',
+  BEFOREDRAG: 'beforedrag',
+  DRAG: 'drag',
+  END: 'end'
+};
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.StrokeState}
-   */
-  this.textStrokeState_ = null;
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.TextState}
-   */
-  this.textState_ = null;
+/**
+ * Reference to drag target element.
+ * @type {Element}
+ */
+goog.fx.Dragger.prototype.target;
 
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.pixelCoordinates_ = [];
 
-  /**
-   * @private
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.tmpLocalTransform_ = goog.vec.Mat4.createNumber();
+/**
+ * Reference to the handler that initiates the drag.
+ * @type {Element}
+ */
+goog.fx.Dragger.prototype.handle;
 
-};
+
+/**
+ * Object representing the limits of the drag region.
+ * @type {goog.math.Rect}
+ */
+goog.fx.Dragger.prototype.limits;
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
+ * Whether the element is rendered right-to-left. We initialize this lazily.
+ * @type {boolean|undefined}}
  * @private
  */
-ol.render.canvas.Immediate.prototype.drawImages_ =
-    function(flatCoordinates, offset, end, stride) {
-  if (goog.isNull(this.image_)) {
-    return;
-  }
-  goog.asserts.assert(offset === 0);
-  goog.asserts.assert(end == flatCoordinates.length);
-  var pixelCoordinates = ol.geom.flat.transform.transform2D(
-      flatCoordinates, offset, end, 2, this.transform_,
-      this.pixelCoordinates_);
-  var context = this.context_;
-  var localTransform = this.tmpLocalTransform_;
-  var alpha = context.globalAlpha;
-  if (this.imageOpacity_ != 1) {
-    context.globalAlpha = alpha * this.imageOpacity_;
-  }
-  var rotation = this.imageRotation_;
-  if (this.imageRotateWithView_) {
-    rotation += this.viewRotation_;
-  }
-  var i, ii;
-  for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) {
-    var x = pixelCoordinates[i] - this.imageAnchorX_;
-    var y = pixelCoordinates[i + 1] - this.imageAnchorY_;
-    if (this.imageSnapToPixel_) {
-      x = (x + 0.5) | 0;
-      y = (y + 0.5) | 0;
-    }
-    if (rotation !== 0 || this.imageScale_ != 1) {
-      var centerX = x + this.imageAnchorX_;
-      var centerY = y + this.imageAnchorY_;
-      ol.vec.Mat4.makeTransform2D(localTransform,
-          centerX, centerY, this.imageScale_, this.imageScale_,
-          rotation, -centerX, -centerY);
-      context.setTransform(
-          goog.vec.Mat4.getElement(localTransform, 0, 0),
-          goog.vec.Mat4.getElement(localTransform, 1, 0),
-          goog.vec.Mat4.getElement(localTransform, 0, 1),
-          goog.vec.Mat4.getElement(localTransform, 1, 1),
-          goog.vec.Mat4.getElement(localTransform, 0, 3),
-          goog.vec.Mat4.getElement(localTransform, 1, 3));
-    }
-    context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_,
-        this.imageWidth_, this.imageHeight_, x, y,
-        this.imageWidth_, this.imageHeight_);
-  }
-  if (rotation !== 0 || this.imageScale_ != 1) {
-    context.setTransform(1, 0, 0, 1, 0, 0);
-  }
-  if (this.imageOpacity_ != 1) {
-    context.globalAlpha = alpha;
-  }
-};
+goog.fx.Dragger.prototype.rightToLeft_;
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @private
+ * Current x position of mouse or touch relative to viewport.
+ * @type {number}
  */
-ol.render.canvas.Immediate.prototype.drawText_ =
-    function(flatCoordinates, offset, end, stride) {
-  if (goog.isNull(this.textState_) || this.text_ === '') {
-    return;
-  }
-  if (!goog.isNull(this.textFillState_)) {
-    this.setContextFillState_(this.textFillState_);
-  }
-  if (!goog.isNull(this.textStrokeState_)) {
-    this.setContextStrokeState_(this.textStrokeState_);
-  }
-  this.setContextTextState_(this.textState_);
-  goog.asserts.assert(offset === 0);
-  goog.asserts.assert(end == flatCoordinates.length);
-  var pixelCoordinates = ol.geom.flat.transform.transform2D(
-      flatCoordinates, offset, end, stride, this.transform_,
-      this.pixelCoordinates_);
-  var context = this.context_;
-  for (; offset < end; offset += stride) {
-    var x = pixelCoordinates[offset] + this.textOffsetX_;
-    var y = pixelCoordinates[offset + 1] + this.textOffsetY_;
-    if (this.textRotation_ !== 0 || this.textScale_ != 1) {
-      var localTransform = ol.vec.Mat4.makeTransform2D(this.tmpLocalTransform_,
-          x, y, this.textScale_, this.textScale_, this.textRotation_, -x, -y);
-      context.setTransform(
-          goog.vec.Mat4.getElement(localTransform, 0, 0),
-          goog.vec.Mat4.getElement(localTransform, 1, 0),
-          goog.vec.Mat4.getElement(localTransform, 0, 1),
-          goog.vec.Mat4.getElement(localTransform, 1, 1),
-          goog.vec.Mat4.getElement(localTransform, 0, 3),
-          goog.vec.Mat4.getElement(localTransform, 1, 3));
-    }
-    if (!goog.isNull(this.textStrokeState_)) {
-      context.strokeText(this.text_, x, y);
-    }
-    if (!goog.isNull(this.textFillState_)) {
-      context.fillText(this.text_, x, y);
-    }
-  }
-  if (this.textRotation_ !== 0 || this.textScale_ != 1) {
-    context.setTransform(1, 0, 0, 1, 0, 0);
-  }
-};
+goog.fx.Dragger.prototype.clientX = 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.
+ * Current y position of mouse or touch relative to viewport.
+ * @type {number}
  */
-ol.render.canvas.Immediate.prototype.moveToLineTo_ =
-    function(flatCoordinates, offset, end, stride, close) {
-  var context = this.context_;
-  var pixelCoordinates = ol.geom.flat.transform.transform2D(
-      flatCoordinates, offset, end, stride, this.transform_,
-      this.pixelCoordinates_);
-  context.moveTo(pixelCoordinates[0], pixelCoordinates[1]);
-  var i;
-  for (i = 2; i < pixelCoordinates.length; i += 2) {
-    context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]);
-  }
-  if (close) {
-    context.lineTo(pixelCoordinates[0], pixelCoordinates[1]);
-  }
-  return end;
-};
+goog.fx.Dragger.prototype.clientY = 0;
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @private
- * @return {number} End.
+ * Current x position of mouse or touch relative to screen. Deprecated because
+ * it doesn't take into affect zoom level or pixel density.
+ * @type {number}
+ * @deprecated Consider switching to clientX instead.
  */
-ol.render.canvas.Immediate.prototype.drawRings_ =
-    function(flatCoordinates, offset, ends, stride) {
-  var context = this.context_;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    offset = this.moveToLineTo_(
-        flatCoordinates, offset, ends[i], stride, true);
-    context.closePath(); // FIXME is this needed here?
-  }
-  return offset;
-};
+goog.fx.Dragger.prototype.screenX = 0;
 
 
 /**
- * Register a function to be called for rendering at a given zIndex.  The
- * function will be called asynchronously.  The callback will receive a
- * reference to {@link ol.render.canvas.Immediate} context for drawing.
- *
- * @param {number} zIndex Z index.
- * @param {function(ol.render.canvas.Immediate)} callback Callback.
- * @api
+ * Current y position of mouse or touch relative to screen. Deprecated because
+ * it doesn't take into affect zoom level or pixel density.
+ * @type {number}
+ * @deprecated Consider switching to clientY instead.
  */
-ol.render.canvas.Immediate.prototype.drawAsync = function(zIndex, callback) {
-  var zIndexKey = zIndex.toString();
-  var callbacks = this.callbacksByZIndex_[zIndexKey];
-  if (goog.isDef(callbacks)) {
-    callbacks.push(callback);
-  } else {
-    this.callbacksByZIndex_[zIndexKey] = [callback];
-  }
-};
+goog.fx.Dragger.prototype.screenY = 0;
 
 
 /**
- * Render a circle geometry into the canvas.  Rendering is immediate and uses
- * the current fill and stroke styles.
- *
- * @param {ol.geom.Circle} circleGeometry Circle geometry.
- * @param {Object} data Opaque data object,
- * @api
+ * The x position where the first mousedown or touchstart occurred.
+ * @type {number}
  */
-ol.render.canvas.Immediate.prototype.drawCircleGeometry =
-    function(circleGeometry, data) {
-  if (!ol.extent.intersects(this.extent_, circleGeometry.getExtent())) {
-    return;
-  }
-  if (!goog.isNull(this.fillState_) || !goog.isNull(this.strokeState_)) {
-    if (!goog.isNull(this.fillState_)) {
-      this.setContextFillState_(this.fillState_);
-    }
-    if (!goog.isNull(this.strokeState_)) {
-      this.setContextStrokeState_(this.strokeState_);
-    }
-    var pixelCoordinates = ol.geom.transformSimpleGeometry2D(
-        circleGeometry, this.transform_, this.pixelCoordinates_);
-    var dx = pixelCoordinates[2] - pixelCoordinates[0];
-    var dy = pixelCoordinates[3] - pixelCoordinates[1];
-    var radius = Math.sqrt(dx * dx + dy * dy);
-    var context = this.context_;
-    context.beginPath();
-    context.arc(
-        pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI);
-    if (!goog.isNull(this.fillState_)) {
-      context.fill();
-    }
-    if (!goog.isNull(this.strokeState_)) {
-      context.stroke();
-    }
-  }
-  if (this.text_ !== '') {
-    this.drawText_(circleGeometry.getCenter(), 0, 2, 2);
-  }
-};
+goog.fx.Dragger.prototype.startX = 0;
 
 
 /**
- * Render a feature into the canvas.  In order to respect the zIndex of the
- * style this method draws asynchronously and thus *after* calls to
- * drawXxxxGeometry have been finished, effectively drawing the feature
- * *on top* of everything else.  You probably should be using
- * {@link ol.FeatureOverlay} instead of calling this method directly.
- *
- * @param {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
- * @api
+ * The y position where the first mousedown or touchstart occurred.
+ * @type {number}
  */
-ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) {
-  var geometry = feature.getGeometry();
-  if (!goog.isDefAndNotNull(geometry) ||
-      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
-  }
-  var zIndex = style.getZIndex();
-  if (!goog.isDef(zIndex)) {
-    zIndex = 0;
-  }
-  this.drawAsync(zIndex, function(render) {
-    render.setFillStrokeStyle(style.getFill(), style.getStroke());
-    render.setImageStyle(style.getImage());
-    render.setTextStyle(style.getText());
-    var renderGeometry =
-        ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()];
-    goog.asserts.assert(goog.isDef(renderGeometry));
-    renderGeometry.call(render, geometry, null);
-  });
-};
+goog.fx.Dragger.prototype.startY = 0;
 
 
 /**
- * Render a GeometryCollection to the canvas.  Rendering is immediate and
- * uses the current styles appropriate for each geometry in the collection.
- *
- * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
- *     collection.
- * @param {Object} data Opaque data object.
+ * Current x position of drag relative to target's parent.
+ * @type {number}
  */
-ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry =
-    function(geometryCollectionGeometry, data) {
-  var geometries = geometryCollectionGeometry.getGeometriesArray();
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    var geometry = geometries[i];
-    var geometryRenderer =
-        ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()];
-    goog.asserts.assert(goog.isDef(geometryRenderer));
-    geometryRenderer.call(this, geometry, data);
-  }
-};
+goog.fx.Dragger.prototype.deltaX = 0;
 
 
 /**
- * Render a Point geometry into the canvas.  Rendering is immediate and uses
- * the current style.
- *
- * @param {ol.geom.Point} pointGeometry Point geometry.
- * @param {Object} data Opaque data object.
- * @api
+ * Current y position of drag relative to target's parent.
+ * @type {number}
  */
-ol.render.canvas.Immediate.prototype.drawPointGeometry =
-    function(pointGeometry, data) {
-  var flatCoordinates = pointGeometry.getFlatCoordinates();
-  var stride = pointGeometry.getStride();
-  if (!goog.isNull(this.image_)) {
-    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-  if (this.text_ !== '') {
-    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-};
+goog.fx.Dragger.prototype.deltaY = 0;
 
 
 /**
- * Render a MultiPoint geometry  into the canvas.  Rendering is immediate and
- * uses the current style.
- *
- * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry.
- * @param {Object} data Opaque data object.
- * @api
+ * The current page scroll value.
+ * @type {goog.math.Coordinate}
  */
-ol.render.canvas.Immediate.prototype.drawMultiPointGeometry =
-    function(multiPointGeometry, data) {
-  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
-  var stride = multiPointGeometry.getStride();
-  if (!goog.isNull(this.image_)) {
-    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-  if (this.text_ !== '') {
-    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-};
+goog.fx.Dragger.prototype.pageScroll;
 
 
 /**
- * Render a LineString into the canvas.  Rendering is immediate and uses
- * the current style.
- *
- * @param {ol.geom.LineString} lineStringGeometry Line string geometry.
- * @param {Object} data Opaque data object.
- * @api
+ * Whether dragging is currently enabled.
+ * @type {boolean}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.drawLineStringGeometry =
-    function(lineStringGeometry, data) {
-  if (!ol.extent.intersects(this.extent_, lineStringGeometry.getExtent())) {
-    return;
-  }
-  if (!goog.isNull(this.strokeState_)) {
-    this.setContextStrokeState_(this.strokeState_);
-    var context = this.context_;
-    var flatCoordinates = lineStringGeometry.getFlatCoordinates();
-    context.beginPath();
-    this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length,
-        lineStringGeometry.getStride(), false);
-    context.stroke();
-  }
-  if (this.text_ !== '') {
-    var flatMidpoint = lineStringGeometry.getFlatMidpoint();
-    this.drawText_(flatMidpoint, 0, 2, 2);
-  }
-};
+goog.fx.Dragger.prototype.enabled_ = true;
 
 
 /**
- * Render a MultiLineString geometry into the canvas.  Rendering is immediate
- * and uses the current style.
- *
- * @param {ol.geom.MultiLineString} multiLineStringGeometry
- *     MultiLineString geometry.
- * @param {Object} data Opaque data object.
- * @api
+ * Whether object is currently being dragged.
+ * @type {boolean}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry =
-    function(multiLineStringGeometry, data) {
-  var geometryExtent = multiLineStringGeometry.getExtent();
-  if (!ol.extent.intersects(this.extent_, geometryExtent)) {
-    return;
-  }
-  if (!goog.isNull(this.strokeState_)) {
-    this.setContextStrokeState_(this.strokeState_);
-    var context = this.context_;
-    var flatCoordinates = multiLineStringGeometry.getFlatCoordinates();
-    var offset = 0;
-    var ends = multiLineStringGeometry.getEnds();
-    var stride = multiLineStringGeometry.getStride();
-    context.beginPath();
-    var i, ii;
-    for (i = 0, ii = ends.length; i < ii; ++i) {
-      offset = this.moveToLineTo_(
-          flatCoordinates, offset, ends[i], stride, false);
-    }
-    context.stroke();
-  }
-  if (this.text_ !== '') {
-    var flatMidpoints = multiLineStringGeometry.getFlatMidpoints();
-    this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2);
-  }
-};
+goog.fx.Dragger.prototype.dragging_ = false;
 
 
 /**
- * Render a Polygon geometry into the canvas.  Rendering is immediate and uses
- * the current style.
- *
- * @param {ol.geom.Polygon} polygonGeometry Polygon geometry.
- * @param {Object} data Opaque data object.
- * @api
+ * The amount of distance, in pixels, after which a mousedown or touchstart is
+ * considered a drag.
+ * @type {number}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.drawPolygonGeometry =
-    function(polygonGeometry, data) {
-  if (!ol.extent.intersects(this.extent_, polygonGeometry.getExtent())) {
-    return;
-  }
-  if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) {
-    if (!goog.isNull(this.fillState_)) {
-      this.setContextFillState_(this.fillState_);
-    }
-    if (!goog.isNull(this.strokeState_)) {
-      this.setContextStrokeState_(this.strokeState_);
-    }
-    var context = this.context_;
-    context.beginPath();
-    this.drawRings_(polygonGeometry.getOrientedFlatCoordinates(),
-        0, polygonGeometry.getEnds(), polygonGeometry.getStride());
-    if (!goog.isNull(this.fillState_)) {
-      context.fill();
-    }
-    if (!goog.isNull(this.strokeState_)) {
-      context.stroke();
-    }
-  }
-  if (this.text_ !== '') {
-    var flatInteriorPoint = polygonGeometry.getFlatInteriorPoint();
-    this.drawText_(flatInteriorPoint, 0, 2, 2);
-  }
-};
+goog.fx.Dragger.prototype.hysteresisDistanceSquared_ = 0;
 
 
 /**
- * Render MultiPolygon geometry into the canvas.  Rendering is immediate and
- * uses the current style.
- * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
- * @param {Object} data Opaque data object.
- * @api
+ * Timestamp of when the mousedown or touchstart occurred.
+ * @type {number}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry =
-    function(multiPolygonGeometry, data) {
-  if (!ol.extent.intersects(this.extent_, multiPolygonGeometry.getExtent())) {
-    return;
-  }
-  if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) {
-    if (!goog.isNull(this.fillState_)) {
-      this.setContextFillState_(this.fillState_);
-    }
-    if (!goog.isNull(this.strokeState_)) {
-      this.setContextStrokeState_(this.strokeState_);
-    }
-    var context = this.context_;
-    var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates();
-    var offset = 0;
-    var endss = multiPolygonGeometry.getEndss();
-    var stride = multiPolygonGeometry.getStride();
-    var i, ii;
-    for (i = 0, ii = endss.length; i < ii; ++i) {
-      var ends = endss[i];
-      context.beginPath();
-      offset = this.drawRings_(flatCoordinates, offset, ends, stride);
-      if (!goog.isNull(this.fillState_)) {
-        context.fill();
-      }
-      if (!goog.isNull(this.strokeState_)) {
-        context.stroke();
-      }
-    }
-  }
-  if (this.text_ !== '') {
-    var flatInteriorPoints = multiPolygonGeometry.getFlatInteriorPoints();
-    this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2);
-  }
-};
+goog.fx.Dragger.prototype.mouseDownTime_ = 0;
 
 
 /**
- * @inheritDoc
+ * Reference to a document object to use for the events.
+ * @type {Document}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.drawText = goog.abstractMethod;
+goog.fx.Dragger.prototype.document_;
 
 
 /**
- * FIXME: empty description for jsdoc
+ * The SCROLL event target used to make drag element follow scrolling.
+ * @type {EventTarget}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.flush = function() {
-  /** @type {Array.<number>} */
-  var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number);
-  goog.array.sort(zs);
-  var i, ii, callbacks, j, jj;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    callbacks = this.callbacksByZIndex_[zs[i].toString()];
-    for (j = 0, jj = callbacks.length; j < jj; ++j) {
-      callbacks[j](this);
-    }
-  }
-};
+goog.fx.Dragger.prototype.scrollTarget_;
 
 
 /**
- * @param {ol.render.canvas.FillState} fillState Fill state.
+ * Whether IE drag events cancelling is on.
+ * @type {boolean}
  * @private
  */
-ol.render.canvas.Immediate.prototype.setContextFillState_ =
-    function(fillState) {
-  var context = this.context_;
-  var contextFillState = this.contextFillState_;
-  if (goog.isNull(contextFillState)) {
-    context.fillStyle = fillState.fillStyle;
-    this.contextFillState_ = {
-      fillStyle: fillState.fillStyle
-    };
-  } else {
-    if (contextFillState.fillStyle != fillState.fillStyle) {
-      contextFillState.fillStyle = context.fillStyle = fillState.fillStyle;
-    }
-  }
-};
+goog.fx.Dragger.prototype.ieDragStartCancellingOn_ = false;
 
 
 /**
- * @param {ol.render.canvas.StrokeState} strokeState Stroke state.
+ * Whether the dragger implements the changes described in http://b/6324964,
+ * making it truly RTL.  This is a temporary flag to allow clients to transition
+ * to the new behavior at their convenience.  At some point it will be the
+ * default.
+ * @type {boolean}
  * @private
  */
-ol.render.canvas.Immediate.prototype.setContextStrokeState_ =
-    function(strokeState) {
-  var context = this.context_;
-  var contextStrokeState = this.contextStrokeState_;
-  if (goog.isNull(contextStrokeState)) {
-    context.lineCap = strokeState.lineCap;
-    if (ol.has.CANVAS_LINE_DASH) {
-      context.setLineDash(strokeState.lineDash);
-    }
-    context.lineJoin = strokeState.lineJoin;
-    context.lineWidth = strokeState.lineWidth;
-    context.miterLimit = strokeState.miterLimit;
-    context.strokeStyle = strokeState.strokeStyle;
-    this.contextStrokeState_ = {
-      lineCap: strokeState.lineCap,
-      lineDash: strokeState.lineDash,
-      lineJoin: strokeState.lineJoin,
-      lineWidth: strokeState.lineWidth,
-      miterLimit: strokeState.miterLimit,
-      strokeStyle: strokeState.strokeStyle
-    };
-  } else {
-    if (contextStrokeState.lineCap != strokeState.lineCap) {
-      contextStrokeState.lineCap = context.lineCap = strokeState.lineCap;
-    }
-    if (ol.has.CANVAS_LINE_DASH) {
-      if (!goog.array.equals(
-          contextStrokeState.lineDash, strokeState.lineDash)) {
-        context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash);
-      }
-    }
-    if (contextStrokeState.lineJoin != strokeState.lineJoin) {
-      contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin;
-    }
-    if (contextStrokeState.lineWidth != strokeState.lineWidth) {
-      contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth;
-    }
-    if (contextStrokeState.miterLimit != strokeState.miterLimit) {
-      contextStrokeState.miterLimit = context.miterLimit =
-          strokeState.miterLimit;
-    }
-    if (contextStrokeState.strokeStyle != strokeState.strokeStyle) {
-      contextStrokeState.strokeStyle = context.strokeStyle =
-          strokeState.strokeStyle;
-    }
-  }
-};
+goog.fx.Dragger.prototype.useRightPositioningForRtl_ = false;
 
 
 /**
- * @param {ol.render.canvas.TextState} textState Text state.
- * @private
+ * Turns on/off true RTL behavior.  This should be called immediately after
+ * construction.  This is a temporary flag to allow clients to transition
+ * to the new component at their convenience.  At some point true will be the
+ * default.
+ * @param {boolean} useRightPositioningForRtl True if "right" should be used for
+ *     positioning, false if "left" should be used for positioning.
  */
-ol.render.canvas.Immediate.prototype.setContextTextState_ =
-    function(textState) {
-  var context = this.context_;
-  var contextTextState = this.contextTextState_;
-  if (goog.isNull(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;
-    }
-  }
+goog.fx.Dragger.prototype.enableRightPositioningForRtl =
+    function(useRightPositioningForRtl) {
+  this.useRightPositioningForRtl_ = useRightPositioningForRtl;
 };
 
 
 /**
- * Set the fill and stroke style for subsequent draw operations.  To clear
- * either fill or stroke styles, pass null for the appropriate parameter.
- *
- * @param {ol.style.Fill} fillStyle Fill style.
- * @param {ol.style.Stroke} strokeStyle Stroke style.
- * @api
+ * Returns the event handler, intended for subclass use.
+ * @return {!goog.events.EventHandler<T>} The event handler.
+ * @this T
+ * @template T
  */
-ol.render.canvas.Immediate.prototype.setFillStrokeStyle =
-    function(fillStyle, strokeStyle) {
-  if (goog.isNull(fillStyle)) {
-    this.fillState_ = null;
-  } else {
-    var fillStyleColor = fillStyle.getColor();
-    this.fillState_ = {
-      fillStyle: ol.color.asString(!goog.isNull(fillStyleColor) ?
-          fillStyleColor : ol.render.canvas.defaultFillStyle)
-    };
-  }
-  if (goog.isNull(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: goog.isDef(strokeStyleLineCap) ?
-          strokeStyleLineCap : ol.render.canvas.defaultLineCap,
-      lineDash: goog.isDefAndNotNull(strokeStyleLineDash) ?
-          strokeStyleLineDash : ol.render.canvas.defaultLineDash,
-      lineJoin: goog.isDef(strokeStyleLineJoin) ?
-          strokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
-      lineWidth: this.pixelRatio_ * (goog.isDef(strokeStyleWidth) ?
-          strokeStyleWidth : ol.render.canvas.defaultLineWidth),
-      miterLimit: goog.isDef(strokeStyleMiterLimit) ?
-          strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
-      strokeStyle: ol.color.asString(!goog.isNull(strokeStyleColor) ?
-          strokeStyleColor : ol.render.canvas.defaultStrokeStyle)
-    };
-  }
+goog.fx.Dragger.prototype.getHandler = function() {
+  // TODO(user): templated "this" values currently result in "this" being
+  // "unknown" in the body of the function.
+  var self = /** @type {goog.fx.Dragger} */ (this);
+  return self.eventHandler_;
 };
 
 
 /**
- * Set the image style for subsequent draw operations.  Pass null to remove
- * the image style.
- *
- * @param {ol.style.Image} imageStyle Image style.
- * @api
+ * Sets (or reset) the Drag limits after a Dragger is created.
+ * @param {goog.math.Rect?} limits Object containing left, top, width,
+ *     height for new Dragger limits. If target is right-to-left and
+ *     enableRightPositioningForRtl(true) is called, then rect is interpreted as
+ *     right, top, width, and height.
  */
-ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) {
-  if (goog.isNull(imageStyle)) {
-    this.image_ = null;
-  } else {
-    var imageAnchor = imageStyle.getAnchor();
-    // FIXME pixel ratio
-    var imageImage = imageStyle.getImage(1);
-    var imageOrigin = imageStyle.getOrigin();
-    var imageSize = imageStyle.getSize();
-    goog.asserts.assert(!goog.isNull(imageAnchor));
-    goog.asserts.assert(!goog.isNull(imageImage));
-    goog.asserts.assert(!goog.isNull(imageOrigin));
-    goog.asserts.assert(!goog.isNull(imageSize));
-    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];
-  }
+goog.fx.Dragger.prototype.setLimits = function(limits) {
+  this.limits = limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
 };
 
 
 /**
- * Set the text style for subsequent draw operations.  Pass null to
- * remove the text style.
- *
- * @param {ol.style.Text} textStyle Text style.
- * @api
+ * Sets the distance the user has to drag the element before a drag operation is
+ * started.
+ * @param {number} distance The number of pixels after which a mousedown and
+ *     move is considered a drag.
  */
-ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) {
-  if (goog.isNull(textStyle)) {
-    this.text_ = '';
-  } else {
-    var textFillStyle = textStyle.getFill();
-    if (goog.isNull(textFillStyle)) {
-      this.textFillState_ = null;
-    } else {
-      var textFillStyleColor = textFillStyle.getColor();
-      this.textFillState_ = {
-        fillStyle: ol.color.asString(!goog.isNull(textFillStyleColor) ?
-            textFillStyleColor : ol.render.canvas.defaultFillStyle)
-      };
-    }
-    var textStrokeStyle = textStyle.getStroke();
-    if (goog.isNull(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: goog.isDef(textStrokeStyleLineCap) ?
-            textStrokeStyleLineCap : ol.render.canvas.defaultLineCap,
-        lineDash: goog.isDefAndNotNull(textStrokeStyleLineDash) ?
-            textStrokeStyleLineDash : ol.render.canvas.defaultLineDash,
-        lineJoin: goog.isDef(textStrokeStyleLineJoin) ?
-            textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
-        lineWidth: goog.isDef(textStrokeStyleWidth) ?
-            textStrokeStyleWidth : ol.render.canvas.defaultLineWidth,
-        miterLimit: goog.isDef(textStrokeStyleMiterLimit) ?
-            textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
-        strokeStyle: ol.color.asString(!goog.isNull(textStrokeStyleColor) ?
-            textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle)
-      };
-    }
-    var textFont = textStyle.getFont();
-    var textOffsetX = textStyle.getOffsetX();
-    var textOffsetY = textStyle.getOffsetY();
-    var textRotation = textStyle.getRotation();
-    var textScale = textStyle.getScale();
-    var textText = textStyle.getText();
-    var textTextAlign = textStyle.getTextAlign();
-    var textTextBaseline = textStyle.getTextBaseline();
-    this.textState_ = {
-      font: goog.isDef(textFont) ?
-          textFont : ol.render.canvas.defaultFont,
-      textAlign: goog.isDef(textTextAlign) ?
-          textTextAlign : ol.render.canvas.defaultTextAlign,
-      textBaseline: goog.isDef(textTextBaseline) ?
-          textTextBaseline : ol.render.canvas.defaultTextBaseline
-    };
-    this.text_ = goog.isDef(textText) ? textText : '';
-    this.textOffsetX_ =
-        goog.isDef(textOffsetX) ? (this.pixelRatio_ * textOffsetX) : 0;
-    this.textOffsetY_ =
-        goog.isDef(textOffsetY) ? (this.pixelRatio_ * textOffsetY) : 0;
-    this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0;
-    this.textScale_ = this.pixelRatio_ * (goog.isDef(textScale) ?
-        textScale : 1);
-  }
+goog.fx.Dragger.prototype.setHysteresis = function(distance) {
+  this.hysteresisDistanceSquared_ = Math.pow(distance, 2);
 };
 
 
 /**
- * @const
- * @private
- * @type {Object.<ol.geom.GeometryType,
- *                function(this: ol.render.canvas.Immediate, ol.geom.Geometry,
- *                         Object)>}
+ * Gets the distance the user has to drag the element before a drag operation is
+ * started.
+ * @return {number} distance The number of pixels after which a mousedown and
+ *     move is considered a drag.
  */
-ol.render.canvas.Immediate.GEOMETRY_RENDERERS_ = {
-  'Point': ol.render.canvas.Immediate.prototype.drawPointGeometry,
-  'LineString': ol.render.canvas.Immediate.prototype.drawLineStringGeometry,
-  'Polygon': ol.render.canvas.Immediate.prototype.drawPolygonGeometry,
-  'MultiPoint': ol.render.canvas.Immediate.prototype.drawMultiPointGeometry,
-  'MultiLineString':
-      ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry,
-  'MultiPolygon': ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry,
-  'GeometryCollection':
-      ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry,
-  'Circle': ol.render.canvas.Immediate.prototype.drawCircleGeometry
+goog.fx.Dragger.prototype.getHysteresis = function() {
+  return Math.sqrt(this.hysteresisDistanceSquared_);
 };
 
-// FIXME add option to apply snapToPixel to all coordinates?
-// FIXME can eliminate empty set styles and strokes (when all geoms skipped)
-
-goog.provide('ol.render.canvas.Replay');
-goog.provide('ol.render.canvas.ReplayGroup');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('goog.vec.Mat4');
-goog.require('ol.array');
-goog.require('ol.color');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.extent.Relationship');
-goog.require('ol.geom.flat.simplify');
-goog.require('ol.geom.flat.transform');
-goog.require('ol.has');
-goog.require('ol.render.IReplayGroup');
-goog.require('ol.render.IVectorContext');
-goog.require('ol.render.canvas');
-goog.require('ol.vec.Mat4');
+/**
+ * Sets the SCROLL event target to make drag element follow scrolling.
+ *
+ * @param {EventTarget} scrollTarget The event target that dispatches SCROLL
+ *     events.
+ */
+goog.fx.Dragger.prototype.setScrollTarget = function(scrollTarget) {
+  this.scrollTarget_ = scrollTarget;
+};
 
 
 /**
- * @enum {number}
+ * Enables cancelling of built-in IE drag events.
+ * @param {boolean} cancelIeDragStart Whether to enable cancelling of IE
+ *     dragstart event.
  */
-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.fx.Dragger.prototype.setCancelIeDragStart = function(cancelIeDragStart) {
+  this.ieDragStartCancellingOn_ = cancelIeDragStart;
 };
 
 
-
 /**
- * @constructor
- * @implements {ol.render.IVectorContext}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @protected
- * @struct
+ * @return {boolean} Whether the dragger is enabled.
  */
-ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) {
+goog.fx.Dragger.prototype.getEnabled = function() {
+  return this.enabled_;
+};
 
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.tolerance = tolerance;
 
-  /**
-   * @protected
-   * @type {ol.Extent}
-   */
-  this.maxExtent = maxExtent;
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.maxLineWidth = 0;
+/**
+ * Set whether dragger is enabled
+ * @param {boolean} enabled Whether dragger is enabled.
+ */
+goog.fx.Dragger.prototype.setEnabled = function(enabled) {
+  this.enabled_ = enabled;
+};
 
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.resolution = resolution;
 
-  /**
-   * @private
-   * @type {Array.<*>}
-   */
-  this.beginGeometryInstruction1_ = null;
+/** @override */
+goog.fx.Dragger.prototype.disposeInternal = function() {
+  goog.fx.Dragger.superClass_.disposeInternal.call(this);
+  goog.events.unlisten(this.handle,
+      [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN],
+      this.startDrag, false, this);
+  this.cleanUpAfterDragging_();
 
-  /**
-   * @private
-   * @type {Array.<*>}
-   */
-  this.beginGeometryInstruction2_ = null;
+  this.target = null;
+  this.handle = null;
+};
 
-  /**
-   * @protected
-   * @type {Array.<*>}
-   */
-  this.instructions = [];
 
-  /**
-   * @protected
-   * @type {Array.<number>}
-   */
-  this.coordinates = [];
+/**
+ * Whether the DOM element being manipulated is rendered right-to-left.
+ * @return {boolean} True if the DOM element is rendered right-to-left, false
+ *     otherwise.
+ * @private
+ */
+goog.fx.Dragger.prototype.isRightToLeft_ = function() {
+  if (!goog.isDef(this.rightToLeft_)) {
+    this.rightToLeft_ = goog.style.isRightToLeft(this.target);
+  }
+  return this.rightToLeft_;
+};
 
-  /**
-   * @private
-   * @type {goog.vec.Mat4.Number}
-   */
-  this.renderedTransform_ = goog.vec.Mat4.createNumber();
 
-  /**
-   * @protected
-   * @type {Array.<*>}
-   */
-  this.hitDetectionInstructions = [];
+/**
+ * Event handler that is used to start the drag
+ * @param {goog.events.BrowserEvent} e Event object.
+ */
+goog.fx.Dragger.prototype.startDrag = function(e) {
+  var isMouseDown = e.type == goog.events.EventType.MOUSEDOWN;
 
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.pixelCoordinates_ = [];
+  // Dragger.startDrag() can be called by AbstractDragDrop with a mousemove
+  // event and IE does not report pressed mouse buttons on mousemove. Also,
+  // it does not make sense to check for the button if the user is already
+  // dragging.
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = ol.extent.createEmpty();
+  if (this.enabled_ && !this.dragging_ &&
+      (!isMouseDown || e.isMouseActionButton())) {
+    this.maybeReinitTouchEvent_(e);
+    if (this.hysteresisDistanceSquared_ == 0) {
+      if (this.fireDragStart_(e)) {
+        this.dragging_ = true;
+        e.preventDefault();
+      } else {
+        // If the start drag is cancelled, don't setup for a drag.
+        return;
+      }
+    } else {
+      // Need to preventDefault for hysteresis to prevent page getting selected.
+      e.preventDefault();
+    }
+    this.setupDragHandlers();
 
-  /**
-   * @private
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.tmpLocalTransform_ = goog.vec.Mat4.createNumber();
+    this.clientX = this.startX = e.clientX;
+    this.clientY = this.startY = e.clientY;
+    this.screenX = e.screenX;
+    this.screenY = e.screenY;
+    this.computeInitialPosition();
+    this.pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll();
 
+    this.mouseDownTime_ = goog.now();
+  } else {
+    this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL);
+  }
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {boolean} close Close.
+ * Sets up event handlers when dragging starts.
  * @protected
- * @return {number} My end.
  */
-ol.render.canvas.Replay.prototype.appendFlatCoordinates =
-    function(flatCoordinates, offset, end, stride, close) {
+goog.fx.Dragger.prototype.setupDragHandlers = function() {
+  var doc = this.document_;
+  var docEl = doc.documentElement;
+  // Use bubbling when we have setCapture since we got reports that IE has
+  // problems with the capturing events in combination with setCapture.
+  var useCapture = !goog.fx.Dragger.HAS_SET_CAPTURE_;
 
-  var myEnd = this.coordinates.length;
-  var extent = this.getBufferedMaxExtent();
-  var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
-  var nextCoord = [NaN, NaN];
-  var skipped = true;
+  this.eventHandler_.listen(doc,
+      [goog.events.EventType.TOUCHMOVE, goog.events.EventType.MOUSEMOVE],
+      this.handleMove_, useCapture);
+  this.eventHandler_.listen(doc,
+      [goog.events.EventType.TOUCHEND, goog.events.EventType.MOUSEUP],
+      this.endDrag, useCapture);
 
-  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;
+  if (goog.fx.Dragger.HAS_SET_CAPTURE_) {
+    docEl.setCapture(false);
+    this.eventHandler_.listen(docEl,
+                              goog.events.EventType.LOSECAPTURE,
+                              this.endDrag);
+  } else {
+    // Make sure we stop the dragging if the window loses focus.
+    // Don't use capture in this listener because we only want to end the drag
+    // if the actual window loses focus. Since blur events do not bubble we use
+    // a bubbling listener on the window.
+    this.eventHandler_.listen(goog.dom.getWindow(doc),
+                              goog.events.EventType.BLUR,
+                              this.endDrag);
   }
 
-  // handle case where there is only one point to append
-  if (i === offset + stride) {
-    this.coordinates[myEnd++] = lastCoord[0];
-    this.coordinates[myEnd++] = lastCoord[1];
+  if (goog.userAgent.IE && this.ieDragStartCancellingOn_) {
+    // Cancel IE's 'ondragstart' event.
+    this.eventHandler_.listen(doc, goog.events.EventType.DRAGSTART,
+                              goog.events.Event.preventDefault);
   }
 
-  if (close) {
-    this.coordinates[myEnd++] = flatCoordinates[offset];
-    this.coordinates[myEnd++] = flatCoordinates[offset + 1];
+  if (this.scrollTarget_) {
+    this.eventHandler_.listen(this.scrollTarget_, goog.events.EventType.SCROLL,
+                              this.onScroll_, useCapture);
   }
-  return myEnd;
 };
 
 
 /**
- * @protected
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {Object} data Opaque data object.
+ * Fires a goog.fx.Dragger.EventType.START event.
+ * @param {goog.events.BrowserEvent} e Browser event that triggered the drag.
+ * @return {boolean} False iff preventDefault was called on the DragEvent.
+ * @private
  */
-ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, data) {
-  this.beginGeometryInstruction1_ =
-      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, geometry, data, 0];
-  this.instructions.push(this.beginGeometryInstruction1_);
-  this.beginGeometryInstruction2_ =
-      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, geometry, data, 0];
-  this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
+goog.fx.Dragger.prototype.fireDragStart_ = function(e) {
+  return this.dispatchEvent(new goog.fx.DragEvent(
+      goog.fx.Dragger.EventType.START, this, e.clientX, e.clientY, e));
 };
 
 
 /**
+ * Unregisters the event handlers that are only active during dragging, and
+ * releases mouse capture.
  * @private
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object} skippedFeaturesHash Ids of features to skip.
- * @param {Array.<*>} instructions Instructions array.
- * @param {function(ol.geom.Geometry, Object): T|undefined} geometryCallback
- *     Geometry callback.
- * @return {T|undefined} Callback result.
- * @template T
  */
-ol.render.canvas.Replay.prototype.replay_ = function(
-    context, pixelRatio, transform, viewRotation, skippedFeaturesHash,
-    instructions, geometryCallback) {
-  /** @type {Array.<number>} */
-  var pixelCoordinates;
-  if (ol.vec.Mat4.equals2D(transform, this.renderedTransform_)) {
-    pixelCoordinates = this.pixelCoordinates_;
-  } else {
-    pixelCoordinates = ol.geom.flat.transform.transform2D(
-        this.coordinates, 0, this.coordinates.length, 2,
-        transform, this.pixelCoordinates_);
-    goog.vec.Mat4.setFromArray(this.renderedTransform_, transform);
-    goog.asserts.assert(pixelCoordinates === this.pixelCoordinates_);
+goog.fx.Dragger.prototype.cleanUpAfterDragging_ = function() {
+  this.eventHandler_.removeAll();
+  if (goog.fx.Dragger.HAS_SET_CAPTURE_) {
+    this.document_.releaseCapture();
   }
-  var i = 0; // instruction index
-  var ii = instructions.length; // end of instructions
-  var d = 0; // data index
-  var dd; // end of per-instruction data
-  var localTransform = this.tmpLocalTransform_;
-  while (i < ii) {
-    var instruction = instructions[i];
-    var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
-    var data, fill, geometry, stroke, text, x, y;
-    switch (type) {
-      case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
-        geometry = /** @type {ol.geom.Geometry} */ (instruction[1]);
-        data = /** @type {Object} */ (instruction[2]);
-        var dataUid = goog.getUid(data).toString();
-        if (!goog.isDef(goog.object.get(skippedFeaturesHash, dataUid))) {
-          ++i;
-        } else {
-          i = /** @type {number} */ (instruction[3]);
-        }
-        break;
-      case ol.render.canvas.Instruction.BEGIN_PATH:
-        context.beginPath();
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.CIRCLE:
-        goog.asserts.assert(goog.isNumber(instruction[1]));
-        d = /** @type {number} */ (instruction[1]);
-        var x1 = pixelCoordinates[d];
-        var y1 = pixelCoordinates[d + 1];
-        var x2 = pixelCoordinates[d + 2];
-        var y2 = pixelCoordinates[d + 3];
-        var dx = x2 - x1;
-        var dy = y2 - y1;
-        var r = Math.sqrt(dx * dx + dy * dy);
-        context.arc(x1, y1, r, 0, 2 * Math.PI, true);
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.CLOSE_PATH:
-        context.closePath();
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.DRAW_IMAGE:
-        goog.asserts.assert(goog.isNumber(instruction[1]));
-        d = /** @type {number} */ (instruction[1]);
-        goog.asserts.assert(goog.isNumber(instruction[2]));
-        dd = /** @type {number} */ (instruction[2]);
-        var image =  /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */
-            (instruction[3]);
-        // Remaining arguments in DRAW_IMAGE are in alphabetical order
-        var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio;
-        var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio;
-        var height = /** @type {number} */ (instruction[6]);
-        var opacity = /** @type {number} */ (instruction[7]);
-        var originX = /** @type {number} */ (instruction[8]);
-        var originY = /** @type {number} */ (instruction[9]);
-        var rotateWithView = /** @type {boolean} */ (instruction[10]);
-        var rotation = /** @type {number} */ (instruction[11]);
-        var scale = /** @type {number} */ (instruction[12]);
-        var snapToPixel = /** @type {boolean} */ (instruction[13]);
-        var width = /** @type {number} */ (instruction[14]);
-        if (rotateWithView) {
-          rotation += viewRotation;
-        }
-        for (; d < dd; d += 2) {
-          x = pixelCoordinates[d] - anchorX;
-          y = pixelCoordinates[d + 1] - anchorY;
-          if (snapToPixel) {
-            x = (x + 0.5) | 0;
-            y = (y + 0.5) | 0;
-          }
-          if (scale != 1 || rotation !== 0) {
-            var centerX = x + anchorX;
-            var centerY = y + anchorY;
-            ol.vec.Mat4.makeTransform2D(
-                localTransform, centerX, centerY, scale, scale,
-                rotation, -centerX, -centerY);
-            context.setTransform(
-                goog.vec.Mat4.getElement(localTransform, 0, 0),
-                goog.vec.Mat4.getElement(localTransform, 1, 0),
-                goog.vec.Mat4.getElement(localTransform, 0, 1),
-                goog.vec.Mat4.getElement(localTransform, 1, 1),
-                goog.vec.Mat4.getElement(localTransform, 0, 3),
-                goog.vec.Mat4.getElement(localTransform, 1, 3));
-          }
-          var alpha = context.globalAlpha;
-          if (opacity != 1) {
-            context.globalAlpha = alpha * opacity;
-          }
+};
 
-          context.drawImage(image, originX, originY, width, height,
-              x, y, width * pixelRatio, height * pixelRatio);
 
-          if (opacity != 1) {
-            context.globalAlpha = alpha;
-          }
-          if (scale != 1 || rotation !== 0) {
-            context.setTransform(1, 0, 0, 1, 0, 0);
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.DRAW_TEXT:
-        goog.asserts.assert(goog.isNumber(instruction[1]));
-        d = /** @type {number} */ (instruction[1]);
-        goog.asserts.assert(goog.isNumber(instruction[2]));
-        dd = /** @type {number} */ (instruction[2]);
-        goog.asserts.assert(goog.isString(instruction[3]));
-        text = /** @type {string} */ (instruction[3]);
-        goog.asserts.assert(goog.isNumber(instruction[4]));
-        var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio;
-        goog.asserts.assert(goog.isNumber(instruction[5]));
-        var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio;
-        goog.asserts.assert(goog.isNumber(instruction[6]));
-        rotation = /** @type {number} */ (instruction[6]);
-        goog.asserts.assert(goog.isNumber(instruction[7]));
-        scale = /** @type {number} */ (instruction[7]) * pixelRatio;
-        goog.asserts.assert(goog.isBoolean(instruction[8]));
-        fill = /** @type {boolean} */ (instruction[8]);
-        goog.asserts.assert(goog.isBoolean(instruction[9]));
-        stroke = /** @type {boolean} */ (instruction[9]);
-        for (; d < dd; d += 2) {
-          x = pixelCoordinates[d] + offsetX;
-          y = pixelCoordinates[d + 1] + offsetY;
-          if (scale != 1 || rotation !== 0) {
-            ol.vec.Mat4.makeTransform2D(
-                localTransform, x, y, scale, scale, rotation, -x, -y);
-            context.setTransform(
-                goog.vec.Mat4.getElement(localTransform, 0, 0),
-                goog.vec.Mat4.getElement(localTransform, 1, 0),
-                goog.vec.Mat4.getElement(localTransform, 0, 1),
-                goog.vec.Mat4.getElement(localTransform, 1, 1),
-                goog.vec.Mat4.getElement(localTransform, 0, 3),
-                goog.vec.Mat4.getElement(localTransform, 1, 3));
-          }
-          if (stroke) {
-            context.strokeText(text, x, y);
-          }
-          if (fill) {
-            context.fillText(text, x, y);
-          }
-          if (scale != 1 || rotation !== 0) {
-            context.setTransform(1, 0, 0, 1, 0, 0);
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.END_GEOMETRY:
-        if (goog.isDef(geometryCallback)) {
-          geometry = /** @type {ol.geom.Geometry} */ (instruction[1]);
-          data = /** @type {Object} */ (instruction[2]);
-          var result = geometryCallback(geometry, data);
-          if (result) {
-            return result;
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.FILL:
-        context.fill();
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
-        goog.asserts.assert(goog.isNumber(instruction[1]));
-        d = /** @type {number} */ (instruction[1]);
-        goog.asserts.assert(goog.isNumber(instruction[2]));
-        dd = /** @type {number} */ (instruction[2]);
-        context.moveTo(pixelCoordinates[d], pixelCoordinates[d + 1]);
-        for (d += 2; d < dd; d += 2) {
-          context.lineTo(pixelCoordinates[d], pixelCoordinates[d + 1]);
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_FILL_STYLE:
-        goog.asserts.assert(goog.isString(instruction[1]));
-        context.fillStyle = /** @type {string} */ (instruction[1]);
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
-        goog.asserts.assert(goog.isString(instruction[1]));
-        goog.asserts.assert(goog.isNumber(instruction[2]));
-        goog.asserts.assert(goog.isString(instruction[3]));
-        goog.asserts.assert(goog.isString(instruction[4]));
-        goog.asserts.assert(goog.isNumber(instruction[5]));
-        goog.asserts.assert(!goog.isNull(instruction[6]));
-        var usePixelRatio = goog.isDef(instruction[7]) ? instruction[7] : true;
-        var lineWidth = /** @type {number} */ (instruction[2]);
-        context.strokeStyle = /** @type {string} */ (instruction[1]);
-        context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth;
-        context.lineCap = /** @type {string} */ (instruction[3]);
-        context.lineJoin = /** @type {string} */ (instruction[4]);
-        context.miterLimit = /** @type {number} */ (instruction[5]);
-        if (ol.has.CANVAS_LINE_DASH) {
-          context.setLineDash(/** @type {Array.<number>} */ (instruction[6]));
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_TEXT_STYLE:
-        goog.asserts.assert(goog.isString(instruction[1]));
-        goog.asserts.assert(goog.isString(instruction[2]));
-        goog.asserts.assert(goog.isString(instruction[3]));
-        context.font = /** @type {string} */ (instruction[1]);
-        context.textAlign = /** @type {string} */ (instruction[2]);
-        context.textBaseline = /** @type {string} */ (instruction[3]);
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.STROKE:
-        context.stroke();
-        ++i;
-        break;
-      default:
-        goog.asserts.fail();
-        ++i; // consume the instruction anyway, to avoid an infinite loop
-        break;
-    }
+/**
+ * Event handler that is used to end the drag.
+ * @param {goog.events.BrowserEvent} e Event object.
+ * @param {boolean=} opt_dragCanceled Whether the drag has been canceled.
+ */
+goog.fx.Dragger.prototype.endDrag = function(e, opt_dragCanceled) {
+  this.cleanUpAfterDragging_();
+
+  if (this.dragging_) {
+    this.maybeReinitTouchEvent_(e);
+    this.dragging_ = false;
+
+    var x = this.limitX(this.deltaX);
+    var y = this.limitY(this.deltaY);
+    var dragCanceled = opt_dragCanceled ||
+        e.type == goog.events.EventType.TOUCHCANCEL;
+    this.dispatchEvent(new goog.fx.DragEvent(
+        goog.fx.Dragger.EventType.END, this, e.clientX, e.clientY, e, x, y,
+        dragCanceled));
+  } else {
+    this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL);
   }
-  // assert that all instructions were consumed
-  goog.asserts.assert(i == instructions.length);
-  return undefined;
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object} skippedFeaturesHash Ids of features to skip
- * @return {T|undefined} Callback result.
- * @template T
+ * Event handler that is used to end the drag by cancelling it.
+ * @param {goog.events.BrowserEvent} e Event object.
  */
-ol.render.canvas.Replay.prototype.replay = function(
-    context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
-  var instructions = this.instructions;
-  return this.replay_(context, pixelRatio, transform, viewRotation,
-      skippedFeaturesHash, instructions, undefined);
+goog.fx.Dragger.prototype.endDragCancel = function(e) {
+  this.endDrag(e, true);
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object} skippedFeaturesHash Ids of features to skip
- * @param {function(ol.geom.Geometry, Object): T=} opt_geometryCallback
- *     Geometry callback.
- * @return {T|undefined} Callback result.
- * @template T
+ * Re-initializes the event with the first target touch event or, in the case
+ * of a stop event, the last changed touch.
+ * @param {goog.events.BrowserEvent} e A TOUCH... event.
+ * @private
  */
-ol.render.canvas.Replay.prototype.replayHitDetection = function(
-    context, transform, viewRotation, skippedFeaturesHash,
-    opt_geometryCallback) {
-  var instructions = this.hitDetectionInstructions;
-  return this.replay_(context, 1, transform, viewRotation,
-      skippedFeaturesHash, instructions, opt_geometryCallback);
+goog.fx.Dragger.prototype.maybeReinitTouchEvent_ = function(e) {
+  var type = e.type;
+
+  if (type == goog.events.EventType.TOUCHSTART ||
+      type == goog.events.EventType.TOUCHMOVE) {
+    e.init(e.getBrowserEvent().targetTouches[0], e.currentTarget);
+  } else if (type == goog.events.EventType.TOUCHEND ||
+             type == goog.events.EventType.TOUCHCANCEL) {
+    e.init(e.getBrowserEvent().changedTouches[0], e.currentTarget);
+  }
 };
 
 
 /**
+ * Event handler that is used on mouse / touch move to update the drag
+ * @param {goog.events.BrowserEvent} e Event object.
  * @private
  */
-ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions_ =
-    function() {
-  var hitDetectionInstructions = this.hitDetectionInstructions;
-  // step 1 - reverse array
-  hitDetectionInstructions.reverse();
-  // step 2 - reverse instructions within geometry blocks
-  var i;
-  var n = hitDetectionInstructions.length;
-  var instruction;
-  var type;
-  var begin = -1;
-  for (i = 0; i < n; ++i) {
-    instruction = hitDetectionInstructions[i];
-    type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
-    if (type == ol.render.canvas.Instruction.END_GEOMETRY) {
-      goog.asserts.assert(begin == -1);
-      begin = i;
-    } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) {
-      instruction[3] = i;
-      goog.asserts.assert(begin >= 0);
-      ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i);
-      begin = -1;
-    }
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawAsync = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawCircleGeometry = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawFeature = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawGeometryCollectionGeometry =
-    goog.abstractMethod;
-
+goog.fx.Dragger.prototype.handleMove_ = function(e) {
+  if (this.enabled_) {
+    this.maybeReinitTouchEvent_(e);
+    // dx in right-to-left cases is relative to the right.
+    var sign = this.useRightPositioningForRtl_ &&
+        this.isRightToLeft_() ? -1 : 1;
+    var dx = sign * (e.clientX - this.clientX);
+    var dy = e.clientY - this.clientY;
+    this.clientX = e.clientX;
+    this.clientY = e.clientY;
+    this.screenX = e.screenX;
+    this.screenY = e.screenY;
 
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawLineStringGeometry = goog.abstractMethod;
+    if (!this.dragging_) {
+      var diffX = this.startX - this.clientX;
+      var diffY = this.startY - this.clientY;
+      var distance = diffX * diffX + diffY * diffY;
+      if (distance > this.hysteresisDistanceSquared_) {
+        if (this.fireDragStart_(e)) {
+          this.dragging_ = true;
+        } else {
+          // DragListGroup disposes of the dragger if BEFOREDRAGSTART is
+          // canceled.
+          if (!this.isDisposed()) {
+            this.endDrag(e);
+          }
+          return;
+        }
+      }
+    }
 
+    var pos = this.calculatePosition_(dx, dy);
+    var x = pos.x;
+    var y = pos.y;
 
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawMultiLineStringGeometry =
-    goog.abstractMethod;
+    if (this.dragging_) {
 
+      var rv = this.dispatchEvent(new goog.fx.DragEvent(
+          goog.fx.Dragger.EventType.BEFOREDRAG, this, e.clientX, e.clientY,
+          e, x, y));
 
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawPointGeometry = goog.abstractMethod;
+      // Only do the defaultAction and dispatch drag event if predrag didn't
+      // prevent default
+      if (rv) {
+        this.doDrag(e, x, y, false);
+        e.preventDefault();
+      }
+    }
+  }
+};
 
 
 /**
- * @inheritDoc
+ * Calculates the drag position.
+ *
+ * @param {number} dx The horizontal movement delta.
+ * @param {number} dy The vertical movement delta.
+ * @return {!goog.math.Coordinate} The newly calculated drag element position.
+ * @private
  */
-ol.render.canvas.Replay.prototype.drawMultiPointGeometry = goog.abstractMethod;
+goog.fx.Dragger.prototype.calculatePosition_ = function(dx, dy) {
+  // Update the position for any change in body scrolling
+  var pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll();
+  dx += pageScroll.x - this.pageScroll.x;
+  dy += pageScroll.y - this.pageScroll.y;
+  this.pageScroll = pageScroll;
 
+  this.deltaX += dx;
+  this.deltaY += dy;
 
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.drawPolygonGeometry = goog.abstractMethod;
+  var x = this.limitX(this.deltaX);
+  var y = this.limitY(this.deltaY);
+  return new goog.math.Coordinate(x, y);
+};
 
 
 /**
- * @inheritDoc
+ * Event handler for scroll target scrolling.
+ * @param {goog.events.BrowserEvent} e The event.
+ * @private
  */
-ol.render.canvas.Replay.prototype.drawMultiPolygonGeometry =
-    goog.abstractMethod;
+goog.fx.Dragger.prototype.onScroll_ = function(e) {
+  var pos = this.calculatePosition_(0, 0);
+  e.clientX = this.clientX;
+  e.clientY = this.clientY;
+  this.doDrag(e, pos.x, pos.y, true);
+};
 
 
 /**
- * @inheritDoc
+ * @param {goog.events.BrowserEvent} e The closure object
+ *     representing the browser event that caused a drag event.
+ * @param {number} x The new horizontal position for the drag element.
+ * @param {number} y The new vertical position for the drag element.
+ * @param {boolean} dragFromScroll Whether dragging was caused by scrolling
+ *     the associated scroll target.
+ * @protected
  */
-ol.render.canvas.Replay.prototype.drawText = goog.abstractMethod;
+goog.fx.Dragger.prototype.doDrag = function(e, x, y, dragFromScroll) {
+  this.defaultAction(x, y);
+  this.dispatchEvent(new goog.fx.DragEvent(
+      goog.fx.Dragger.EventType.DRAG, this, e.clientX, e.clientY, e, x, y));
+};
 
 
 /**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {Object} data Opaque data object.
+ * Returns the 'real' x after limits are applied (allows for some
+ * limits to be undefined).
+ * @param {number} x X-coordinate to limit.
+ * @return {number} The 'real' X-coordinate after limits are applied.
  */
-ol.render.canvas.Replay.prototype.endGeometry = function(geometry, data) {
-  goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction1_));
-  this.beginGeometryInstruction1_[3] = this.instructions.length;
-  this.beginGeometryInstruction1_ = null;
-  goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction2_));
-  this.beginGeometryInstruction2_[3] = this.hitDetectionInstructions.length;
-  this.beginGeometryInstruction2_ = null;
-  var endGeometryInstruction =
-      [ol.render.canvas.Instruction.END_GEOMETRY, geometry, data];
-  this.instructions.push(endGeometryInstruction);
-  this.hitDetectionInstructions.push(endGeometryInstruction);
+goog.fx.Dragger.prototype.limitX = function(x) {
+  var rect = this.limits;
+  var left = !isNaN(rect.left) ? rect.left : null;
+  var width = !isNaN(rect.width) ? rect.width : 0;
+  var maxX = left != null ? left + width : Infinity;
+  var minX = left != null ? left : -Infinity;
+  return Math.min(maxX, Math.max(minX, x));
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * Returns the 'real' y after limits are applied (allows for some
+ * limits to be undefined).
+ * @param {number} y Y-coordinate to limit.
+ * @return {number} The 'real' Y-coordinate after limits are applied.
  */
-ol.render.canvas.Replay.prototype.finish = goog.nullFunction;
+goog.fx.Dragger.prototype.limitY = function(y) {
+  var rect = this.limits;
+  var top = !isNaN(rect.top) ? rect.top : null;
+  var height = !isNaN(rect.height) ? rect.height : 0;
+  var maxY = top != null ? top + height : Infinity;
+  var minY = top != null ? top : -Infinity;
+  return Math.min(maxY, Math.max(minY, y));
+};
 
 
 /**
- * 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.
+ * Overridable function for computing the initial position of the target
+ * before dragging begins.
  * @protected
  */
-ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() {
-  return this.maxExtent;
+goog.fx.Dragger.prototype.computeInitialPosition = function() {
+  this.deltaX = this.useRightPositioningForRtl_ ?
+      goog.style.bidi.getOffsetStart(this.target) : this.target.offsetLeft;
+  this.deltaY = this.target.offsetTop;
 };
 
 
 /**
- * @return {ol.Extent} Extent.
+ * Overridable function for handling the default action of the drag behaviour.
+ * Normally this is simply moving the element to x,y though in some cases it
+ * might be used to resize the layer.  This is basically a shortcut to
+ * implementing a default ondrag event handler.
+ * @param {number} x X-coordinate for target element. In right-to-left, x this
+ *     is the number of pixels the target should be moved to from the right.
+ * @param {number} y Y-coordinate for target element.
  */
-ol.render.canvas.Replay.prototype.getExtent = function() {
-  return this.extent_;
+goog.fx.Dragger.prototype.defaultAction = function(x, y) {
+  if (this.useRightPositioningForRtl_ && this.isRightToLeft_()) {
+    this.target.style.right = x + 'px';
+  } else {
+    this.target.style.left = x + 'px';
+  }
+  this.target.style.top = y + 'px';
 };
 
 
 /**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.setFillStrokeStyle = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.Replay.prototype.setImageStyle = goog.abstractMethod;
-
-
-/**
- * @inheritDoc
+ * @return {boolean} Whether the dragger is currently in the midst of a drag.
  */
-ol.render.canvas.Replay.prototype.setTextStyle = goog.abstractMethod;
+goog.fx.Dragger.prototype.isDragging = function() {
+  return this.dragging_;
+};
 
 
 
 /**
+ * Object representing a drag event
+ * @param {string} type Event type.
+ * @param {goog.fx.Dragger} dragobj Drag object initiating event.
+ * @param {number} clientX X-coordinate relative to the viewport.
+ * @param {number} clientY Y-coordinate relative to the viewport.
+ * @param {goog.events.BrowserEvent} browserEvent The closure object
+ *   representing the browser event that caused this drag event.
+ * @param {number=} opt_actX Optional actual x for drag if it has been limited.
+ * @param {number=} opt_actY Optional actual y for drag if it has been limited.
+ * @param {boolean=} opt_dragCanceled Whether the drag has been canceled.
  * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @protected
- * @struct
+ * @extends {goog.events.Event}
  */
-ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution) {
-
-  goog.base(this, tolerance, maxExtent, resolution);
+goog.fx.DragEvent = function(type, dragobj, clientX, clientY, browserEvent,
+                             opt_actX, opt_actY, opt_dragCanceled) {
+  goog.events.Event.call(this, type);
 
   /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   * X-coordinate relative to the viewport
+   * @type {number}
    */
-  this.hitDetectionImage_ = null;
+  this.clientX = clientX;
 
   /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   * Y-coordinate relative to the viewport
+   * @type {number}
    */
-  this.image_ = null;
+  this.clientY = clientY;
 
   /**
-   * @private
-   * @type {number|undefined}
+   * The closure object representing the browser event that caused this drag
+   * event.
+   * @type {goog.events.BrowserEvent}
    */
-  this.anchorX_ = undefined;
+  this.browserEvent = browserEvent;
 
   /**
-   * @private
-   * @type {number|undefined}
+   * The real x-position of the drag if it has been limited
+   * @type {number}
    */
-  this.anchorY_ = undefined;
+  this.left = goog.isDef(opt_actX) ? opt_actX : dragobj.deltaX;
 
   /**
-   * @private
-   * @type {number|undefined}
+   * The real y-position of the drag if it has been limited
+   * @type {number}
    */
-  this.height_ = undefined;
+  this.top = goog.isDef(opt_actY) ? opt_actY : dragobj.deltaY;
 
   /**
-   * @private
-   * @type {number|undefined}
+   * Reference to the drag object for this event
+   * @type {goog.fx.Dragger}
    */
-  this.opacity_ = undefined;
+  this.dragger = dragobj;
 
   /**
-   * @private
-   * @type {number|undefined}
+   * Whether drag was canceled with this event. Used to differentiate between
+   * a legitimate drag END that can result in an action and a drag END which is
+   * a result of a drag cancelation. For now it can happen 1) with drag END
+   * event on FireFox when user drags the mouse out of the window, 2) with
+   * drag END event on IE7 which is generated on MOUSEMOVE event when user
+   * moves the mouse into the document after the mouse button has been
+   * released, 3) when TOUCHCANCEL is raised instead of TOUCHEND (on touch
+   * events).
+   * @type {boolean}
    */
-  this.originX_ = undefined;
+  this.dragCanceled = !!opt_dragCanceled;
+};
+goog.inherits(goog.fx.DragEvent, goog.events.Event);
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.originY_ = undefined;
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-  /**
-   * @private
-   * @type {boolean|undefined}
-   */
-  this.rotateWithView_ = undefined;
+/**
+ * @fileoverview Abstract Base Class for Drag and Drop.
+ *
+ * Provides functionality for implementing drag and drop classes. Also provides
+ * support classes and events.
+ *
+ * @author eae@google.com (Emil A Eklund)
+ */
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.rotation_ = undefined;
+goog.provide('goog.fx.AbstractDragDrop');
+goog.provide('goog.fx.AbstractDragDrop.EventType');
+goog.provide('goog.fx.DragDropEvent');
+goog.provide('goog.fx.DragDropItem');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.classlist');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.fx.Dragger');
+goog.require('goog.math.Box');
+goog.require('goog.math.Coordinate');
+goog.require('goog.style');
+
+
+
+/**
+ * Abstract class that provides reusable functionality for implementing drag
+ * and drop functionality.
+ *
+ * This class also allows clients to define their own subtargeting function
+ * so that drop areas can have finer granularity then a singe element. This is
+ * accomplished by using a client provided function to map from element and
+ * coordinates to a subregion id.
+ *
+ * This class can also be made aware of scrollable containers that contain
+ * drop targets by calling addScrollableContainer. This will cause dnd to
+ * take changing scroll positions into account while a drag is occuring.
+ *
+ * @extends {goog.events.EventTarget}
+ * @constructor
+ */
+goog.fx.AbstractDragDrop = function() {
+  goog.fx.AbstractDragDrop.base(this, 'constructor');
 
   /**
-   * @private
-   * @type {number|undefined}
+   * List of items that makes up the drag source or drop target.
+   * @type {Array<goog.fx.DragDropItem>}
+   * @protected
+   * @suppress {underscore|visibility}
    */
-  this.scale_ = undefined;
+  this.items_ = [];
 
   /**
+   * List of associated drop targets.
+   * @type {Array<goog.fx.AbstractDragDrop>}
    * @private
-   * @type {boolean|undefined}
    */
-  this.snapToPixel_ = undefined;
+  this.targets_ = [];
 
   /**
+   * Scrollable containers to account for during drag
+   * @type {Array<goog.fx.ScrollableContainer_>}
    * @private
-   * @type {number|undefined}
    */
-  this.width_ = undefined;
+  this.scrollableContainers_ = [];
 
 };
-goog.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay);
+goog.inherits(goog.fx.AbstractDragDrop, goog.events.EventTarget);
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
+ * Minimum size (in pixels) for a dummy target. If the box for the target is
+ * less than the specified size it's not created.
+ * @type {number}
  * @private
- * @return {number} My end.
  */
-ol.render.canvas.ImageReplay.prototype.drawCoordinates_ =
-    function(flatCoordinates, offset, end, stride) {
-  return this.appendFlatCoordinates(
-      flatCoordinates, offset, end, stride, false);
-};
+goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ = 10;
 
 
 /**
- * @inheritDoc
+ * Flag indicating if it's a drag source, set by addTarget.
+ * @type {boolean}
+ * @private
  */
-ol.render.canvas.ImageReplay.prototype.drawPointGeometry =
-    function(pointGeometry, data) {
-  if (goog.isNull(this.image_)) {
-    return;
-  }
-  goog.asserts.assert(goog.isDef(this.anchorX_));
-  goog.asserts.assert(goog.isDef(this.anchorY_));
-  goog.asserts.assert(goog.isDef(this.height_));
-  goog.asserts.assert(goog.isDef(this.opacity_));
-  goog.asserts.assert(goog.isDef(this.originX_));
-  goog.asserts.assert(goog.isDef(this.originY_));
-  goog.asserts.assert(goog.isDef(this.rotateWithView_));
-  goog.asserts.assert(goog.isDef(this.rotation_));
-  goog.asserts.assert(goog.isDef(this.scale_));
-  goog.asserts.assert(goog.isDef(this.width_));
-  ol.extent.extend(this.extent_, pointGeometry.getExtent());
-  this.beginGeometry(pointGeometry, data);
-  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, data);
-};
+goog.fx.AbstractDragDrop.prototype.isSource_ = false;
 
 
 /**
- * @inheritDoc
+ * Flag indicating if it's a drop target, set when added as target to another
+ * DragDrop object.
+ * @type {boolean}
+ * @private
  */
-ol.render.canvas.ImageReplay.prototype.drawMultiPointGeometry =
-    function(multiPointGeometry, data) {
-  if (goog.isNull(this.image_)) {
-    return;
-  }
-  goog.asserts.assert(goog.isDef(this.anchorX_));
-  goog.asserts.assert(goog.isDef(this.anchorY_));
-  goog.asserts.assert(goog.isDef(this.height_));
-  goog.asserts.assert(goog.isDef(this.opacity_));
-  goog.asserts.assert(goog.isDef(this.originX_));
-  goog.asserts.assert(goog.isDef(this.originY_));
-  goog.asserts.assert(goog.isDef(this.rotateWithView_));
-  goog.asserts.assert(goog.isDef(this.rotation_));
-  goog.asserts.assert(goog.isDef(this.scale_));
-  goog.asserts.assert(goog.isDef(this.width_));
-  ol.extent.extend(this.extent_, multiPointGeometry.getExtent());
-  this.beginGeometry(multiPointGeometry, data);
-  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, data);
-};
+goog.fx.AbstractDragDrop.prototype.isTarget_ = false;
 
 
 /**
- * @inheritDoc
+ * Subtargeting function accepting args:
+ * (goog.fx.DragDropItem, goog.math.Box, number, number)
+ * @type {Function}
+ * @private
  */
-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;
-};
+goog.fx.AbstractDragDrop.prototype.subtargetFunction_;
 
 
 /**
- * @inheritDoc
+ * Last active subtarget.
+ * @type {Object}
+ * @private
  */
-ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
-  goog.asserts.assert(!goog.isNull(imageStyle));
-  var anchor = imageStyle.getAnchor();
-  goog.asserts.assert(!goog.isNull(anchor));
-  var size = imageStyle.getSize();
-  goog.asserts.assert(!goog.isNull(size));
-  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
-  goog.asserts.assert(!goog.isNull(hitDetectionImage));
-  var image = imageStyle.getImage(1);
-  goog.asserts.assert(!goog.isNull(image));
-  var origin = imageStyle.getOrigin();
-  goog.asserts.assert(!goog.isNull(origin));
-  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.fx.AbstractDragDrop.prototype.activeSubtarget_;
 
 
 /**
- * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @protected
- * @struct
+ * Class name to add to source elements being dragged. Set by setDragClass.
+ * @type {?string}
+ * @private
  */
-ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution) {
+goog.fx.AbstractDragDrop.prototype.dragClass_;
 
-  goog.base(this, tolerance, maxExtent, resolution);
 
-  /**
-   * @private
-   * @type {{currentStrokeStyle: (string|undefined),
-   *         currentLineCap: (string|undefined),
-   *         currentLineDash: Array.<number>,
-   *         currentLineJoin: (string|undefined),
-   *         currentLineWidth: (number|undefined),
-   *         currentMiterLimit: (number|undefined),
-   *         lastStroke: number,
-   *         strokeStyle: (string|undefined),
-   *         lineCap: (string|undefined),
-   *         lineDash: Array.<number>,
-   *         lineJoin: (string|undefined),
-   *         lineWidth: (number|undefined),
-   *         miterLimit: (number|undefined)}|null}
-   */
-  this.state_ = {
-    currentStrokeStyle: undefined,
-    currentLineCap: undefined,
-    currentLineDash: null,
-    currentLineJoin: undefined,
-    currentLineWidth: undefined,
-    currentMiterLimit: undefined,
-    lastStroke: 0,
-    strokeStyle: undefined,
-    lineCap: undefined,
-    lineDash: null,
-    lineJoin: undefined,
-    lineWidth: undefined,
-    miterLimit: undefined
-  };
+/**
+ * Class name to add to source elements. Set by setSourceClass.
+ * @type {?string}
+ * @private
+ */
+goog.fx.AbstractDragDrop.prototype.sourceClass_;
 
-};
-goog.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay);
+
+/**
+ * Class name to add to target elements. Set by setTargetClass.
+ * @type {?string}
+ * @private
+ */
+goog.fx.AbstractDragDrop.prototype.targetClass_;
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
+ * The SCROLL event target used to make drag element follow scrolling.
+ * @type {EventTarget}
  * @private
- * @return {number} end.
  */
-ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ =
-    function(flatCoordinates, offset, end, stride) {
-  var myBegin = this.coordinates.length;
-  var myEnd = this.appendFlatCoordinates(
-      flatCoordinates, offset, end, stride, false);
-  var moveToLineToInstruction =
-      [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
-  this.instructions.push(moveToLineToInstruction);
-  this.hitDetectionInstructions.push(moveToLineToInstruction);
-  return end;
-};
+goog.fx.AbstractDragDrop.prototype.scrollTarget_;
 
 
 /**
- * @inheritDoc
+ * Dummy target, {@see maybeCreateDummyTargetForPosition_}.
+ * @type {goog.fx.ActiveDropTarget_}
+ * @private
  */
-ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() {
-  var extent = this.maxExtent;
-  if (this.maxLineWidth) {
-    extent = ol.extent.buffer(
-        extent, this.resolution * (this.maxLineWidth + 1) / 2);
-  }
-  return extent;
-};
+goog.fx.AbstractDragDrop.prototype.dummyTarget_;
 
 
 /**
+ * Whether the object has been initialized.
+ * @type {boolean}
  * @private
  */
-ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() {
-  var state = this.state_;
-  var strokeStyle = state.strokeStyle;
-  var lineCap = state.lineCap;
-  var lineDash = state.lineDash;
-  var lineJoin = state.lineJoin;
-  var lineWidth = state.lineWidth;
-  var miterLimit = state.miterLimit;
-  goog.asserts.assert(goog.isDef(strokeStyle));
-  goog.asserts.assert(goog.isDef(lineCap));
-  goog.asserts.assert(!goog.isNull(lineDash));
-  goog.asserts.assert(goog.isDef(lineJoin));
-  goog.asserts.assert(goog.isDef(lineWidth));
-  goog.asserts.assert(goog.isDef(miterLimit));
-  if (state.currentStrokeStyle != strokeStyle ||
-      state.currentLineCap != lineCap ||
-      !goog.array.equals(state.currentLineDash, lineDash) ||
-      state.currentLineJoin != lineJoin ||
-      state.currentLineWidth != lineWidth ||
-      state.currentMiterLimit != miterLimit) {
-    if (state.lastStroke != this.coordinates.length) {
-      this.instructions.push(
-          [ol.render.canvas.Instruction.STROKE]);
-      state.lastStroke = this.coordinates.length;
-    }
-    this.instructions.push(
-        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-         strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash],
-        [ol.render.canvas.Instruction.BEGIN_PATH]);
-    state.currentStrokeStyle = strokeStyle;
-    state.currentLineCap = lineCap;
-    state.currentLineDash = lineDash;
-    state.currentLineJoin = lineJoin;
-    state.currentLineWidth = lineWidth;
-    state.currentMiterLimit = miterLimit;
-  }
-};
+goog.fx.AbstractDragDrop.prototype.initialized_ = false;
 
 
 /**
- * @inheritDoc
+ * Constants for event names
+ * @const
  */
-ol.render.canvas.LineStringReplay.prototype.drawLineStringGeometry =
-    function(lineStringGeometry, data) {
-  var state = this.state_;
-  goog.asserts.assert(!goog.isNull(state));
-  var strokeStyle = state.strokeStyle;
-  var lineWidth = state.lineWidth;
-  if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) {
-    return;
-  }
-  ol.extent.extend(this.extent_, lineStringGeometry.getExtent());
-  this.setStrokeStyle_();
-  this.beginGeometry(lineStringGeometry, data);
-  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, data);
+goog.fx.AbstractDragDrop.EventType = {
+  DRAGOVER: 'dragover',
+  DRAGOUT: 'dragout',
+  DRAG: 'drag',
+  DROP: 'drop',
+  DRAGSTART: 'dragstart',
+  DRAGEND: 'dragend'
 };
 
 
 /**
- * @inheritDoc
+ * Constant for distance threshold, in pixels, an element has to be moved to
+ * initiate a drag operation.
+ * @type {number}
  */
-ol.render.canvas.LineStringReplay.prototype.drawMultiLineStringGeometry =
-    function(multiLineStringGeometry, data) {
-  var state = this.state_;
-  goog.asserts.assert(!goog.isNull(state));
-  var strokeStyle = state.strokeStyle;
-  var lineWidth = state.lineWidth;
-  if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) {
-    return;
-  }
-  ol.extent.extend(this.extent_, multiLineStringGeometry.getExtent());
-  this.setStrokeStyle_();
-  this.beginGeometry(multiLineStringGeometry, data);
-  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, data);
-};
+goog.fx.AbstractDragDrop.initDragDistanceThreshold = 5;
 
 
 /**
- * @inheritDoc
+ * Set class to add to source elements being dragged.
+ *
+ * @param {string} className Class to be added.  Must be a single, valid
+ *     classname.
  */
-ol.render.canvas.LineStringReplay.prototype.finish = function() {
-  var state = this.state_;
-  goog.asserts.assert(!goog.isNull(state));
-  if (state.lastStroke != this.coordinates.length) {
-    this.instructions.push([ol.render.canvas.Instruction.STROKE]);
-  }
-  this.reverseHitDetectionInstructions_();
-  this.state_ = null;
+goog.fx.AbstractDragDrop.prototype.setDragClass = function(className) {
+  this.dragClass_ = className;
 };
 
 
 /**
- * @inheritDoc
+ * Set class to add to source elements.
+ *
+ * @param {string} className Class to be added.  Must be a single, valid
+ *     classname.
  */
-ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle =
-    function(fillStyle, strokeStyle) {
-  goog.asserts.assert(!goog.isNull(this.state_));
-  goog.asserts.assert(goog.isNull(fillStyle));
-  goog.asserts.assert(!goog.isNull(strokeStyle));
-  var strokeStyleColor = strokeStyle.getColor();
-  this.state_.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ?
-      strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
-  var strokeStyleLineCap = strokeStyle.getLineCap();
-  this.state_.lineCap = goog.isDef(strokeStyleLineCap) ?
-      strokeStyleLineCap : ol.render.canvas.defaultLineCap;
-  var strokeStyleLineDash = strokeStyle.getLineDash();
-  this.state_.lineDash = !goog.isNull(strokeStyleLineDash) ?
-      strokeStyleLineDash : ol.render.canvas.defaultLineDash;
-  var strokeStyleLineJoin = strokeStyle.getLineJoin();
-  this.state_.lineJoin = goog.isDef(strokeStyleLineJoin) ?
-      strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
-  var strokeStyleWidth = strokeStyle.getWidth();
-  this.state_.lineWidth = goog.isDef(strokeStyleWidth) ?
-      strokeStyleWidth : ol.render.canvas.defaultLineWidth;
-  var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
-  this.state_.miterLimit = goog.isDef(strokeStyleMiterLimit) ?
-      strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-  this.maxLineWidth = Math.max(this.maxLineWidth, this.state_.lineWidth);
+goog.fx.AbstractDragDrop.prototype.setSourceClass = function(className) {
+  this.sourceClass_ = className;
 };
 
 
+/**
+ * Set class to add to target elements.
+ *
+ * @param {string} className Class to be added.  Must be a single, valid
+ *     classname.
+ */
+goog.fx.AbstractDragDrop.prototype.setTargetClass = function(className) {
+  this.targetClass_ = className;
+};
+
 
 /**
- * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @protected
- * @struct
+ * Whether the control has been initialized.
+ *
+ * @return {boolean} True if it's been initialized.
  */
-ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) {
+goog.fx.AbstractDragDrop.prototype.isInitialized = function() {
+  return this.initialized_;
+};
 
-  goog.base(this, tolerance, maxExtent, resolution);
 
-  /**
-   * @private
-   * @type {{currentFillStyle: (string|undefined),
-   *         currentStrokeStyle: (string|undefined),
-   *         currentLineCap: (string|undefined),
-   *         currentLineDash: Array.<number>,
-   *         currentLineJoin: (string|undefined),
-   *         currentLineWidth: (number|undefined),
-   *         currentMiterLimit: (number|undefined),
-   *         fillStyle: (string|undefined),
-   *         strokeStyle: (string|undefined),
-   *         lineCap: (string|undefined),
-   *         lineDash: Array.<number>,
-   *         lineJoin: (string|undefined),
-   *         lineWidth: (number|undefined),
-   *         miterLimit: (number|undefined)}|null}
-   */
-  this.state_ = {
-    currentFillStyle: undefined,
-    currentStrokeStyle: undefined,
-    currentLineCap: undefined,
-    currentLineDash: null,
-    currentLineJoin: undefined,
-    currentLineWidth: undefined,
-    currentMiterLimit: undefined,
-    fillStyle: undefined,
-    strokeStyle: undefined,
-    lineCap: undefined,
-    lineDash: null,
-    lineJoin: undefined,
-    lineWidth: undefined,
-    miterLimit: undefined
-  };
+/**
+ * Add item to drag object.
+ *
+ * @param {Element|string} element Dom Node, or string representation of node
+ *     id, to be used as drag source/drop target.
+ * @throws Error Thrown if called on instance of abstract class
+ */
+goog.fx.AbstractDragDrop.prototype.addItem = goog.abstractMethod;
+
 
+/**
+ * Associate drop target with drag element.
+ *
+ * @param {goog.fx.AbstractDragDrop} target Target to add.
+ */
+goog.fx.AbstractDragDrop.prototype.addTarget = function(target) {
+  this.targets_.push(target);
+  target.isTarget_ = true;
+  this.isSource_ = true;
 };
-goog.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @private
- * @return {number} End.
+ * Sets the SCROLL event target to make drag element follow scrolling.
+ *
+ * @param {EventTarget} scrollTarget The element that dispatches SCROLL events.
  */
-ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ =
-    function(flatCoordinates, offset, ends, stride) {
-  var state = this.state_;
-  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
-  this.instructions.push(beginPathInstruction);
-  this.hitDetectionInstructions.push(beginPathInstruction);
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var myBegin = this.coordinates.length;
-    var myEnd = this.appendFlatCoordinates(
-        flatCoordinates, offset, end, stride, true);
-    var moveToLineToInstruction =
-        [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
-    var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH];
-    this.instructions.push(moveToLineToInstruction, closePathInstruction);
-    this.hitDetectionInstructions.push(moveToLineToInstruction,
-        closePathInstruction);
-    offset = end;
-  }
-  // FIXME is it quicker to fill and stroke each polygon individually,
-  // FIXME or all polygons together?
-  var fillInstruction = [ol.render.canvas.Instruction.FILL];
-  this.hitDetectionInstructions.push(fillInstruction);
-  if (goog.isDef(state.fillStyle)) {
-    this.instructions.push(fillInstruction);
-  }
-  if (goog.isDef(state.strokeStyle)) {
-    goog.asserts.assert(goog.isDef(state.lineWidth));
-    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
-    this.instructions.push(strokeInstruction);
-    this.hitDetectionInstructions.push(strokeInstruction);
-  }
-  return offset;
+goog.fx.AbstractDragDrop.prototype.setScrollTarget = function(scrollTarget) {
+  this.scrollTarget_ = scrollTarget;
 };
 
 
 /**
- * @inheritDoc
+ * Initialize drag and drop functionality for sources/targets already added.
+ * Sources/targets added after init has been called will initialize themselves
+ * one by one.
  */
-ol.render.canvas.PolygonReplay.prototype.drawCircleGeometry =
-    function(circleGeometry, data) {
-  var state = this.state_;
-  goog.asserts.assert(!goog.isNull(state));
-  var fillStyle = state.fillStyle;
-  var strokeStyle = state.strokeStyle;
-  if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) {
+goog.fx.AbstractDragDrop.prototype.init = function() {
+  if (this.initialized_) {
     return;
   }
-  if (goog.isDef(strokeStyle)) {
-    goog.asserts.assert(goog.isDef(state.lineWidth));
-  }
-  ol.extent.extend(this.extent_, circleGeometry.getExtent());
-  this.setFillStrokeStyles_();
-  this.beginGeometry(circleGeometry, data);
-  // 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 (goog.isDef(state.strokeStyle)) {
-    this.hitDetectionInstructions.push(
-        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
-         state.miterLimit, state.lineDash]);
+  for (var item, i = 0; item = this.items_[i]; i++) {
+    this.initItem(item);
   }
-  var flatCoordinates = circleGeometry.getFlatCoordinates();
-  var stride = circleGeometry.getStride();
-  var myBegin = this.coordinates.length;
-  this.appendFlatCoordinates(
-      flatCoordinates, 0, flatCoordinates.length, stride, false);
-  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
-  var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin];
-  this.instructions.push(beginPathInstruction, circleInstruction);
-  this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction);
-  var fillInstruction = [ol.render.canvas.Instruction.FILL];
-  this.hitDetectionInstructions.push(fillInstruction);
-  if (goog.isDef(state.fillStyle)) {
-    this.instructions.push(fillInstruction);
+
+  this.initialized_ = true;
+};
+
+
+/**
+ * Initializes a single item.
+ *
+ * @param {goog.fx.DragDropItem} item Item to initialize.
+ * @protected
+ */
+goog.fx.AbstractDragDrop.prototype.initItem = function(item) {
+  if (this.isSource_) {
+    goog.events.listen(item.element, goog.events.EventType.MOUSEDOWN,
+                       item.mouseDown_, false, item);
+    if (this.sourceClass_) {
+      goog.dom.classlist.add(
+          goog.asserts.assert(item.element), this.sourceClass_);
+    }
   }
-  if (goog.isDef(state.strokeStyle)) {
-    goog.asserts.assert(goog.isDef(state.lineWidth));
-    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
-    this.instructions.push(strokeInstruction);
-    this.hitDetectionInstructions.push(strokeInstruction);
+
+  if (this.isTarget_ && this.targetClass_) {
+    goog.dom.classlist.add(
+        goog.asserts.assert(item.element), this.targetClass_);
   }
-  this.endGeometry(circleGeometry, data);
 };
 
 
 /**
- * @inheritDoc
+ * Called when removing an item. Removes event listeners and classes.
+ *
+ * @param {goog.fx.DragDropItem} item Item to dispose.
+ * @protected
  */
-ol.render.canvas.PolygonReplay.prototype.drawPolygonGeometry =
-    function(polygonGeometry, data) {
-  var state = this.state_;
-  goog.asserts.assert(!goog.isNull(state));
-  var fillStyle = state.fillStyle;
-  var strokeStyle = state.strokeStyle;
-  if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) {
-    return;
+goog.fx.AbstractDragDrop.prototype.disposeItem = function(item) {
+  if (this.isSource_) {
+    goog.events.unlisten(item.element, goog.events.EventType.MOUSEDOWN,
+                         item.mouseDown_, false, item);
+    if (this.sourceClass_) {
+      goog.dom.classlist.remove(
+          goog.asserts.assert(item.element), this.sourceClass_);
+    }
   }
-  if (goog.isDef(strokeStyle)) {
-    goog.asserts.assert(goog.isDef(state.lineWidth));
+  if (this.isTarget_ && this.targetClass_) {
+    goog.dom.classlist.remove(
+        goog.asserts.assert(item.element), this.targetClass_);
   }
-  ol.extent.extend(this.extent_, polygonGeometry.getExtent());
-  this.setFillStrokeStyles_();
-  this.beginGeometry(polygonGeometry, data);
-  // 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 (goog.isDef(state.strokeStyle)) {
-    this.hitDetectionInstructions.push(
-        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
-         state.miterLimit, state.lineDash]);
+  item.dispose();
+};
+
+
+/**
+ * Removes all items.
+ */
+goog.fx.AbstractDragDrop.prototype.removeItems = function() {
+  for (var item, i = 0; item = this.items_[i]; i++) {
+    this.disposeItem(item);
   }
-  var ends = polygonGeometry.getEnds();
-  var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
-  var stride = polygonGeometry.getStride();
-  this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
-  this.endGeometry(polygonGeometry, data);
+  this.items_.length = 0;
 };
 
 
 /**
- * @inheritDoc
+ * Starts a drag event for an item if the mouse button stays pressed and the
+ * cursor moves a few pixels. Allows dragging of items without first having to
+ * register them with addItem.
+ *
+ * @param {goog.events.BrowserEvent} event Mouse down event.
+ * @param {goog.fx.DragDropItem} item Item that's being dragged.
  */
-ol.render.canvas.PolygonReplay.prototype.drawMultiPolygonGeometry =
-    function(multiPolygonGeometry, data) {
-  var state = this.state_;
-  goog.asserts.assert(!goog.isNull(state));
-  var fillStyle = state.fillStyle;
-  var strokeStyle = state.strokeStyle;
-  if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) {
+goog.fx.AbstractDragDrop.prototype.maybeStartDrag = function(event, item) {
+  item.maybeStartDrag_(event, item.element);
+};
+
+
+/**
+ * Event handler that's used to start drag.
+ *
+ * @param {goog.events.BrowserEvent} event Mouse move event.
+ * @param {goog.fx.DragDropItem} item Item that's being dragged.
+ */
+goog.fx.AbstractDragDrop.prototype.startDrag = function(event, item) {
+
+  // Prevent a new drag operation from being started if another one is already
+  // in progress (could happen if the mouse was released outside of the
+  // document).
+  if (this.dragItem_) {
     return;
   }
-  if (goog.isDef(strokeStyle)) {
-    goog.asserts.assert(goog.isDef(state.lineWidth));
-  }
-  ol.extent.extend(this.extent_, multiPolygonGeometry.getExtent());
-  this.setFillStrokeStyles_();
-  this.beginGeometry(multiPolygonGeometry, data);
-  // 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 (goog.isDef(state.strokeStyle)) {
-    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.dragItem_ = item;
+
+  // Dispatch DRAGSTART event
+  var dragStartEvent = new goog.fx.DragDropEvent(
+      goog.fx.AbstractDragDrop.EventType.DRAGSTART, this, this.dragItem_);
+  if (this.dispatchEvent(dragStartEvent) == false) {
+    this.dragItem_ = null;
+    return;
   }
-  this.endGeometry(multiPolygonGeometry, data);
+
+  // Get the source element and create a drag element for it.
+  var el = item.getCurrentDragElement();
+  this.dragEl_ = this.createDragElement(el);
+  var doc = goog.dom.getOwnerDocument(el);
+  doc.body.appendChild(this.dragEl_);
+
+  this.dragger_ = this.createDraggerFor(el, this.dragEl_, event);
+  this.dragger_.setScrollTarget(this.scrollTarget_);
+
+  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG,
+                     this.moveDrag_, false, this);
+
+  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.END,
+                     this.endDrag, false, this);
+
+  // IE may issue a 'selectstart' event when dragging over an iframe even when
+  // default mousemove behavior is suppressed. If the default selectstart
+  // behavior is not suppressed, elements dragged over will show as selected.
+  goog.events.listen(doc.body, goog.events.EventType.SELECTSTART,
+                     this.suppressSelect_);
+
+  this.recalculateDragTargets();
+  this.recalculateScrollableContainers();
+  this.activeTarget_ = null;
+  this.initScrollableContainerListeners_();
+  this.dragger_.startDrag(event);
+
+  event.preventDefault();
 };
 
 
 /**
- * @inheritDoc
+ * Recalculates the geometry of this source's drag targets.  Call this
+ * if the position or visibility of a drag target has changed during
+ * a drag, or if targets are added or removed.
+ *
+ * TODO(user): this is an expensive operation;  more efficient APIs
+ * may be necessary.
  */
-ol.render.canvas.PolygonReplay.prototype.finish = function() {
-  goog.asserts.assert(!goog.isNull(this.state_));
-  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);
+goog.fx.AbstractDragDrop.prototype.recalculateDragTargets = function() {
+  this.targetList_ = [];
+  for (var target, i = 0; target = this.targets_[i]; i++) {
+    for (var itm, j = 0; itm = target.items_[j]; j++) {
+      this.addDragTarget_(target, itm);
     }
   }
+  if (!this.targetBox_) {
+    this.targetBox_ = new goog.math.Box(0, 0, 0, 0);
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Recalculates the current scroll positions of scrollable containers and
+ * allocates targets. Call this if the position of a container changed or if
+ * targets are added or removed.
  */
-ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() {
-  var extent = this.maxExtent;
-  if (this.maxLineWidth) {
-    extent = ol.extent.buffer(
-        extent, this.resolution * (this.maxLineWidth + 1) / 2);
+goog.fx.AbstractDragDrop.prototype.recalculateScrollableContainers =
+    function() {
+  var container, i, j, target;
+  for (i = 0; container = this.scrollableContainers_[i]; i++) {
+    container.containedTargets_ = [];
+    container.savedScrollLeft_ = container.element_.scrollLeft;
+    container.savedScrollTop_ = container.element_.scrollTop;
+    var pos = goog.style.getPageOffset(container.element_);
+    var size = goog.style.getSize(container.element_);
+    container.box_ = new goog.math.Box(pos.y, pos.x + size.width,
+                                       pos.y + size.height, pos.x);
+  }
+
+  for (i = 0; target = this.targetList_[i]; i++) {
+    for (j = 0; container = this.scrollableContainers_[j]; j++) {
+      if (goog.dom.contains(container.element_, target.element_)) {
+        container.containedTargets_.push(target);
+        target.scrollableContainer_ = container;
+      }
+    }
   }
-  return extent;
 };
 
 
 /**
- * @inheritDoc
+ * Creates the Dragger for the drag element.
+ * @param {Element} sourceEl Drag source element.
+ * @param {Element} el the element created by createDragElement().
+ * @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
+ * @return {!goog.fx.Dragger} The new Dragger.
+ * @protected
  */
-ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle =
-    function(fillStyle, strokeStyle) {
-  goog.asserts.assert(!goog.isNull(this.state_));
-  goog.asserts.assert(!goog.isNull(fillStyle) || !goog.isNull(strokeStyle));
-  var state = this.state_;
-  if (!goog.isNull(fillStyle)) {
-    var fillStyleColor = fillStyle.getColor();
-    state.fillStyle = ol.color.asString(!goog.isNull(fillStyleColor) ?
-        fillStyleColor : ol.render.canvas.defaultFillStyle);
-  } else {
-    state.fillStyle = undefined;
-  }
-  if (!goog.isNull(strokeStyle)) {
-    var strokeStyleColor = strokeStyle.getColor();
-    state.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ?
-        strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
-    var strokeStyleLineCap = strokeStyle.getLineCap();
-    state.lineCap = goog.isDef(strokeStyleLineCap) ?
-        strokeStyleLineCap : ol.render.canvas.defaultLineCap;
-    var strokeStyleLineDash = strokeStyle.getLineDash();
-    state.lineDash = !goog.isNull(strokeStyleLineDash) ?
-        strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
-    var strokeStyleLineJoin = strokeStyle.getLineJoin();
-    state.lineJoin = goog.isDef(strokeStyleLineJoin) ?
-        strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
-    var strokeStyleWidth = strokeStyle.getWidth();
-    state.lineWidth = goog.isDef(strokeStyleWidth) ?
-        strokeStyleWidth : ol.render.canvas.defaultLineWidth;
-    var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
-    state.miterLimit = goog.isDef(strokeStyleMiterLimit) ?
-        strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-    this.maxLineWidth = Math.max(this.maxLineWidth, state.lineWidth);
-  } else {
-    state.strokeStyle = undefined;
-    state.lineCap = undefined;
-    state.lineDash = null;
-    state.lineJoin = undefined;
-    state.lineWidth = undefined;
-    state.miterLimit = undefined;
-  }
+goog.fx.AbstractDragDrop.prototype.createDraggerFor =
+    function(sourceEl, el, event) {
+  // Position the drag element.
+  var pos = this.getDragElementPosition(sourceEl, el, event);
+  el.style.position = 'absolute';
+  el.style.left = pos.x + 'px';
+  el.style.top = pos.y + 'px';
+  return new goog.fx.Dragger(el);
 };
 
 
 /**
- * @private
+ * Event handler that's used to stop drag. Fires a drop event if over a valid
+ * target.
+ *
+ * @param {goog.fx.DragEvent} event Drag event.
  */
-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 (goog.isDef(fillStyle) && state.currentFillStyle != fillStyle) {
-    this.instructions.push(
-        [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]);
-    state.currentFillStyle = state.fillStyle;
-  }
-  if (goog.isDef(strokeStyle)) {
-    goog.asserts.assert(goog.isDef(lineCap));
-    goog.asserts.assert(!goog.isNull(lineDash));
-    goog.asserts.assert(goog.isDef(lineJoin));
-    goog.asserts.assert(goog.isDef(lineWidth));
-    goog.asserts.assert(goog.isDef(miterLimit));
-    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.fx.AbstractDragDrop.prototype.endDrag = function(event) {
+  var activeTarget = event.dragCanceled ? null : this.activeTarget_;
+  if (activeTarget && activeTarget.target_) {
+    var clientX = event.clientX;
+    var clientY = event.clientY;
+    var scroll = this.getScrollPos();
+    var x = clientX + scroll.x;
+    var y = clientY + scroll.y;
+
+    var subtarget;
+    // If a subtargeting function is enabled get the current subtarget
+    if (this.subtargetFunction_) {
+      subtarget = this.subtargetFunction_(activeTarget.item_,
+          activeTarget.box_, x, y);
     }
+
+    var dragEvent = new goog.fx.DragDropEvent(
+        goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_,
+        activeTarget.target_, activeTarget.item_, activeTarget.element_,
+        clientX, clientY, x, y);
+    this.dispatchEvent(dragEvent);
+
+    var dropEvent = new goog.fx.DragDropEvent(
+        goog.fx.AbstractDragDrop.EventType.DROP, this, this.dragItem_,
+        activeTarget.target_, activeTarget.item_, activeTarget.element_,
+        clientX, clientY, x, y, subtarget);
+    activeTarget.target_.dispatchEvent(dropEvent);
   }
-};
 
+  var dragEndEvent = new goog.fx.DragDropEvent(
+      goog.fx.AbstractDragDrop.EventType.DRAGEND, this, this.dragItem_);
+  this.dispatchEvent(dragEndEvent);
+
+  goog.events.unlisten(this.dragger_, goog.fx.Dragger.EventType.DRAG,
+                       this.moveDrag_, false, this);
+  goog.events.unlisten(this.dragger_, goog.fx.Dragger.EventType.END,
+                       this.endDrag, false, this);
+  var doc = goog.dom.getOwnerDocument(this.dragItem_.getCurrentDragElement());
+  goog.events.unlisten(doc.body, goog.events.EventType.SELECTSTART,
+                       this.suppressSelect_);
+
+
+  this.afterEndDrag(this.activeTarget_ ? this.activeTarget_.item_ : null);
+};
 
 
 /**
- * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
+ * Called after a drag operation has finished.
+ *
+ * @param {goog.fx.DragDropItem=} opt_dropTarget Target for successful drop.
  * @protected
- * @struct
  */
-ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution) {
+goog.fx.AbstractDragDrop.prototype.afterEndDrag = function(opt_dropTarget) {
+  this.disposeDrag();
+};
 
-  goog.base(this, tolerance, maxExtent, resolution);
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.FillState}
-   */
-  this.replayFillState_ = null;
+/**
+ * Called once a drag operation has finished. Removes event listeners and
+ * elements.
+ *
+ * @protected
+ */
+goog.fx.AbstractDragDrop.prototype.disposeDrag = function() {
+  this.disposeScrollableContainerListeners_();
+  this.dragger_.dispose();
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.StrokeState}
-   */
-  this.replayStrokeState_ = null;
+  goog.dom.removeNode(this.dragEl_);
+  delete this.dragItem_;
+  delete this.dragEl_;
+  delete this.dragger_;
+  delete this.targetList_;
+  delete this.activeTarget_;
+};
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.TextState}
-   */
-  this.replayTextState_ = null;
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.text_ = '';
+/**
+ * Event handler for drag events. Determines the active drop target, if any, and
+ * fires dragover and dragout events appropriately.
+ *
+ * @param {goog.fx.DragEvent} event Drag event.
+ * @private
+ */
+goog.fx.AbstractDragDrop.prototype.moveDrag_ = function(event) {
+  var position = this.getEventPosition(event);
+  var x = position.x;
+  var y = position.y;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetX_ = 0;
+  // Check if we're still inside the bounds of the active target, if not fire
+  // a dragout event and proceed to find a new target.
+  var activeTarget = this.activeTarget_;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetY_ = 0;
+  var subtarget;
+  if (activeTarget) {
+    // If a subtargeting function is enabled get the current subtarget
+    if (this.subtargetFunction_ && activeTarget.target_) {
+      subtarget = this.subtargetFunction_(activeTarget.item_,
+          activeTarget.box_, x, y);
+    }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textRotation_ = 0;
+    if (activeTarget.box_.contains(position) &&
+        subtarget == this.activeSubtarget_) {
+      return;
+    }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textScale_ = 0;
+    if (activeTarget.target_) {
+      var sourceDragOutEvent = new goog.fx.DragDropEvent(
+          goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_,
+          activeTarget.target_, activeTarget.item_, activeTarget.element_);
+      this.dispatchEvent(sourceDragOutEvent);
+
+      // The event should be dispatched the by target DragDrop so that the
+      // target DragDrop can manage these events without having to know what
+      // sources this is a target for.
+      var targetDragOutEvent = new goog.fx.DragDropEvent(
+          goog.fx.AbstractDragDrop.EventType.DRAGOUT,
+          this,
+          this.dragItem_,
+          activeTarget.target_,
+          activeTarget.item_,
+          activeTarget.element_,
+          undefined,
+          undefined,
+          undefined,
+          undefined,
+          this.activeSubtarget_);
+      activeTarget.target_.dispatchEvent(targetDragOutEvent);
+    }
+    this.activeSubtarget_ = subtarget;
+    this.activeTarget_ = null;
+  }
+
+  // Check if inside target box
+  if (this.targetBox_.contains(position)) {
+    // Search for target and fire a dragover event if found
+    activeTarget = this.activeTarget_ = this.getTargetFromPosition_(position);
+    if (activeTarget && activeTarget.target_) {
+      // If a subtargeting function is enabled get the current subtarget
+      if (this.subtargetFunction_) {
+        subtarget = this.subtargetFunction_(activeTarget.item_,
+            activeTarget.box_, x, y);
+      }
+      var sourceDragOverEvent = new goog.fx.DragDropEvent(
+          goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
+          activeTarget.target_, activeTarget.item_, activeTarget.element_);
+      sourceDragOverEvent.subtarget = subtarget;
+      this.dispatchEvent(sourceDragOverEvent);
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.FillState}
-   */
-  this.textFillState_ = null;
+      // The event should be dispatched by the target DragDrop so that the
+      // target DragDrop can manage these events without having to know what
+      // sources this is a target for.
+      var targetDragOverEvent = new goog.fx.DragDropEvent(
+          goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
+          activeTarget.target_, activeTarget.item_, activeTarget.element_,
+          event.clientX, event.clientY, undefined, undefined, subtarget);
+      activeTarget.target_.dispatchEvent(targetDragOverEvent);
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.StrokeState}
-   */
-  this.textStrokeState_ = null;
+    } else if (!activeTarget) {
+      // If no target was found create a dummy one so we won't have to iterate
+      // over all possible targets for every move event.
+      this.activeTarget_ = this.maybeCreateDummyTargetForPosition_(x, y);
+    }
+  }
+};
 
-  /**
-   * @private
-   * @type {?ol.render.canvas.TextState}
-   */
-  this.textState_ = null;
 
+/**
+ * Event handler for suppressing selectstart events. Selecting should be
+ * disabled while dragging.
+ *
+ * @param {goog.events.Event} event The selectstart event to suppress.
+ * @return {boolean} Whether to perform default behavior.
+ * @private
+ */
+goog.fx.AbstractDragDrop.prototype.suppressSelect_ = function(event) {
+  return false;
 };
-goog.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
 
 
 /**
- * @inheritDoc
+ * Sets up listeners for the scrollable containers that keep track of their
+ * scroll positions.
+ * @private
  */
-ol.render.canvas.TextReplay.prototype.drawText =
-    function(flatCoordinates, offset, end, stride, geometry, data) {
-  if (this.text_ === '' ||
-      goog.isNull(this.textState_) ||
-      (goog.isNull(this.textFillState_) &&
-       goog.isNull(this.textStrokeState_))) {
-    return;
-  }
-  ol.extent.extendFlatCoordinates(
-      this.extent_, flatCoordinates, offset, end, stride);
-  if (!goog.isNull(this.textFillState_)) {
-    this.setReplayFillState_(this.textFillState_);
-  }
-  if (!goog.isNull(this.textStrokeState_)) {
-    this.setReplayStrokeState_(this.textStrokeState_);
+goog.fx.AbstractDragDrop.prototype.initScrollableContainerListeners_ =
+    function() {
+  var container, i;
+  for (i = 0; container = this.scrollableContainers_[i]; i++) {
+    goog.events.listen(container.element_, goog.events.EventType.SCROLL,
+        this.containerScrollHandler_, false, this);
   }
-  this.setReplayTextState_(this.textState_);
-  this.beginGeometry(geometry, data);
-  var myBegin = this.coordinates.length;
-  var myEnd =
-      this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false);
-  var fill = !goog.isNull(this.textFillState_);
-  var stroke = !goog.isNull(this.textStrokeState_);
-  var drawTextInstruction = [
-    ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_,
-    this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_,
-    fill, stroke];
-  this.instructions.push(drawTextInstruction);
-  this.hitDetectionInstructions.push(drawTextInstruction);
-  this.endGeometry(geometry, data);
 };
 
 
 /**
- * @param {ol.render.canvas.FillState} fillState Fill state.
+ * Cleans up the scrollable container listeners.
  * @private
  */
-ol.render.canvas.TextReplay.prototype.setReplayFillState_ =
-    function(fillState) {
-  var replayFillState = this.replayFillState_;
-  if (!goog.isNull(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 (goog.isNull(replayFillState)) {
-    this.replayFillState_ = {
-      fillStyle: fillState.fillStyle
-    };
-  } else {
-    replayFillState.fillStyle = fillState.fillStyle;
+goog.fx.AbstractDragDrop.prototype.disposeScrollableContainerListeners_ =
+    function() {
+  for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
+    goog.events.unlisten(container.element_, 'scroll',
+        this.containerScrollHandler_, false, this);
+    container.containedTargets_ = [];
   }
 };
 
 
 /**
- * @param {ol.render.canvas.StrokeState} strokeState Stroke state.
- * @private
+ * Makes drag and drop aware of a target container that could scroll mid drag.
+ * @param {Element} element The scroll container.
  */
-ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ =
-    function(strokeState) {
-  var replayStrokeState = this.replayStrokeState_;
-  if (!goog.isNull(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 (goog.isNull(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;
-  }
+goog.fx.AbstractDragDrop.prototype.addScrollableContainer = function(element) {
+  this.scrollableContainers_.push(new goog.fx.ScrollableContainer_(element));
 };
 
 
 /**
- * @param {ol.render.canvas.TextState} textState Text state.
- * @private
+ * Removes all scrollable containers.
  */
-ol.render.canvas.TextReplay.prototype.setReplayTextState_ =
-    function(textState) {
-  var replayTextState = this.replayTextState_;
-  if (!goog.isNull(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 (goog.isNull(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;
-  }
+goog.fx.AbstractDragDrop.prototype.removeAllScrollableContainers = function() {
+  this.disposeScrollableContainerListeners_();
+  this.scrollableContainers_ = [];
 };
 
 
 /**
- * @inheritDoc
+ * Event handler for containers scrolling.
+ * @param {goog.events.Event} e The event.
+ * @private
  */
-ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
-  if (goog.isNull(textStyle)) {
-    this.text_ = '';
-  } else {
-    var textFillStyle = textStyle.getFill();
-    if (goog.isNull(textFillStyle)) {
-      this.textFillState_ = null;
-    } else {
-      var textFillStyleColor = textFillStyle.getColor();
-      var fillStyle = ol.color.asString(!goog.isNull(textFillStyleColor) ?
-          textFillStyleColor : ol.render.canvas.defaultFillStyle);
-      if (goog.isNull(this.textFillState_)) {
-        this.textFillState_ = {
-          fillStyle: fillStyle
-        };
-      } else {
-        var textFillState = this.textFillState_;
-        textFillState.fillStyle = fillStyle;
+goog.fx.AbstractDragDrop.prototype.containerScrollHandler_ = function(e) {
+  for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
+    if (e.target == container.element_) {
+      var deltaTop = container.savedScrollTop_ - container.element_.scrollTop;
+      var deltaLeft =
+          container.savedScrollLeft_ - container.element_.scrollLeft;
+      container.savedScrollTop_ = container.element_.scrollTop;
+      container.savedScrollLeft_ = container.element_.scrollLeft;
+
+      // When the container scrolls, it's possible that one of the targets will
+      // move to the region contained by the dummy target. Since we don't know
+      // which sides (if any) of the dummy target are defined by targets
+      // contained by this container, we are conservative and just shrink it.
+      if (this.dummyTarget_ && this.activeTarget_ == this.dummyTarget_) {
+        if (deltaTop > 0) {
+          this.dummyTarget_.box_.top += deltaTop;
+        } else {
+          this.dummyTarget_.box_.bottom += deltaTop;
+        }
+        if (deltaLeft > 0) {
+          this.dummyTarget_.box_.left += deltaLeft;
+        } else {
+          this.dummyTarget_.box_.right += deltaLeft;
+        }
       }
-    }
-    var textStrokeStyle = textStyle.getStroke();
-    if (goog.isNull(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 = goog.isDef(textStrokeStyleLineCap) ?
-          textStrokeStyleLineCap : ol.render.canvas.defaultLineCap;
-      var lineDash = goog.isDefAndNotNull(textStrokeStyleLineDash) ?
-          textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
-      var lineJoin = goog.isDef(textStrokeStyleLineJoin) ?
-          textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
-      var lineWidth = goog.isDef(textStrokeStyleWidth) ?
-          textStrokeStyleWidth : ol.render.canvas.defaultLineWidth;
-      var miterLimit = goog.isDef(textStrokeStyleMiterLimit) ?
-          textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-      var strokeStyle = ol.color.asString(!goog.isNull(textStrokeStyleColor) ?
-          textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle);
-      if (goog.isNull(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;
+      for (var j = 0, target; target = container.containedTargets_[j]; j++) {
+        var box = target.box_;
+        box.top += deltaTop;
+        box.left += deltaLeft;
+        box.bottom += deltaTop;
+        box.right += deltaLeft;
+
+        this.calculateTargetBox_(box);
       }
     }
-    var textFont = textStyle.getFont();
-    var textOffsetX = textStyle.getOffsetX();
-    var textOffsetY = textStyle.getOffsetY();
-    var textRotation = textStyle.getRotation();
-    var textScale = textStyle.getScale();
-    var textText = textStyle.getText();
-    var textTextAlign = textStyle.getTextAlign();
-    var textTextBaseline = textStyle.getTextBaseline();
-    var font = goog.isDef(textFont) ?
-        textFont : ol.render.canvas.defaultFont;
-    var textAlign = goog.isDef(textTextAlign) ?
-        textTextAlign : ol.render.canvas.defaultTextAlign;
-    var textBaseline = goog.isDef(textTextBaseline) ?
-        textTextBaseline : ol.render.canvas.defaultTextBaseline;
-    if (goog.isNull(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_ = goog.isDef(textText) ? textText : '';
-    this.textOffsetX_ = goog.isDef(textOffsetX) ? textOffsetX : 0;
-    this.textOffsetY_ = goog.isDef(textOffsetY) ? textOffsetY : 0;
-    this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0;
-    this.textScale_ = goog.isDef(textScale) ? textScale : 1;
   }
+  this.dragger_.onScroll_(e);
 };
 
 
-
 /**
- * @constructor
- * @implements {ol.render.IReplayGroup}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Max extent.
- * @param {number} resolution Resolution.
- * @struct
+ * Set a function that provides subtargets. A subtargeting function
+ * returns an arbitrary identifier for each subtarget of an element.
+ * DnD code will generate additional drag over / out events when
+ * switching from subtarget to subtarget. This is useful for instance
+ * if you are interested if you are on the top half or the bottom half
+ * of the element.
+ * The provided function will be given the DragDropItem, box, x, y
+ * box is the current window coordinates occupied by element
+ * x, y is the mouse position in window coordinates
+ *
+ * @param {Function} f The new subtarget function.
  */
-ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution) {
+goog.fx.AbstractDragDrop.prototype.setSubtargetFunction = function(f) {
+  this.subtargetFunction_ = f;
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.tolerance_ = tolerance;
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.maxExtent_ = maxExtent;
+/**
+ * Creates an element for the item being dragged.
+ *
+ * @param {Element} sourceEl Drag source element.
+ * @return {Element} The new drag element.
+ */
+goog.fx.AbstractDragDrop.prototype.createDragElement = function(sourceEl) {
+  var dragEl = this.createDragElementInternal(sourceEl);
+  goog.asserts.assert(dragEl);
+  if (this.dragClass_) {
+    goog.dom.classlist.add(dragEl, this.dragClass_);
+  }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.resolution_ = resolution;
+  return dragEl;
+};
 
-  /**
-   * @private
-   * @type {Object.<string,
-   *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
-   */
-  this.replaysByZIndex_ = {};
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1);
+/**
+ * Returns the position for the drag element.
+ *
+ * @param {Element} el Drag source element.
+ * @param {Element} dragEl The dragged element created by createDragElement().
+ * @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
+ * @return {!goog.math.Coordinate} The position for the drag element.
+ */
+goog.fx.AbstractDragDrop.prototype.getDragElementPosition =
+    function(el, dragEl, event) {
+  var pos = goog.style.getPageOffset(el);
 
-  /**
-   * @private
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.hitDetectionTransform_ = goog.vec.Mat4.createNumber();
+  // Subtract margin from drag element position twice, once to adjust the
+  // position given by the original node and once for the drag node.
+  var marginBox = goog.style.getMarginBox(el);
+  pos.x -= (marginBox.left || 0) * 2;
+  pos.y -= (marginBox.top || 0) * 2;
 
+  return pos;
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {ol.Extent} extent Extent.
- * @param {number} pixelRatio Pixel ratio.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object} skippedFeaturesHash Ids of features to skip
- * @return {T|undefined} Callback result.
- * @template T
+ * Returns the dragger object.
+ *
+ * @return {goog.fx.Dragger} The dragger object used by this drag and drop
+ *     instance.
  */
-ol.render.canvas.ReplayGroup.prototype.replay = function(context, extent,
-    pixelRatio, transform, viewRotation, skippedFeaturesHash) {
-  /** @type {Array.<number>} */
-  var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number);
-  goog.array.sort(zs);
-  return this.replay_(zs, context, extent, pixelRatio, transform,
-      viewRotation, skippedFeaturesHash);
+goog.fx.AbstractDragDrop.prototype.getDragger = function() {
+  return this.dragger_;
 };
 
 
 /**
+ * Creates copy of node being dragged.
+ *
+ * @param {Element} sourceEl Element to copy.
+ * @return {!Element} The clone of {@code sourceEl}.
+ * @deprecated Use goog.fx.Dragger.cloneNode().
  * @private
- * @param {Array.<number>} zs Z-indices array.
- * @param {CanvasRenderingContext2D} context Context.
- * @param {ol.Extent} extent Extent.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object} skippedFeaturesHash Ids of features to skip
- * @param {function(ol.geom.Geometry, Object): T} geometryCallback Geometry
- *     callback.
- * @return {T|undefined} Callback result.
- * @template T
  */
-ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
-    zs, context, extent, transform, viewRotation, skippedFeaturesHash,
-    geometryCallback) {
-  var i, ii, replays, replayType, replay, result;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    replays = this.replaysByZIndex_[zs[i].toString()];
-    for (replayType in replays) {
-      replay = replays[replayType];
-      if (ol.extent.intersects(extent, replay.getExtent())) {
-        result = replay.replayHitDetection(context, transform, viewRotation,
-            skippedFeaturesHash, geometryCallback);
-        if (result) {
-          return result;
-        }
-      }
-    }
-  }
-  return undefined;
+goog.fx.AbstractDragDrop.prototype.cloneNode_ = function(sourceEl) {
+  return goog.fx.Dragger.cloneNode(sourceEl);
 };
 
 
 /**
- * @private
- * @param {Array.<number>} zs Z-indices array.
- * @param {CanvasRenderingContext2D} context Context.
- * @param {ol.Extent} extent Extent.
- * @param {number} pixelRatio Pixel ratio.
- * @param {goog.vec.Mat4.Number} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object} skippedFeaturesHash Ids of features to skip
- * @return {T|undefined} Callback result.
- * @template T
+ * Generates an element to follow the cursor during dragging, given a drag
+ * source element.  The default behavior is simply to clone the source element,
+ * but this may be overridden in subclasses.  This method is called by
+ * {@code createDragElement()} before the drag class is added.
+ *
+ * @param {Element} sourceEl Drag source element.
+ * @return {!Element} The new drag element.
+ * @protected
+ * @suppress {deprecated}
  */
-ol.render.canvas.ReplayGroup.prototype.replay_ = function(
-    zs, context, extent, pixelRatio, transform, viewRotation,
-    skippedFeaturesHash) {
-
-  var maxExtent = this.maxExtent_;
-  var minX = maxExtent[0];
-  var minY = maxExtent[1];
-  var maxX = maxExtent[2];
-  var maxY = maxExtent[3];
-  var flatClipCoords = ol.geom.flat.transform.transform2D(
-      [minX, minY, minX, maxY, maxX, maxY, maxX, minY],
-      0, 8, 2, transform);
-  context.save();
-  context.beginPath();
-  context.moveTo(flatClipCoords[0], flatClipCoords[1]);
-  context.lineTo(flatClipCoords[2], flatClipCoords[3]);
-  context.lineTo(flatClipCoords[4], flatClipCoords[5]);
-  context.lineTo(flatClipCoords[6], flatClipCoords[7]);
-  context.closePath();
-  context.clip();
-
-  var i, ii, j, jj, replays, replayType, replay, result;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    replays = this.replaysByZIndex_[zs[i].toString()];
-    for (j = 0, jj = ol.render.REPLAY_ORDER.length; j < jj; ++j) {
-      replay = replays[ol.render.REPLAY_ORDER[j]];
-      if (goog.isDef(replay) &&
-          ol.extent.intersects(extent, replay.getExtent())) {
-        result = replay.replay(context, pixelRatio, transform, viewRotation,
-            skippedFeaturesHash);
-        if (result) {
-          return result;
-        }
-      }
-    }
-  }
-
-  context.restore();
-  return undefined;
+goog.fx.AbstractDragDrop.prototype.createDragElementInternal =
+    function(sourceEl) {
+  return this.cloneNode_(sourceEl);
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {Object} skippedFeaturesHash Ids of features to skip
- * @param {function(ol.geom.Geometry, Object): T} callback Geometry callback.
- * @return {T|undefined} Callback result.
- * @template T
+ * Add possible drop target for current drag operation.
+ *
+ * @param {goog.fx.AbstractDragDrop} target Drag handler.
+ * @param {goog.fx.DragDropItem} item Item that's being dragged.
+ * @private
  */
-ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function(
-    extent, resolution, rotation, coordinate,
-    skippedFeaturesHash, callback) {
+goog.fx.AbstractDragDrop.prototype.addDragTarget_ = function(target, item) {
 
-  var transform = this.hitDetectionTransform_;
-  ol.vec.Mat4.makeTransform2D(transform, 0.5, 0.5,
-      1 / resolution, -1 / resolution, -rotation,
-      -coordinate[0], -coordinate[1]);
+  // Get all the draggable elements and add each one.
+  var draggableElements = item.getDraggableElements();
+  var targetList = this.targetList_;
+  for (var i = 0; i < draggableElements.length; i++) {
+    var draggableElement = draggableElements[i];
 
-  /** @type {Array.<number>} */
-  var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number);
-  goog.array.sort(zs, function(a, b) { return b - a; });
+    // Determine target position and dimension
+    var box = this.getElementBox(item, draggableElement);
 
-  var context = this.hitDetectionContext_;
-  context.clearRect(0, 0, 1, 1);
+    targetList.push(
+        new goog.fx.ActiveDropTarget_(box, target, item, draggableElement));
 
-  return this.replayHitDetection_(zs, context, extent, transform,
-      rotation, skippedFeaturesHash,
-      /**
-       * @param {ol.geom.Geometry} geometry Geometry.
-       * @param {Object} data Opaque data object.
-       * @return {?} Callback result.
-       */
-      function(geometry, data) {
-        var imageData = context.getImageData(0, 0, 1, 1).data;
-        if (imageData[3] > 0) {
-          var result = callback(geometry, data);
-          if (result) {
-            return result;
-          }
-          context.clearRect(0, 0, 1, 1);
-        }
-      });
+    this.calculateTargetBox_(box);
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Calculates the position and dimension of a draggable element.
+ *
+ * @param {goog.fx.DragDropItem} item Item that's being dragged.
+ * @param {Element} element The element to calculate the box.
+ *
+ * @return {!goog.math.Box} Box describing the position and dimension
+ *     of element.
+ * @protected
  */
-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();
-    }
-  }
+goog.fx.AbstractDragDrop.prototype.getElementBox = function(item, element) {
+  var pos = goog.style.getPageOffset(element);
+  var size = goog.style.getSize(element);
+  return new goog.math.Box(pos.y, pos.x + size.width, pos.y + size.height,
+      pos.x);
 };
 
 
 /**
- * @inheritDoc
+ * Calculate the outer bounds (the region all targets are inside).
+ *
+ * @param {goog.math.Box} box Box describing the position and dimension
+ *     of a drag target.
+ * @private
  */
-ol.render.canvas.ReplayGroup.prototype.getReplay =
-    function(zIndex, replayType) {
-  var zIndexKey = goog.isDef(zIndex) ? zIndex.toString() : '0';
-  var replays = this.replaysByZIndex_[zIndexKey];
-  if (!goog.isDef(replays)) {
-    replays = {};
-    this.replaysByZIndex_[zIndexKey] = replays;
-  }
-  var replay = replays[replayType];
-  if (!goog.isDef(replay)) {
-    var Constructor = ol.render.canvas.BATCH_CONSTRUCTORS_[replayType];
-    goog.asserts.assert(goog.isDef(Constructor));
-    replay = new Constructor(this.tolerance_, this.maxExtent_,
-        this.resolution_);
-    replays[replayType] = replay;
-  }
-  return replay;
-};
+goog.fx.AbstractDragDrop.prototype.calculateTargetBox_ = function(box) {
+  if (this.targetList_.length == 1) {
+    this.targetBox_ = new goog.math.Box(box.top, box.right,
+                                        box.bottom, box.left);
+  } else {
+    var tb = this.targetBox_;
+    tb.left = Math.min(box.left, tb.left);
+    tb.right = Math.max(box.right, tb.right);
+    tb.top = Math.min(box.top, tb.top);
+    tb.bottom = Math.max(box.bottom, tb.bottom);
+  }
+};
+
+
+/**
+ * Creates a dummy target for the given cursor position. The assumption is to
+ * create as big dummy target box as possible, the only constraints are:
+ * - The dummy target box cannot overlap any of real target boxes.
+ * - The dummy target has to contain a point with current mouse coordinates.
+ *
+ * NOTE: For performance reasons the box construction algorithm is kept simple
+ * and it is not optimal (see example below). Currently it is O(n) in regard to
+ * the number of real drop target boxes, but its result depends on the order
+ * of those boxes being processed (the order in which they're added to the
+ * targetList_ collection).
+ *
+ * The algorithm.
+ * a) Assumptions
+ * - Mouse pointer is in the bounding box of real target boxes.
+ * - None of the boxes have negative coordinate values.
+ * - Mouse pointer is not contained by any of "real target" boxes.
+ * - For targets inside a scrollable container, the box used is the
+ *   intersection of the scrollable container's box and the target's box.
+ *   This is because the part of the target that extends outside the scrollable
+ *   container should not be used in the clipping calculations.
+ *
+ * b) Outline
+ * - Initialize the fake target to the bounding box of real targets.
+ * - For each real target box - clip the fake target box so it does not contain
+ *   that target box, but does contain the mouse pointer.
+ *   -- Project the real target box, mouse pointer and fake target box onto
+ *      both axes and calculate the clipping coordinates.
+ *   -- Only one coordinate is used to clip the fake target box to keep the
+ *      fake target as big as possible.
+ *   -- If the projection of the real target box contains the mouse pointer,
+ *      clipping for a given axis is not possible.
+ *   -- If both clippings are possible, the clipping more distant from the
+ *      mouse pointer is selected to keep bigger fake target area.
+ * - Save the created fake target only if it has a big enough area.
+ *
+ *
+ * c) Example
+ * <pre>
+ *        Input:           Algorithm created box:        Maximum box:
+ * +---------------------+ +---------------------+ +---------------------+
+ * | B1      |        B2 | | B1               B2 | | B1               B2 |
+ * |         |           | |   +-------------+   | |+-------------------+|
+ * |---------x-----------| |   |             |   | ||                   ||
+ * |         |           | |   |             |   | ||                   ||
+ * |         |           | |   |             |   | ||                   ||
+ * |         |           | |   |             |   | ||                   ||
+ * |         |           | |   |             |   | ||                   ||
+ * |         |           | |   +-------------+   | |+-------------------+|
+ * | B4      |        B3 | | B4               B3 | | B4               B3 |
+ * +---------------------+ +---------------------+ +---------------------+
+ * </pre>
+ *
+ * @param {number} x Cursor position on the x-axis.
+ * @param {number} y Cursor position on the y-axis.
+ * @return {goog.fx.ActiveDropTarget_} Dummy drop target.
+ * @private
+ */
+goog.fx.AbstractDragDrop.prototype.maybeCreateDummyTargetForPosition_ =
+    function(x, y) {
+  if (!this.dummyTarget_) {
+    this.dummyTarget_ = new goog.fx.ActiveDropTarget_(this.targetBox_.clone());
+  }
+  var fakeTargetBox = this.dummyTarget_.box_;
+
+  // Initialize the fake target box to the bounding box of DnD targets.
+  fakeTargetBox.top = this.targetBox_.top;
+  fakeTargetBox.right = this.targetBox_.right;
+  fakeTargetBox.bottom = this.targetBox_.bottom;
+  fakeTargetBox.left = this.targetBox_.left;
+
+  // Clip the fake target based on mouse position and DnD target boxes.
+  for (var i = 0, target; target = this.targetList_[i]; i++) {
+    var box = target.box_;
+
+    if (target.scrollableContainer_) {
+      // If the target has a scrollable container, use the intersection of that
+      // container's box and the target's box.
+      var scrollBox = target.scrollableContainer_.box_;
+
+      box = new goog.math.Box(
+          Math.max(box.top, scrollBox.top),
+          Math.min(box.right, scrollBox.right),
+          Math.min(box.bottom, scrollBox.bottom),
+          Math.max(box.left, scrollBox.left));
+    }
+
+    // Calculate clipping coordinates for horizontal and vertical axis.
+    // The clipping coordinate is calculated by projecting fake target box,
+    // the mouse pointer and DnD target box onto an axis and checking how
+    // box projections overlap and if the projected DnD target box contains
+    // mouse pointer. The clipping coordinate cannot be computed and is set to
+    // a negative value if the projected DnD target contains the mouse pointer.
+
+    var horizontalClip = null; // Assume mouse is above or below the DnD box.
+    if (x >= box.right) { // Mouse is to the right of the DnD box.
+      // Clip the fake box only if the DnD box overlaps it.
+      horizontalClip = box.right > fakeTargetBox.left ?
+          box.right : fakeTargetBox.left;
+    } else if (x < box.left) { // Mouse is to the left of the DnD box.
+      // Clip the fake box only if the DnD box overlaps it.
+      horizontalClip = box.left < fakeTargetBox.right ?
+          box.left : fakeTargetBox.right;
+    }
+    var verticalClip = null;
+    if (y >= box.bottom) {
+      verticalClip = box.bottom > fakeTargetBox.top ?
+          box.bottom : fakeTargetBox.top;
+    } else if (y < box.top) {
+      verticalClip = box.top < fakeTargetBox.bottom ?
+          box.top : fakeTargetBox.bottom;
+    }
+
+    // If both clippings are possible, choose one that gives us larger distance
+    // to mouse pointer (mark the shorter clipping as impossible, by setting it
+    // to null).
+    if (!goog.isNull(horizontalClip) && !goog.isNull(verticalClip)) {
+      if (Math.abs(horizontalClip - x) > Math.abs(verticalClip - y)) {
+        verticalClip = null;
+      } else {
+        horizontalClip = null;
+      }
+    }
 
+    // Clip none or one of fake target box sides (at most one clipping
+    // coordinate can be active).
+    if (!goog.isNull(horizontalClip)) {
+      if (horizontalClip <= x) {
+        fakeTargetBox.left = horizontalClip;
+      } else {
+        fakeTargetBox.right = horizontalClip;
+      }
+    } else if (!goog.isNull(verticalClip)) {
+      if (verticalClip <= y) {
+        fakeTargetBox.top = verticalClip;
+      } else {
+        fakeTargetBox.bottom = verticalClip;
+      }
+    }
+  }
 
-/**
- * @inheritDoc
- */
-ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
-  return goog.object.isEmpty(this.replaysByZIndex_);
+  // Only return the new fake target if it is big enough.
+  return (fakeTargetBox.right - fakeTargetBox.left) *
+         (fakeTargetBox.bottom - fakeTargetBox.top) >=
+         goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ ?
+      this.dummyTarget_ : null;
 };
 
 
 /**
- * @const
+ * Returns the target for a given cursor position.
+ *
+ * @param {goog.math.Coordinate} position Cursor position.
+ * @return {Object} Target for position or null if no target was defined
+ *     for the given position.
  * @private
- * @type {Object.<ol.render.ReplayType,
- *                function(new: ol.render.canvas.Replay, number, ol.Extent,
- *                number)>}
- */
-ol.render.canvas.BATCH_CONSTRUCTORS_ = {
-  'Image': ol.render.canvas.ImageReplay,
-  'LineString': ol.render.canvas.LineStringReplay,
-  'Polygon': ol.render.canvas.PolygonReplay,
-  'Text': ol.render.canvas.TextReplay
-};
-
-goog.provide('ol.renderer.canvas.Layer');
-
-goog.require('goog.array');
-goog.require('goog.vec.Mat4');
-goog.require('ol.dom');
-goog.require('ol.layer.Layer');
-goog.require('ol.render.Event');
-goog.require('ol.render.EventType');
-goog.require('ol.render.canvas.Immediate');
-goog.require('ol.renderer.Layer');
-goog.require('ol.vec.Mat4');
-
-
-
-/**
- * @constructor
- * @extends {ol.renderer.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Layer} layer Layer.
- */
-ol.renderer.canvas.Layer = function(mapRenderer, layer) {
-
-  goog.base(this, mapRenderer, layer);
-
-  /**
-   * @private
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.transform_ = goog.vec.Mat4.createNumber();
-
-};
-goog.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.layer.LayerState} layerState Layer state.
- * @param {CanvasRenderingContext2D} context Context.
  */
-ol.renderer.canvas.Layer.prototype.composeFrame =
-    function(frameState, layerState, context) {
-
-  this.dispatchPreComposeEvent(context, frameState);
-
-  var image = this.getImage();
-  if (!goog.isNull(image)) {
-    var imageTransform = this.getImageTransform();
-    // for performance reasons, context.save / context.restore is not used
-    // to save and restore the transformation matrix and the opacity.
-    // see http://jsperf.com/context-save-restore-versus-variable
-    var alpha = context.globalAlpha;
-    context.globalAlpha = layerState.opacity;
-
-    // for performance reasons, context.setTransform is only used
-    // when the view is rotated. see http://jsperf.com/canvas-transform
-    if (frameState.viewState.rotation === 0) {
-      var dx = goog.vec.Mat4.getElement(imageTransform, 0, 3);
-      var dy = goog.vec.Mat4.getElement(imageTransform, 1, 3);
-      var dw = image.width * goog.vec.Mat4.getElement(imageTransform, 0, 0);
-      var dh = image.height * goog.vec.Mat4.getElement(imageTransform, 1, 1);
-      context.drawImage(image, 0, 0, +image.width, +image.height,
-          Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
-    } else {
-      context.setTransform(
-          goog.vec.Mat4.getElement(imageTransform, 0, 0),
-          goog.vec.Mat4.getElement(imageTransform, 1, 0),
-          goog.vec.Mat4.getElement(imageTransform, 0, 1),
-          goog.vec.Mat4.getElement(imageTransform, 1, 1),
-          goog.vec.Mat4.getElement(imageTransform, 0, 3),
-          goog.vec.Mat4.getElement(imageTransform, 1, 3));
-      context.drawImage(image, 0, 0);
-      context.setTransform(1, 0, 0, 1, 0, 0);
+goog.fx.AbstractDragDrop.prototype.getTargetFromPosition_ = function(position) {
+  for (var target, i = 0; target = this.targetList_[i]; i++) {
+    if (target.box_.contains(position)) {
+      if (target.scrollableContainer_) {
+        // If we have a scrollable container we will need to make sure
+        // we account for clipping of the scroll area
+        var box = target.scrollableContainer_.box_;
+        if (box.contains(position)) {
+          return target;
+        }
+      } else {
+        return target;
+      }
     }
-    context.globalAlpha = alpha;
   }
 
-  this.dispatchPostComposeEvent(context, frameState);
-
+  return null;
 };
 
 
 /**
- * @param {ol.render.EventType} type Event type.
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {goog.vec.Mat4.Number=} opt_transform Transform.
- * @private
+ * Checks whatever a given point is inside a given box.
+ *
+ * @param {number} x Cursor position on the x-axis.
+ * @param {number} y Cursor position on the y-axis.
+ * @param {goog.math.Box} box Box to check position against.
+ * @return {boolean} Whether the given point is inside {@code box}.
+ * @protected
+ * @deprecated Use goog.math.Box.contains.
  */
-ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ =
-    function(type, context, frameState, opt_transform) {
-  var layer = this.getLayer();
-  if (layer.hasListener(type)) {
-    var transform = goog.isDef(opt_transform) ?
-        opt_transform : this.getTransform(frameState);
-    var render = new ol.render.canvas.Immediate(
-        context, frameState.pixelRatio, frameState.extent, transform,
-        frameState.viewState.rotation);
-    var composeEvent = new ol.render.Event(type, layer, render, null,
-        frameState, context, null);
-    layer.dispatchEvent(composeEvent);
-    render.flush();
-  }
+goog.fx.AbstractDragDrop.prototype.isInside = function(x, y, box) {
+  return x >= box.left &&
+         x < box.right &&
+         y >= box.top &&
+         y < box.bottom;
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * Gets the scroll distance as a coordinate object, using
+ * the window of the current drag element's dom.
+ * @return {!goog.math.Coordinate} Object with scroll offsets 'x' and 'y'.
  * @protected
  */
-ol.renderer.canvas.Layer.prototype.dispatchPostComposeEvent =
-    function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context,
-      frameState, opt_transform);
+goog.fx.AbstractDragDrop.prototype.getScrollPos = function() {
+  return goog.dom.getDomHelper(this.dragEl_).getDocumentScroll();
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {goog.vec.Mat4.Number=} opt_transform Transform.
+ * Get the position of a drag event.
+ * @param {goog.fx.DragEvent} event Drag event.
+ * @return {!goog.math.Coordinate} Position of the event.
  * @protected
  */
-ol.renderer.canvas.Layer.prototype.dispatchPreComposeEvent =
-    function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context,
-      frameState, opt_transform);
+goog.fx.AbstractDragDrop.prototype.getEventPosition = function(event) {
+  var scroll = this.getScrollPos();
+  return new goog.math.Coordinate(event.clientX + scroll.x,
+                                  event.clientY + scroll.y);
 };
 
 
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {goog.vec.Mat4.Number=} opt_transform Transform.
- * @protected
- */
-ol.renderer.canvas.Layer.prototype.dispatchRenderEvent =
-    function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.EventType.RENDER, context,
-      frameState, opt_transform);
+/** @override */
+goog.fx.AbstractDragDrop.prototype.disposeInternal = function() {
+  goog.fx.AbstractDragDrop.base(this, 'disposeInternal');
+  this.removeItems();
 };
 
 
+
 /**
- * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
+ * Object representing a drag and drop event.
+ *
+ * @param {string} type Event type.
+ * @param {goog.fx.AbstractDragDrop} source Source drag drop object.
+ * @param {goog.fx.DragDropItem} sourceItem Source item.
+ * @param {goog.fx.AbstractDragDrop=} opt_target Target drag drop object.
+ * @param {goog.fx.DragDropItem=} opt_targetItem Target item.
+ * @param {Element=} opt_targetElement Target element.
+ * @param {number=} opt_clientX X-Position relative to the screen.
+ * @param {number=} opt_clientY Y-Position relative to the screen.
+ * @param {number=} opt_x X-Position relative to the viewport.
+ * @param {number=} opt_y Y-Position relative to the viewport.
+ * @param {Object=} opt_subtarget The currently active subtarget.
+ * @extends {goog.events.Event}
+ * @constructor
  */
-ol.renderer.canvas.Layer.prototype.getImage = goog.abstractMethod;
+goog.fx.DragDropEvent = function(type, source, sourceItem,
+                                 opt_target, opt_targetItem, opt_targetElement,
+                                 opt_clientX, opt_clientY, opt_x, opt_y,
+                                 opt_subtarget) {
+  // TODO(eae): Get rid of all the optional parameters and have the caller set
+  // the fields directly instead.
+  goog.fx.DragDropEvent.base(this, 'constructor', type);
 
+  /**
+   * Reference to the source goog.fx.AbstractDragDrop object.
+   * @type {goog.fx.AbstractDragDrop}
+   */
+  this.dragSource = source;
 
-/**
- * @return {!goog.vec.Mat4.Number} Image transform.
- */
-ol.renderer.canvas.Layer.prototype.getImageTransform = goog.abstractMethod;
+  /**
+   * Reference to the source goog.fx.DragDropItem object.
+   * @type {goog.fx.DragDropItem}
+   */
+  this.dragSourceItem = sourceItem;
 
+  /**
+   * Reference to the target goog.fx.AbstractDragDrop object.
+   * @type {goog.fx.AbstractDragDrop|undefined}
+   */
+  this.dropTarget = opt_target;
 
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @protected
- * @return {!goog.vec.Mat4.Number} Transform.
- */
-ol.renderer.canvas.Layer.prototype.getTransform = function(frameState) {
-  var viewState = frameState.viewState;
-  var pixelRatio = frameState.pixelRatio;
-  return ol.vec.Mat4.makeTransform2D(this.transform_,
-      pixelRatio * frameState.size[0] / 2,
-      pixelRatio * frameState.size[1] / 2,
-      pixelRatio / viewState.resolution,
-      -pixelRatio / viewState.resolution,
-      -viewState.rotation,
-      -viewState.center[0], -viewState.center[1]);
-};
+  /**
+   * Reference to the target goog.fx.DragDropItem object.
+   * @type {goog.fx.DragDropItem|undefined}
+   */
+  this.dropTargetItem = opt_targetItem;
 
+  /**
+   * The actual element of the drop target that is the target for this event.
+   * @type {Element|undefined}
+   */
+  this.dropTargetElement = opt_targetElement;
 
-/**
- * @param {ol.Size} size Size.
- * @return {boolean} True when the canvas with the current size does not exceed
- *     the maximum dimensions.
- */
-ol.renderer.canvas.Layer.testCanvasSize = (function() {
+  /**
+   * X-Position relative to the screen.
+   * @type {number|undefined}
+   */
+  this.clientX = opt_clientX;
 
   /**
-   * @type {CanvasRenderingContext2D}
+   * Y-Position relative to the screen.
+   * @type {number|undefined}
    */
-  var context = null;
+  this.clientY = opt_clientY;
 
   /**
-   * @type {ImageData}
+   * X-Position relative to the viewport.
+   * @type {number|undefined}
    */
-  var imageData = null;
+  this.viewportX = opt_x;
 
-  return function(size) {
-    if (goog.isNull(context)) {
-      context = ol.dom.createCanvasContext2D(1, 1);
-      imageData = context.createImageData(1, 1);
-      var data = imageData.data;
-      data[0] = 42;
-      data[1] = 84;
-      data[2] = 126;
-      data[3] = 255;
-    }
-    var canvas = context.canvas;
-    var good = size[0] <= canvas.width && size[1] <= canvas.height;
-    if (!good) {
-      canvas.width = size[0];
-      canvas.height = size[1];
-      var x = size[0] - 1;
-      var y = size[1] - 1;
-      context.putImageData(imageData, x, y);
-      var result = context.getImageData(x, y, 1, 1);
-      good = goog.array.equals(imageData.data, result.data);
-    }
-    return good;
-  };
-})();
+  /**
+   * Y-Position relative to the viewport.
+   * @type {number|undefined}
+   */
+  this.viewportY = opt_y;
 
-goog.provide('ol.renderer.canvas.ImageLayer');
+  /**
+   * The subtarget that is currently active if a subtargeting function
+   * is supplied.
+   * @type {Object|undefined}
+   */
+  this.subtarget = opt_subtarget;
+};
+goog.inherits(goog.fx.DragDropEvent, goog.events.Event);
 
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.vec.Mat4');
-goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
-goog.require('ol.ViewHint');
-goog.require('ol.extent');
-goog.require('ol.layer.Image');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.vec.Mat4');
+
+/** @override */
+goog.fx.DragDropEvent.prototype.disposeInternal = function() {
+};
 
 
 
 /**
+ * Class representing a source or target element for drag and drop operations.
+ *
+ * @param {Element|string} element Dom Node, or string representation of node
+ *     id, to be used as drag source/drop target.
+ * @param {Object=} opt_data Data associated with the source/target.
+ * @throws Error If no element argument is provided or if the type is invalid
+ * @extends {goog.events.EventTarget}
  * @constructor
- * @extends {ol.renderer.canvas.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Image} imageLayer Single image layer.
  */
-ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) {
+goog.fx.DragDropItem = function(element, opt_data) {
+  goog.fx.DragDropItem.base(this, 'constructor');
 
-  goog.base(this, mapRenderer, imageLayer);
+  /**
+   * Reference to drag source/target element
+   * @type {Element}
+   */
+  this.element = goog.dom.getElement(element);
+
+  /**
+   * Data associated with element.
+   * @type {Object|undefined}
+   */
+  this.data = opt_data;
 
   /**
+   * Drag object the item belongs to.
+   * @type {goog.fx.AbstractDragDrop?}
    * @private
-   * @type {?ol.ImageBase}
    */
-  this.image_ = null;
+  this.parent_ = null;
 
   /**
+   * Event handler for listeners on events that can initiate a drag.
+   * @type {!goog.events.EventHandler<!goog.fx.DragDropItem>}
    * @private
-   * @type {!goog.vec.Mat4.Number}
    */
-  this.imageTransform_ = goog.vec.Mat4.createNumber();
+  this.eventHandler_ = new goog.events.EventHandler(this);
+  this.registerDisposable(this.eventHandler_);
 
+  if (!this.element) {
+    throw Error('Invalid argument');
+  }
 };
-goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer);
+goog.inherits(goog.fx.DragDropItem, goog.events.EventTarget);
 
 
 /**
- * @inheritDoc
+ * The current element being dragged. This is needed because a DragDropItem can
+ * have multiple elements that can be dragged.
+ * @type {Element}
+ * @private
  */
-ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtPixel =
-    function(coordinate, frameState, callback, thisArg) {
-  var layer = this.getLayer();
-  var source = layer.getSource();
-  var extent = frameState.extent;
-  var resolution = frameState.viewState.resolution;
-  var rotation = frameState.viewState.rotation;
-  var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtPixel(
-      extent, resolution, rotation, coordinate, skippedFeatureUids,
-      /**
-       * @param {ol.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        return callback.call(thisArg, feature, layer);
-      });
-};
+goog.fx.DragDropItem.prototype.currentDragElement_ = null;
 
 
 /**
- * @inheritDoc
+ * Get the data associated with the source/target.
+ * @return {Object|null|undefined} Data associated with the source/target.
  */
-ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
-  return goog.isNull(this.image_) ?
-      null : this.image_.getImageElement();
+goog.fx.DragDropItem.prototype.getData = function() {
+  return this.data;
 };
 
 
 /**
- * @inheritDoc
+ * Gets the element that is actually draggable given that the given target was
+ * attempted to be dragged. This should be overriden when the element that was
+ * given actually contains many items that can be dragged. From the target, you
+ * can determine what element should actually be dragged.
+ *
+ * @param {Element} target The target that was attempted to be dragged.
+ * @return {Element} The element that is draggable given the target. If
+ *     none are draggable, this will return null.
  */
-ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() {
-  return this.imageTransform_;
+goog.fx.DragDropItem.prototype.getDraggableElement = function(target) {
+  return target;
 };
 
 
 /**
- * @inheritDoc
+ * Gets the element that is currently being dragged.
+ *
+ * @return {Element} The element that is currently being dragged.
  */
-ol.renderer.canvas.ImageLayer.prototype.prepareFrame =
-    function(frameState, layerState) {
+goog.fx.DragDropItem.prototype.getCurrentDragElement = function() {
+  return this.currentDragElement_;
+};
 
-  var pixelRatio = frameState.pixelRatio;
-  var viewState = frameState.viewState;
-  var viewCenter = viewState.center;
-  var viewResolution = viewState.resolution;
-  var viewRotation = viewState.rotation;
 
-  var image;
-  var imageLayer = this.getLayer();
-  goog.asserts.assertInstanceof(imageLayer, ol.layer.Image);
-  var imageSource = imageLayer.getSource();
+/**
+ * Gets all the elements of this item that are potentially draggable/
+ *
+ * @return {!Array<Element>} The draggable elements.
+ */
+goog.fx.DragDropItem.prototype.getDraggableElements = function() {
+  return [this.element];
+};
 
-  var hints = frameState.viewHints;
 
-  var renderedExtent = frameState.extent;
-  if (goog.isDef(layerState.extent)) {
-    renderedExtent = ol.extent.getIntersection(
-        renderedExtent, layerState.extent);
+/**
+ * Event handler for mouse down.
+ *
+ * @param {goog.events.BrowserEvent} event Mouse down event.
+ * @private
+ */
+goog.fx.DragDropItem.prototype.mouseDown_ = function(event) {
+  if (!event.isMouseActionButton()) {
+    return;
   }
 
-  if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
-      !ol.extent.isEmpty(renderedExtent)) {
-    image = imageSource.getImage(
-        renderedExtent, viewResolution, pixelRatio, viewState.projection);
-    if (!goog.isNull(image)) {
-      var imageState = image.getState();
-      if (imageState == ol.ImageState.IDLE) {
-        goog.events.listenOnce(image, goog.events.EventType.CHANGE,
-            this.handleImageChange, false, this);
-        image.load();
-      } else if (imageState == ol.ImageState.LOADED) {
-        this.image_ = image;
-      }
-    }
+  // Get the draggable element for the target.
+  var element = this.getDraggableElement(/** @type {Element} */ (event.target));
+  if (element) {
+    this.maybeStartDrag_(event, element);
   }
+};
 
-  if (!goog.isNull(this.image_)) {
-    image = this.image_;
-    var imageExtent = image.getExtent();
-    var imageResolution = image.getResolution();
-    var imagePixelRatio = image.getPixelRatio();
-    var scale = pixelRatio * imageResolution /
-        (viewResolution * imagePixelRatio);
-    ol.vec.Mat4.makeTransform2D(this.imageTransform_,
-        pixelRatio * frameState.size[0] / 2,
-        pixelRatio * frameState.size[1] / 2,
-        scale, scale,
-        viewRotation,
-        imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
-        imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
-    this.updateAttributions(frameState.attributions, image.getAttributions());
-    this.updateLogos(frameState, imageSource);
-  }
 
-  return true;
+/**
+ * Sets the dragdrop to which this item belongs.
+ * @param {goog.fx.AbstractDragDrop} parent The parent dragdrop.
+ */
+goog.fx.DragDropItem.prototype.setParent = function(parent) {
+  this.parent_ = parent;
 };
 
-// FIXME find correct globalCompositeOperation
-// FIXME optimize :-)
 
-goog.provide('ol.renderer.canvas.TileLayer');
+/**
+ * Adds mouse move, mouse out and mouse up handlers.
+ *
+ * @param {goog.events.BrowserEvent} event Mouse down event.
+ * @param {Element} element Element.
+ * @private
+ */
+goog.fx.DragDropItem.prototype.maybeStartDrag_ = function(event, element) {
+  var eventType = goog.events.EventType;
+  this.eventHandler_.
+      listen(element, eventType.MOUSEMOVE, this.mouseMove_, false).
+      listen(element, eventType.MOUSEOUT, this.mouseMove_, false);
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('goog.vec.Mat4');
-goog.require('ol.Size');
-goog.require('ol.TileRange');
-goog.require('ol.TileState');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.layer.Tile');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.tilecoord');
-goog.require('ol.vec.Mat4');
+  // Capture the MOUSEUP on the document to ensure that we cancel the start
+  // drag handlers even if the mouse up occurs on some other element. This can
+  // happen for instance when the mouse down changes the geometry of the element
+  // clicked on (e.g. through changes in activation styling) such that the mouse
+  // up occurs outside the original element.
+  var doc = goog.dom.getOwnerDocument(element);
+  this.eventHandler_.listen(doc, eventType.MOUSEUP, this.mouseUp_, true);
+
+  this.currentDragElement_ = element;
+
+  this.startPosition_ = new goog.math.Coordinate(
+      event.clientX, event.clientY);
 
+  event.preventDefault();
+};
 
 
 /**
- * @constructor
- * @extends {ol.renderer.canvas.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Tile} tileLayer Tile layer.
+ * Event handler for mouse move. Starts drag operation if moved more than the
+ * threshold value.
+ *
+ * @param {goog.events.BrowserEvent} event Mouse move or mouse out event.
+ * @private
  */
-ol.renderer.canvas.TileLayer = function(mapRenderer, tileLayer) {
+goog.fx.DragDropItem.prototype.mouseMove_ = function(event) {
+  var distance = Math.abs(event.clientX - this.startPosition_.x) +
+      Math.abs(event.clientY - this.startPosition_.y);
+  // Fire dragStart event if the drag distance exceeds the threshold or if the
+  // mouse leave the dragged element.
+  // TODO(user): Consider using the goog.fx.Dragger to track the distance
+  // even after the mouse leaves the dragged element.
+  var currentDragElement = this.currentDragElement_;
+  var distanceAboveThreshold =
+      distance > goog.fx.AbstractDragDrop.initDragDistanceThreshold;
+  var mouseOutOnDragElement = event.type == goog.events.EventType.MOUSEOUT &&
+      event.target == currentDragElement;
+  if (distanceAboveThreshold || mouseOutOnDragElement) {
+    this.eventHandler_.removeAll();
+    this.parent_.startDrag(event, this);
+  }
+};
 
-  goog.base(this, mapRenderer, tileLayer);
+
+/**
+ * Event handler for mouse up. Removes mouse move, mouse out and mouse up event
+ * handlers.
+ *
+ * @param {goog.events.BrowserEvent} event Mouse up event.
+ * @private
+ */
+goog.fx.DragDropItem.prototype.mouseUp_ = function(event) {
+  this.eventHandler_.removeAll();
+  delete this.startPosition_;
+  this.currentDragElement_ = null;
+};
+
+
+
+/**
+ * Class representing an active drop target
+ *
+ * @param {goog.math.Box} box Box describing the position and dimension of the
+ *     target item.
+ * @param {goog.fx.AbstractDragDrop=} opt_target Target that contains the item
+       associated with position.
+ * @param {goog.fx.DragDropItem=} opt_item Item associated with position.
+ * @param {Element=} opt_element Element of item associated with position.
+ * @constructor
+ * @private
+ */
+goog.fx.ActiveDropTarget_ = function(box, opt_target, opt_item, opt_element) {
 
   /**
+   * Box describing the position and dimension of the target item
+   * @type {goog.math.Box}
    * @private
-   * @type {HTMLCanvasElement}
    */
-  this.canvas_ = null;
+  this.box_ = box;
 
   /**
+   * Target that contains the item associated with position
+   * @type {goog.fx.AbstractDragDrop|undefined}
    * @private
-   * @type {ol.Size}
    */
-  this.canvasSize_ = null;
+  this.target_ = opt_target;
 
   /**
+   * Item associated with position
+   * @type {goog.fx.DragDropItem|undefined}
    * @private
-   * @type {boolean}
    */
-  this.canvasTooBig_ = false;
+  this.item_ = opt_item;
 
   /**
+   * The draggable element of the item associated with position.
+   * @type {Element|undefined}
    * @private
-   * @type {CanvasRenderingContext2D}
    */
-  this.context_ = null;
+  this.element_ = opt_element;
+};
+
+
+/**
+ * If this target is in a scrollable container this is it.
+ * @type {goog.fx.ScrollableContainer_}
+ * @private
+ */
+goog.fx.ActiveDropTarget_.prototype.scrollableContainer_ = null;
+
+
+
+/**
+ * Class for representing a scrollable container
+ * @param {Element} element the scrollable element.
+ * @constructor
+ * @private
+ */
+goog.fx.ScrollableContainer_ = function(element) {
 
   /**
+   * The targets that lie within this container.
+   * @type {Array<goog.fx.ActiveDropTarget_>}
    * @private
-   * @type {!goog.vec.Mat4.Number}
    */
-  this.imageTransform_ = goog.vec.Mat4.createNumber();
+  this.containedTargets_ = [];
 
   /**
+   * The element that is this container
+   * @type {Element}
    * @private
-   * @type {number}
    */
-  this.renderedCanvasZ_ = NaN;
+  this.element_ = element;
 
   /**
+   * The saved scroll left location for calculating deltas.
+   * @type {number}
    * @private
-   * @type {ol.TileRange}
    */
-  this.renderedCanvasTileRange_ = null;
+  this.savedScrollLeft_ = 0;
 
   /**
+   * The saved scroll top location for calculating deltas.
+   * @type {number}
    * @private
-   * @type {Array.<ol.Tile|undefined>}
    */
-  this.renderedTiles_ = null;
+  this.savedScrollTop_ = 0;
 
+  /**
+   * The space occupied by the container.
+   * @type {goog.math.Box}
+   * @private
+   */
+  this.box_ = null;
 };
-goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer);
 
+// FIXME should possibly show tooltip when dragging?
 
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.TileLayer.prototype.getImage = function() {
-  return this.canvas_;
-};
+goog.provide('ol.control.ZoomSlider');
 
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+goog.require('goog.fx.DragDropEvent');
+goog.require('goog.fx.Dragger');
+goog.require('goog.fx.Dragger.EventType');
+goog.require('goog.math');
+goog.require('goog.math.Rect');
+goog.require('goog.style');
+goog.require('ol');
+goog.require('ol.Size');
+goog.require('ol.ViewHint');
+goog.require('ol.animation');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.easing');
 
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() {
-  return this.imageTransform_;
-};
 
 
 /**
- * @inheritDoc
+ * @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.renderer.canvas.TileLayer.prototype.prepareFrame =
-    function(frameState, layerState) {
+ol.control.ZoomSlider = function(opt_options) {
 
-  //
-  // Warning! You're entering a dangerous zone!
-  //
-  // The canvas tile layer renderering is highly optimized, hence
-  // the complexity of this function. For best performance we try
-  // to minimize the number of pixels to update on the canvas. This
-  // includes:
-  //
-  // - Only drawing pixels that will be visible.
-  // - Not re-drawing pixels/tiles that are already correct.
-  // - Minimizing calls to clearRect.
-  // - Never shrink the canvas. Just make it bigger when necessary.
-  //   Re-sizing the canvas also clears it, which further means
-  //   re-creating it (expensive).
-  //
-  // The various steps performed by this functions:
-  //
-  // - Create a canvas element if none has been created yet.
-  //
-  // - Make the canvas bigger if it's too small. Note that we never shrink
-  //   the canvas, we just make it bigger when necessary, when rotating for
-  //   example. Note also that the canvas always contains a whole number
-  //   of tiles.
-  //
-  // - Invalidate the canvas tile range (renderedCanvasTileRange_ = null)
-  //   if (1) the canvas has been enlarged, or (2) the zoom level changes,
-  //   or (3) the canvas tile range doesn't contain the required tile
-  //   range. This canvas tile range invalidation thing is related to
-  //   an optimization where we attempt to redraw as few pixels as
-  //   possible on each prepareFrame call.
-  //
-  // - If the canvas tile range has been invalidated we reset
-  //   renderedCanvasTileRange_ and reset the renderedTiles_ array.
-  //   The renderedTiles_ array is the structure used to determine
-  //   the canvas pixels that need not be redrawn from one prepareFrame
-  //   call to another. It records while tile has been rendered at
-  //   which position in the canvas.
-  //
-  // - We then determine the tiles to draw on the canvas. Tiles for
-  //   the target resolution may not be loaded yet. In that case we
-  //   use low-resolution/interim tiles if loaded already. And, if
-  //   for a non-yet-loaded tile we haven't found a corresponding
-  //   low-resolution tile we indicate that the pixels for that
-  //   tile must be cleared on the canvas. Note: determining the
-  //   interim tiles is based on tile extents instead of tile
-  //   coords, this is to be able to handler irregular tile grids.
-  //
-  // - We're now ready to render. We start by calling clearRect
-  //   for the tiles that aren't loaded yet and are not fully covered
-  //   by a low-resolution tile (if they're loaded, we'll draw them;
-  //   if they're fully covered by a low-resolution tile then there's
-  //   no need to clear). We then render the tiles "back to front",
-  //   i.e. starting with the low-resolution tiles.
-  //
-  // - After rendering some bookkeeping is performed (updateUsedTiles,
-  //   etc.). manageTilePyramid is what enqueue tiles in the tile
-  //   queue for loading.
-  //
-  // - The last step involves updating the image transform matrix,
-  //   which will be used by the map renderer for the final
-  //   composition and positioning.
-  //
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  var pixelRatio = frameState.pixelRatio;
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
+  /**
+   * Will hold the current resolution of the view.
+   *
+   * @type {number|undefined}
+   * @private
+   */
+  this.currentResolution_ = undefined;
 
-  var tileLayer = this.getLayer();
-  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
-  var tileSource = tileLayer.getSource();
-  var tileGrid = tileSource.getTileGridForProjection(projection);
-  var tileGutter = tileSource.getGutter();
-  var z = tileGrid.getZForResolution(viewState.resolution);
-  var tilePixelSize =
-      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
-  var tilePixelRatio = tilePixelSize / tileGrid.getTileSize(z);
-  var tileResolution = tileGrid.getResolution(z);
-  var tilePixelResolution = tileResolution / tilePixelRatio;
-  var center = viewState.center;
-  var extent;
-  if (tileResolution == viewState.resolution) {
-    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
-    extent = ol.extent.getForViewAndSize(
-        center, tileResolution, viewState.rotation, frameState.size);
+  /**
+   * The direction of the slider. Will be determined from actual display of the
+   * container and defaults to ol.control.ZoomSlider.direction.VERTICAL.
+   *
+   * @type {ol.control.ZoomSlider.direction}
+   * @private
+   */
+  this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;
+
+  /**
+   * The calculated thumb size (border box plus margins).  Set when initSlider_
+   * is called.
+   * @type {ol.Size}
+   * @private
+   */
+  this.thumbSize_ = null;
+
+  /**
+   * Whether the slider is initialized.
+   * @type {boolean}
+   * @private
+   */
+  this.sliderInitialized_ = false;
+
+  var className = goog.isDef(options.className) ?
+      options.className : 'ol-zoomslider';
+  var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV,
+      [className + '-thumb', ol.css.CLASS_UNSELECTABLE]);
+  var containerElement = goog.dom.createDom(goog.dom.TagName.DIV,
+      [className, ol.css.CLASS_UNSELECTABLE, ol.css.CLASS_CONTROL],
+      thumbElement);
+
+  /**
+   * @type {goog.fx.Dragger}
+   * @private
+   */
+  this.dragger_ = new goog.fx.Dragger(thumbElement);
+  this.registerDisposable(this.dragger_);
+
+  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.START,
+      this.handleDraggerStart_, false, this);
+  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG,
+      this.handleDraggerDrag_, false, this);
+  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.END,
+      this.handleDraggerEnd_, false, this);
+
+  goog.events.listen(containerElement, goog.events.EventType.CLICK,
+      this.handleContainerClick_, false, this);
+  goog.events.listen(thumbElement, goog.events.EventType.CLICK,
+      goog.events.Event.stopPropagation);
+
+  var render = goog.isDef(options.render) ?
+      options.render : ol.control.ZoomSlider.render;
+
+  goog.base(this, {
+    element: containerElement,
+    render: render
+  });
+};
+goog.inherits(ol.control.ZoomSlider, ol.control.Control);
+
+
+/**
+ * The enum for available directions.
+ *
+ * @enum {number}
+ */
+ol.control.ZoomSlider.direction = {
+  VERTICAL: 0,
+  HORIZONTAL: 1
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.control.ZoomSlider.prototype.setMap = function(map) {
+  goog.base(this, 'setMap', map);
+  if (!goog.isNull(map)) {
+    map.render();
+  }
+};
+
+
+/**
+ * Initializes the slider element. This will determine and set this controls
+ * direction_ and also constrain the dragging of the thumb to always be within
+ * the bounds of the container.
+ *
+ * @private
+ */
+ol.control.ZoomSlider.prototype.initSlider_ = function() {
+  var container = this.element;
+  var containerSize = goog.style.getSize(container);
+
+  var thumb = goog.dom.getFirstElementChild(container);
+  var thumbMargins = goog.style.getMarginBox(thumb);
+  var thumbBorderBoxSize = goog.style.getBorderBoxSize(thumb);
+  var thumbWidth = thumbBorderBoxSize.width +
+      thumbMargins.right + thumbMargins.left;
+  var thumbHeight = thumbBorderBoxSize.height +
+      thumbMargins.top + thumbMargins.bottom;
+  this.thumbSize_ = [thumbWidth, thumbHeight];
+
+  var width = containerSize.width - thumbWidth;
+  var height = containerSize.height - thumbHeight;
+
+  var limits;
+  if (containerSize.width > containerSize.height) {
+    this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL;
+    limits = new goog.math.Rect(0, 0, width, 0);
   } else {
-    extent = frameState.extent;
+    this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;
+    limits = new goog.math.Rect(0, 0, 0, height);
   }
+  this.dragger_.setLimits(limits);
+  this.sliderInitialized_ = true;
+};
 
-  if (goog.isDef(layerState.extent)) {
-    extent = ol.extent.getIntersection(extent, layerState.extent);
+
+/**
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.ZoomSlider}
+ * @api
+ */
+ol.control.ZoomSlider.render = function(mapEvent) {
+  if (goog.isNull(mapEvent.frameState)) {
+    return;
   }
-  if (ol.extent.isEmpty(extent)) {
-    // Return false to prevent the rendering of the layer.
-    return false;
+  goog.asserts.assert(goog.isDefAndNotNull(mapEvent.frameState.viewState));
+  if (!this.sliderInitialized_) {
+    this.initSlider_();
+  }
+  var res = mapEvent.frameState.viewState.resolution;
+  if (res !== this.currentResolution_) {
+    this.currentResolution_ = res;
+    this.setThumbPosition_(res);
   }
+};
 
-  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
-      extent, tileResolution);
 
-  var canvasWidth = tilePixelSize * tileRange.getWidth();
-  var canvasHeight = tilePixelSize * tileRange.getHeight();
+/**
+ * @param {goog.events.BrowserEvent} browserEvent The browser event to handle.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) {
+  var map = this.getMap();
+  var view = map.getView();
+  var currentResolution = view.getResolution();
+  goog.asserts.assert(goog.isDef(currentResolution));
+  map.beforeRender(ol.animation.zoom({
+    resolution: currentResolution,
+    duration: ol.ZOOMSLIDER_ANIMATION_DURATION,
+    easing: ol.easing.easeOut
+  }));
+  var relativePosition = this.getRelativePosition_(
+      browserEvent.offsetX - this.thumbSize_[0] / 2,
+      browserEvent.offsetY - this.thumbSize_[1] / 2);
+  var resolution = this.getResolutionForPosition_(relativePosition);
+  view.setResolution(view.constrainResolution(resolution));
+};
 
-  var canvas, context;
-  if (goog.isNull(this.canvas_)) {
-    goog.asserts.assert(goog.isNull(this.canvasSize_));
-    goog.asserts.assert(goog.isNull(this.context_));
-    goog.asserts.assert(goog.isNull(this.renderedCanvasTileRange_));
-    context = ol.dom.createCanvasContext2D(canvasWidth, canvasHeight);
-    this.canvas_ = context.canvas;
-    this.canvasSize_ = [canvasWidth, canvasHeight];
-    this.context_ = context;
-    this.canvasTooBig_ =
-        !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_);
+
+/**
+ * Handle dragger start events.
+ * @param {goog.fx.DragDropEvent} event The dragdropevent.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) {
+  this.getMap().getView().setHint(ol.ViewHint.INTERACTING, 1);
+};
+
+
+/**
+ * Handle dragger drag events.
+ *
+ * @param {goog.fx.DragDropEvent} event The dragdropevent.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) {
+  var relativePosition = this.getRelativePosition_(event.left, event.top);
+  this.currentResolution_ = this.getResolutionForPosition_(relativePosition);
+  this.getMap().getView().setResolution(this.currentResolution_);
+};
+
+
+/**
+ * Handle dragger end events.
+ * @param {goog.fx.DragDropEvent} event The dragdropevent.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) {
+  var map = this.getMap();
+  var view = map.getView();
+  view.setHint(ol.ViewHint.INTERACTING, -1);
+  goog.asserts.assert(goog.isDef(this.currentResolution_));
+  map.beforeRender(ol.animation.zoom({
+    resolution: this.currentResolution_,
+    duration: ol.ZOOMSLIDER_ANIMATION_DURATION,
+    easing: ol.easing.easeOut
+  }));
+  var resolution = view.constrainResolution(this.currentResolution_);
+  view.setResolution(resolution);
+};
+
+
+/**
+ * Positions the thumb inside its container according to the given resolution.
+ *
+ * @param {number} res The res.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) {
+  var position = this.getPositionForResolution_(res);
+  var dragger = this.dragger_;
+  var thumb = goog.dom.getFirstElementChild(this.element);
+
+  if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) {
+    var left = dragger.limits.left + dragger.limits.width * position;
+    goog.style.setPosition(thumb, left);
   } else {
-    goog.asserts.assert(!goog.isNull(this.canvasSize_));
-    goog.asserts.assert(!goog.isNull(this.context_));
-    canvas = this.canvas_;
-    context = this.context_;
-    if (this.canvasSize_[0] < canvasWidth ||
-        this.canvasSize_[1] < canvasHeight ||
-        (this.canvasTooBig_ && (this.canvasSize_[0] > canvasWidth ||
-        this.canvasSize_[1] > canvasHeight))) {
-      // Canvas is too small, resize it. We never shrink the canvas, unless
-      // we know that the current canvas size exceeds the maximum size
-      canvas.width = canvasWidth;
-      canvas.height = canvasHeight;
-      this.canvasSize_ = [canvasWidth, canvasHeight];
-      this.canvasTooBig_ =
-          !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_);
-      this.renderedCanvasTileRange_ = null;
-    } else {
-      canvasWidth = this.canvasSize_[0];
-      canvasHeight = this.canvasSize_[1];
-      if (z != this.renderedCanvasZ_ ||
-          !this.renderedCanvasTileRange_.containsTileRange(tileRange)) {
-        this.renderedCanvasTileRange_ = null;
-        // Due to limited layer extent, we may be rendering tiles on a small
-        // portion of the canvas.
-        if (z < this.renderedCanvasZ_) {
-          this.context_.clearRect(0, 0, canvasWidth, canvasHeight);
-        }
-      }
-    }
+    var top = dragger.limits.top + dragger.limits.height * position;
+    goog.style.setPosition(thumb, dragger.limits.left, top);
   }
+};
 
-  var canvasTileRange, canvasTileRangeWidth, minX, minY;
-  if (goog.isNull(this.renderedCanvasTileRange_)) {
-    canvasTileRangeWidth = canvasWidth / tilePixelSize;
-    var canvasTileRangeHeight = canvasHeight / tilePixelSize;
-    minX = tileRange.minX -
-        Math.floor((canvasTileRangeWidth - tileRange.getWidth()) / 2);
-    minY = tileRange.minY -
-        Math.floor((canvasTileRangeHeight - tileRange.getHeight()) / 2);
-    this.renderedCanvasZ_ = z;
-    this.renderedCanvasTileRange_ = new ol.TileRange(
-        minX, minX + canvasTileRangeWidth - 1,
-        minY, minY + canvasTileRangeHeight - 1);
-    this.renderedTiles_ =
-        new Array(canvasTileRangeWidth * canvasTileRangeHeight);
-    canvasTileRange = this.renderedCanvasTileRange_;
+
+/**
+ * Calculates the relative position of the thumb given x and y offsets.  The
+ * relative position scales from 0 to 1.  The x and y offsets are assumed to be
+ * in pixel units within the dragger limits.
+ *
+ * @param {number} x Pixel position relative to the left of the slider.
+ * @param {number} y Pixel position relative to the top of the slider.
+ * @return {number} The relative position of the thumb.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) {
+  var draggerLimits = this.dragger_.limits;
+  var amount;
+  if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) {
+    amount = (x - draggerLimits.left) / draggerLimits.width;
   } else {
-    canvasTileRange = this.renderedCanvasTileRange_;
-    canvasTileRangeWidth = canvasTileRange.getWidth();
+    amount = (y - draggerLimits.top) / draggerLimits.height;
   }
+  return goog.math.clamp(amount, 0, 1);
+};
 
-  goog.asserts.assert(canvasTileRange.containsTileRange(tileRange));
+
+/**
+ * Calculates the corresponding resolution of the thumb given its relative
+ * position (where 0 is the minimum and 1 is the maximum).
+ *
+ * @param {number} position The relative position of the thumb.
+ * @return {number} The corresponding resolution.
+ * @private
+ */
+ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) {
+  var fn = this.getMap().getView().getResolutionForValueFunction();
+  return fn(1 - position);
+};
+
+
+/**
+ * Determines the relative position of the slider for the given resolution.  A
+ * relative position of 0 corresponds to the minimum view resolution.  A
+ * relative position of 1 corresponds to the maximum view resolution.
+ *
+ * @param {number} res The resolution.
+ * @return {number} The relative position value (between 0 and 1).
+ * @private
+ */
+ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) {
+  var fn = this.getMap().getView().getValueForResolutionFunction();
+  return 1 - fn(res);
+};
+
+goog.provide('ol.control.ZoomToExtent');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+
+
+
+/**
+ * @classdesc
+ * A button control which, when pressed, changes the map view to a specific
+ * extent. To style this control use the css selector `.ol-zoom-extent`.
+ *
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.ZoomToExtentOptions=} opt_options Options.
+ * @api stable
+ */
+ol.control.ZoomToExtent = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
   /**
-   * @type {Object.<number, Object.<string, ol.Tile>>}
+   * @type {ol.Extent}
+   * @private
    */
-  var tilesToDrawByZ = {};
-  tilesToDrawByZ[z] = {};
-  /** @type {Array.<ol.Tile>} */
-  var tilesToClear = [];
+  this.extent_ = goog.isDef(options.extent) ? options.extent : null;
 
-  var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
-    return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED;
-  }, tileSource, pixelRatio, projection);
-  var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
-      tilesToDrawByZ, getTileIfLoaded);
+  var className = goog.isDef(options.className) ? options.className :
+      'ol-zoom-extent';
 
-  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-  if (!goog.isDef(useInterimTilesOnError)) {
-    useInterimTilesOnError = true;
-  }
+  var tipLabel = goog.isDef(options.tipLabel) ?
+      options.tipLabel : 'Fit to extent';
+  var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
+    'type': 'button',
+    'title': tipLabel
+  });
 
-  var tmpExtent = ol.extent.createEmpty();
-  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-  var childTileRange, fullyLoaded, tile, tileState, x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+  goog.events.listen(button, goog.events.EventType.CLICK,
+      this.handleClick_, false, this);
 
-      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-      tileState = tile.getState();
-      if (tileState == ol.TileState.LOADED ||
-          tileState == ol.TileState.EMPTY ||
-          (tileState == ol.TileState.ERROR && !useInterimTilesOnError)) {
-        tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
-        continue;
-      }
+  goog.events.listen(button, [
+    goog.events.EventType.MOUSEOUT,
+    goog.events.EventType.FOCUSOUT
+  ], function() {
+    this.blur();
+  }, false);
 
-      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
-          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
-      if (!fullyLoaded) {
-        // FIXME we do not need to clear the tile if it is fully covered by its
-        //       children
-        tilesToClear.push(tile);
-        childTileRange = tileGrid.getTileCoordChildTileRange(
-            tile.tileCoord, tmpTileRange, tmpExtent);
-        if (!goog.isNull(childTileRange)) {
-          findLoadedTiles(z + 1, childTileRange);
-        }
-      }
+  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
+      ol.css.CLASS_CONTROL;
+  var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button);
 
-    }
-  }
+  goog.base(this, {
+    element: element,
+    target: options.target
+  });
+};
+goog.inherits(ol.control.ZoomToExtent, ol.control.Control);
 
-  var i, ii;
-  for (i = 0, ii = tilesToClear.length; i < ii; ++i) {
-    tile = tilesToClear[i];
-    x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX);
-    y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]);
-    context.clearRect(x, y, tilePixelSize, tilePixelSize);
-  }
 
-  /** @type {Array.<number>} */
-  var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
-  goog.array.sort(zs);
-  var opaque = tileSource.getOpaque();
-  var origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent(
-      [z, canvasTileRange.minX, canvasTileRange.maxY],
-      tmpExtent));
-  var currentZ, index, scale, tileCoordKey, tileExtent, tilesToDraw;
-  var ix, iy, interimTileExtent, interimTileRange, maxX, maxY;
-  var height, width;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    currentZ = zs[i];
-    tilePixelSize =
-        tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
-    tilesToDraw = tilesToDrawByZ[currentZ];
-    if (currentZ == z) {
-      for (tileCoordKey in tilesToDraw) {
-        tile = tilesToDraw[tileCoordKey];
-        index =
-            (tile.tileCoord[2] - canvasTileRange.minY) * canvasTileRangeWidth +
-            (tile.tileCoord[1] - canvasTileRange.minX);
-        if (this.renderedTiles_[index] != tile) {
-          x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX);
-          y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]);
-          tileState = tile.getState();
-          if (tileState == ol.TileState.EMPTY ||
-              (tileState == ol.TileState.ERROR && !useInterimTilesOnError) ||
-              !opaque) {
-            context.clearRect(x, y, tilePixelSize, tilePixelSize);
-          }
-          if (tileState == ol.TileState.LOADED) {
-            context.drawImage(tile.getImage(),
-                tileGutter, tileGutter, tilePixelSize, tilePixelSize,
-                x, y, tilePixelSize, tilePixelSize);
-          }
-          this.renderedTiles_[index] = tile;
-        }
-      }
-    } else {
-      scale = tileGrid.getResolution(currentZ) / tileResolution;
-      for (tileCoordKey in tilesToDraw) {
-        tile = tilesToDraw[tileCoordKey];
-        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
-        x = (tileExtent[0] - origin[0]) / tilePixelResolution;
-        y = (origin[1] - tileExtent[3]) / tilePixelResolution;
-        width = scale * tilePixelSize;
-        height = scale * tilePixelSize;
-        tileState = tile.getState();
-        if (tileState == ol.TileState.EMPTY || !opaque) {
-          context.clearRect(x, y, width, height);
-        }
-        if (tileState == ol.TileState.LOADED) {
-          context.drawImage(tile.getImage(),
-              tileGutter, tileGutter, tilePixelSize, tilePixelSize,
-              x, y, width, height);
-        }
-        interimTileRange =
-            tileGrid.getTileRangeForExtentAndZ(tileExtent, z, tmpTileRange);
-        minX = Math.max(interimTileRange.minX, canvasTileRange.minX);
-        maxX = Math.min(interimTileRange.maxX, canvasTileRange.maxX);
-        minY = Math.max(interimTileRange.minY, canvasTileRange.minY);
-        maxY = Math.min(interimTileRange.maxY, canvasTileRange.maxY);
-        for (ix = minX; ix <= maxX; ++ix) {
-          for (iy = minY; iy <= maxY; ++iy) {
-            index = (iy - canvasTileRange.minY) * canvasTileRangeWidth +
-                    (ix - canvasTileRange.minX);
-            this.renderedTiles_[index] = undefined;
-          }
-        }
-      }
+/**
+ * @param {goog.events.BrowserEvent} event The event to handle
+ * @private
+ */
+ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleZoomToExtent_();
+};
+
+
+/**
+ * @private
+ */
+ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() {
+  var map = this.getMap();
+  var view = map.getView();
+  var extent = goog.isNull(this.extent_) ?
+      view.getProjection().getExtent() : this.extent_;
+  var size = map.getSize();
+  goog.asserts.assert(goog.isDef(size));
+  view.fitExtent(extent, size);
+};
+
+goog.provide('ol.DeviceOrientation');
+goog.provide('ol.DeviceOrientationProperty');
+
+goog.require('goog.events');
+goog.require('goog.math');
+goog.require('ol.Object');
+goog.require('ol.has');
+
+
+/**
+ * @enum {string}
+ */
+ol.DeviceOrientationProperty = {
+  ALPHA: 'alpha',
+  BETA: 'beta',
+  GAMMA: 'gamma',
+  HEADING: 'heading',
+  TRACKING: 'tracking'
+};
+
+
+
+/**
+ * @classdesc
+ * The ol.DeviceOrientation class provides access to DeviceOrientation
+ * information and 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 targetting mobile devices will be especially interested in this
+ * class.
+ *
+ * Device orientation data are relative to a common starting point. For mobile
+ * devices, the starting point is to lay your phone face up on a table with the
+ * top of the phone pointing north. This represents the zero state. All
+ * angles are then relative to this state. For computers, it is the same except
+ * the screen is open at 90 degrees.
+ *
+ * Device orientation is reported as three angles - `alpha`, `beta`, and
+ * `gamma` - relative to the starting position along the three planar axes X, Y
+ * and Z. The X axis runs from the left edge to the right edge through the
+ * middle of the device. Similarly, the Y axis runs from the bottom to the top
+ * of the device through the middle. The Z axis runs from the back to the front
+ * through the middle. In the starting position, the X axis points to the
+ * right, the Y axis points away from you and the Z axis points straight up
+ * from the device lying flat.
+ *
+ * The three angles representing the device orientation are relative to the
+ * three axes. `alpha` indicates how much the device has been rotated around the
+ * Z axis, which is commonly interpreted as the compass heading (see note
+ * below). `beta` indicates how much the device has been rotated around the X
+ * axis, or how much it is tilted from front to back.  `gamma` indicates how
+ * much the device has been rotated around the Y axis, or how much it is tilted
+ * from left to right.
+ *
+ * For most browsers, the `alpha` value returns the compass heading so if the
+ * device points north, it will be 0.  With Safari on iOS, the 0 value of
+ * `alpha` is calculated from when device orientation was first requested.
+ * ol.DeviceOrientation provides the `heading` property which normalizes this
+ * behavior across all browsers for you.
+ *
+ * It is important to note that the HTML 5 DeviceOrientation specification
+ * indicates that `alpha`, `beta` and `gamma` are in degrees while the
+ * equivalent properties in ol.DeviceOrientation are in radians for consistency
+ * with all other uses of angles throughout OpenLayers.
+ *
+ * @see http://www.w3.org/TR/orientation-event/
+ *
+ * @constructor
+ * @extends {ol.Object}
+ * @fires change Triggered when the device orientation changes.
+ * @param {olx.DeviceOrientationOptions=} opt_options Options.
+ * @api
+ */
+ol.DeviceOrientation = function(opt_options) {
+
+  goog.base(this);
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {goog.events.Key}
+   */
+  this.listenerKey_ = null;
+
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.DeviceOrientationProperty.TRACKING),
+      this.handleTrackingChanged_, false, this);
+
+  this.setTracking(goog.isDef(options.tracking) ? options.tracking : false);
+
+};
+goog.inherits(ol.DeviceOrientation, ol.Object);
+
+
+/**
+ * @inheritDoc
+ */
+ol.DeviceOrientation.prototype.disposeInternal = function() {
+  this.setTracking(false);
+  goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @private
+ * @param {goog.events.BrowserEvent} browserEvent Event.
+ */
+ol.DeviceOrientation.prototype.orientationChange_ = function(browserEvent) {
+  var event = /** @type {DeviceOrientationEvent} */
+      (browserEvent.getBrowserEvent());
+  if (goog.isDefAndNotNull(event.alpha)) {
+    var alpha = goog.math.toRadians(event.alpha);
+    this.set(ol.DeviceOrientationProperty.ALPHA, alpha);
+    // event.absolute is undefined in iOS.
+    if (goog.isBoolean(event.absolute) && event.absolute) {
+      this.set(ol.DeviceOrientationProperty.HEADING, alpha);
+    } else if (goog.isDefAndNotNull(event.webkitCompassHeading) &&
+               goog.isDefAndNotNull(event.webkitCompassAccuracy) &&
+               event.webkitCompassAccuracy != -1) {
+      var heading = goog.math.toRadians(event.webkitCompassHeading);
+      this.set(ol.DeviceOrientationProperty.HEADING, heading);
+    }
+  }
+  if (goog.isDefAndNotNull(event.beta)) {
+    this.set(ol.DeviceOrientationProperty.BETA,
+        goog.math.toRadians(event.beta));
+  }
+  if (goog.isDefAndNotNull(event.gamma)) {
+    this.set(ol.DeviceOrientationProperty.GAMMA,
+        goog.math.toRadians(event.gamma));
+  }
+  this.changed();
+};
+
+
+/**
+ * @return {number|undefined} The euler angle in radians of the device from the
+ *     standard Z axis.
+ * @observable
+ * @api
+ */
+ol.DeviceOrientation.prototype.getAlpha = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.DeviceOrientationProperty.ALPHA));
+};
+goog.exportProperty(
+    ol.DeviceOrientation.prototype,
+    'getAlpha',
+    ol.DeviceOrientation.prototype.getAlpha);
+
+
+/**
+ * @return {number|undefined} The euler angle in radians of the device from the
+ *     planar X axis.
+ * @observable
+ * @api
+ */
+ol.DeviceOrientation.prototype.getBeta = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.DeviceOrientationProperty.BETA));
+};
+goog.exportProperty(
+    ol.DeviceOrientation.prototype,
+    'getBeta',
+    ol.DeviceOrientation.prototype.getBeta);
+
+
+/**
+ * @return {number|undefined} The euler angle in radians of the device from the
+ *     planar Y axis.
+ * @observable
+ * @api
+ */
+ol.DeviceOrientation.prototype.getGamma = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.DeviceOrientationProperty.GAMMA));
+};
+goog.exportProperty(
+    ol.DeviceOrientation.prototype,
+    'getGamma',
+    ol.DeviceOrientation.prototype.getGamma);
+
+
+/**
+ * @return {number|undefined} The heading of the device relative to north, in
+ *     radians, normalizing for different browser behavior.
+ * @observable
+ * @api
+ */
+ol.DeviceOrientation.prototype.getHeading = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.DeviceOrientationProperty.HEADING));
+};
+goog.exportProperty(
+    ol.DeviceOrientation.prototype,
+    'getHeading',
+    ol.DeviceOrientation.prototype.getHeading);
+
+
+/**
+ * Are we tracking the device's orientation?
+ * @return {boolean} The status of tracking changes to alpha, beta and gamma.
+ *     If true, changes are tracked and reported immediately.
+ * @observable
+ * @api
+ */
+ol.DeviceOrientation.prototype.getTracking = function() {
+  return /** @type {boolean} */ (
+      this.get(ol.DeviceOrientationProperty.TRACKING));
+};
+goog.exportProperty(
+    ol.DeviceOrientation.prototype,
+    'getTracking',
+    ol.DeviceOrientation.prototype.getTracking);
+
+
+/**
+ * @private
+ */
+ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() {
+  if (ol.has.DEVICE_ORIENTATION) {
+    var tracking = this.getTracking();
+    if (tracking && goog.isNull(this.listenerKey_)) {
+      this.listenerKey_ = goog.events.listen(goog.global, 'deviceorientation',
+          this.orientationChange_, false, this);
+    } else if (!tracking && !goog.isNull(this.listenerKey_)) {
+      goog.events.unlistenByKey(this.listenerKey_);
+      this.listenerKey_ = null;
+    }
+  }
+};
+
+
+/**
+ * Enable or disable tracking of DeviceOrientation events.
+ * @param {boolean} tracking The status of tracking changes to alpha, beta and
+ *     gamma. If true, changes are tracked and reported immediately.
+ * @observable
+ * @api
+ */
+ol.DeviceOrientation.prototype.setTracking = function(tracking) {
+  this.set(ol.DeviceOrientationProperty.TRACKING, tracking);
+};
+goog.exportProperty(
+    ol.DeviceOrientation.prototype,
+    'setTracking',
+    ol.DeviceOrientation.prototype.setTracking);
+
+goog.provide('ol.dom.Input');
+goog.provide('ol.dom.InputProperty');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('ol.Object');
+
+
+/**
+ * @enum {string}
+ */
+ol.dom.InputProperty = {
+  VALUE: 'value',
+  CHECKED: 'checked'
+};
+
+
+
+/**
+ * @classdesc
+ * Helper class for binding HTML input to an {@link ol.Object}.
+ *
+ * Example:
+ *
+ *     // bind a checkbox with id 'visible' to a layer's visibility
+ *     var visible = new ol.dom.Input(document.getElementById('visible'));
+ *     visible.bindTo('checked', layer, 'visible');
+ *
+ * @constructor
+ * @extends {ol.Object}
+ * @param {Element} target Target element.
+ * @api
+ */
+ol.dom.Input = function(target) {
+  goog.base(this);
+
+  /**
+   * @private
+   * @type {HTMLInputElement}
+   */
+  this.target_ = /** @type {HTMLInputElement} */ (target);
+
+  goog.events.listen(this.target_,
+      [goog.events.EventType.CHANGE, goog.events.EventType.INPUT],
+      this.handleInputChanged_, false, this);
+
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.dom.InputProperty.VALUE),
+      this.handleValueChanged_, false, this);
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.dom.InputProperty.CHECKED),
+      this.handleCheckedChanged_, false, this);
+};
+goog.inherits(ol.dom.Input, ol.Object);
+
+
+/**
+ * If the input is a checkbox, return whether or not the checkbox is checked.
+ * @return {boolean|undefined} The checked state of the Input.
+ * @observable
+ * @api
+ */
+ol.dom.Input.prototype.getChecked = function() {
+  return /** @type {boolean} */ (this.get(ol.dom.InputProperty.CHECKED));
+};
+goog.exportProperty(
+    ol.dom.Input.prototype,
+    'getChecked',
+    ol.dom.Input.prototype.getChecked);
+
+
+/**
+ * Get the value of the input.
+ * @return {string|undefined} The value of the Input.
+ * @observable
+ * @api
+ */
+ol.dom.Input.prototype.getValue = function() {
+  return /** @type {string} */ (this.get(ol.dom.InputProperty.VALUE));
+};
+goog.exportProperty(
+    ol.dom.Input.prototype,
+    'getValue',
+    ol.dom.Input.prototype.getValue);
+
+
+/**
+ * Sets the value of the input.
+ * @param {string} value The value of the Input.
+ * @observable
+ * @api
+ */
+ol.dom.Input.prototype.setValue = function(value) {
+  this.set(ol.dom.InputProperty.VALUE, value);
+};
+goog.exportProperty(
+    ol.dom.Input.prototype,
+    'setValue',
+    ol.dom.Input.prototype.setValue);
+
+
+/**
+ * Set whether or not a checkbox is checked.
+ * @param {boolean} checked The checked state of the Input.
+ * @observable
+ * @api
+ */
+ol.dom.Input.prototype.setChecked = function(checked) {
+  this.set(ol.dom.InputProperty.CHECKED, checked);
+};
+goog.exportProperty(
+    ol.dom.Input.prototype,
+    'setChecked',
+    ol.dom.Input.prototype.setChecked);
+
+
+/**
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @private
+ */
+ol.dom.Input.prototype.handleInputChanged_ = function(browserEvent) {
+  goog.asserts.assert(browserEvent.currentTarget == this.target_);
+  var target = this.target_;
+  if (target.type === 'checkbox' || target.type === 'radio') {
+    this.setChecked(target.checked);
+  } else {
+    this.setValue(target.value);
+  }
+};
+
+
+/**
+ * @param {goog.events.Event} event Change event.
+ * @private
+ */
+ol.dom.Input.prototype.handleCheckedChanged_ = function(event) {
+  this.target_.checked = /** @type {boolean} */ (this.getChecked());
+};
+
+
+/**
+ * @param {goog.events.Event} event Change event.
+ * @private
+ */
+ol.dom.Input.prototype.handleValueChanged_ = function(event) {
+  this.target_.value =  /** @type {string} */ (this.getValue());
+};
+
+goog.provide('ol.Ellipsoid');
+
+goog.require('goog.math');
+goog.require('ol.Coordinate');
+
+
+
+/**
+ * @constructor
+ * @param {number} a Major radius.
+ * @param {number} flattening Flattening.
+ */
+ol.Ellipsoid = function(a, flattening) {
+
+  /**
+   * @const
+   * @type {number}
+   */
+  this.a = a;
+
+  /**
+   * @const
+   * @type {number}
+   */
+  this.flattening = flattening;
+
+  /**
+   * @const
+   * @type {number}
+   */
+  this.b = this.a * (1 - this.flattening);
+
+  /**
+   * @const
+   * @type {number}
+   */
+  this.eSquared = 2 * flattening - flattening * flattening;
+
+  /**
+   * @const
+   * @type {number}
+   */
+  this.e = Math.sqrt(this.eSquared);
+
+};
+
+
+/**
+ * @param {ol.Coordinate} c1 Coordinate 1.
+ * @param {ol.Coordinate} c2 Coordinate 1.
+ * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
+ * @param {number=} opt_maxIterations Maximum iterations.
+ * @return {{distance: number, initialBearing: number, finalBearing: number}}
+ *     Vincenty.
+ */
+ol.Ellipsoid.prototype.vincenty =
+    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
+  var minDeltaLambda = goog.isDef(opt_minDeltaLambda) ?
+      opt_minDeltaLambda : 1e-12;
+  var maxIterations = goog.isDef(opt_maxIterations) ?
+      opt_maxIterations : 100;
+  var f = this.flattening;
+  var lat1 = goog.math.toRadians(c1[1]);
+  var lat2 = goog.math.toRadians(c2[1]);
+  var deltaLon = goog.math.toRadians(c2[0] - c1[0]);
+  var U1 = Math.atan((1 - f) * Math.tan(lat1));
+  var cosU1 = Math.cos(U1);
+  var sinU1 = Math.sin(U1);
+  var U2 = Math.atan((1 - f) * Math.tan(lat2));
+  var cosU2 = Math.cos(U2);
+  var sinU2 = Math.sin(U2);
+  var lambda = deltaLon;
+  var cosSquaredAlpha, sinAlpha;
+  var cosLambda, deltaLambda = Infinity, sinLambda;
+  var cos2SigmaM, cosSigma, sigma, sinSigma;
+  var i;
+  for (i = maxIterations; i > 0; --i) {
+    cosLambda = Math.cos(lambda);
+    sinLambda = Math.sin(lambda);
+    var x = cosU2 * sinLambda;
+    var y = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda;
+    sinSigma = Math.sqrt(x * x + y * y);
+    if (sinSigma === 0) {
+      return {
+        distance: 0,
+        initialBearing: 0,
+        finalBearing: 0
+      };
+    }
+    cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
+    sigma = Math.atan2(sinSigma, cosSigma);
+    sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
+    cosSquaredAlpha = 1 - sinAlpha * sinAlpha;
+    cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSquaredAlpha;
+    if (isNaN(cos2SigmaM)) {
+      cos2SigmaM = 0;
+    }
+    var C = f / 16 * cosSquaredAlpha * (4 + f * (4 - 3 * cosSquaredAlpha));
+    var lambdaPrime = deltaLon + (1 - C) * f * sinAlpha * (sigma +
+        C * sinSigma * (cos2SigmaM +
+        C * cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1)));
+    deltaLambda = Math.abs(lambdaPrime - lambda);
+    lambda = lambdaPrime;
+    if (deltaLambda < minDeltaLambda) {
+      break;
+    }
+  }
+  if (i === 0) {
+    return {
+      distance: NaN,
+      finalBearing: NaN,
+      initialBearing: NaN
+    };
+  }
+  var aSquared = this.a * this.a;
+  var bSquared = this.b * this.b;
+  var uSquared = cosSquaredAlpha * (aSquared - bSquared) / bSquared;
+  var A = 1 + uSquared / 16384 *
+      (4096 + uSquared * (uSquared * (320 - 175 * uSquared) - 768));
+  var B = uSquared / 1024 *
+      (256 + uSquared * (uSquared * (74 - 47 * uSquared) - 128));
+  var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 *
+      (cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1) -
+       B / 6 * cos2SigmaM * (4 * sinSigma * sinSigma - 3) *
+       (4 * cos2SigmaM * cos2SigmaM - 3)));
+  cosLambda = Math.cos(lambda);
+  sinLambda = Math.sin(lambda);
+  var alpha1 = Math.atan2(cosU2 * sinLambda,
+                          cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
+  var alpha2 = Math.atan2(cosU1 * sinLambda,
+                          cosU1 * sinU2 * cosLambda - sinU1 * cosU2);
+  return {
+    distance: this.b * A * (sigma - deltaSigma),
+    initialBearing: goog.math.toDegrees(alpha1),
+    finalBearing: goog.math.toDegrees(alpha2)
+  };
+};
+
+
+/**
+ * Returns the distance from c1 to c2 using Vincenty.
+ *
+ * @param {ol.Coordinate} c1 Coordinate 1.
+ * @param {ol.Coordinate} c2 Coordinate 1.
+ * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
+ * @param {number=} opt_maxIterations Maximum iterations.
+ * @return {number} Vincenty distance.
+ */
+ol.Ellipsoid.prototype.vincentyDistance =
+    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
+  var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations);
+  return vincenty.distance;
+};
+
+
+/**
+ * Returns the final bearing from c1 to c2 using Vincenty.
+ *
+ * @param {ol.Coordinate} c1 Coordinate 1.
+ * @param {ol.Coordinate} c2 Coordinate 1.
+ * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
+ * @param {number=} opt_maxIterations Maximum iterations.
+ * @return {number} Initial bearing.
+ */
+ol.Ellipsoid.prototype.vincentyFinalBearing =
+    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
+  var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations);
+  return vincenty.finalBearing;
+};
+
+
+/**
+ * Returns the initial bearing from c1 to c2 using Vincenty.
+ *
+ * @param {ol.Coordinate} c1 Coordinate 1.
+ * @param {ol.Coordinate} c2 Coordinate 1.
+ * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence.
+ * @param {number=} opt_maxIterations Maximum iterations.
+ * @return {number} Initial bearing.
+ */
+ol.Ellipsoid.prototype.vincentyInitialBearing =
+    function(c1, c2, opt_minDeltaLambda, opt_maxIterations) {
+  var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations);
+  return vincenty.initialBearing;
+};
+
+goog.provide('ol.ellipsoid.WGS84');
+
+goog.require('ol.Ellipsoid');
+
+
+/**
+ * @const
+ * @type {ol.Ellipsoid}
+ */
+ol.ellipsoid.WGS84 = new ol.Ellipsoid(6378137, 1 / 298.257223563);
+
+goog.provide('ol.Feature');
+goog.provide('ol.feature');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.functions');
+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 or feature overlay.
+ *
+ * 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}
+ * @fires change Triggered when the id, the geometry or the style of the
+ *     feature changes.
+ * @param {ol.geom.Geometry|Object.<string, *>=} opt_geometryOrProperties
+ *     You may pass a Geometry object directly, or an object literal
+ *     containing properties.  If you pass an object literal, you may
+ *     include a Geometry associated with a `geometry` key.
+ * @api stable
+ */
+ol.Feature = function(opt_geometryOrProperties) {
+
+  goog.base(this);
+
+  /**
+   * @private
+   * @type {number|string|undefined}
+   */
+  this.id_ = undefined;
+
+  /**
+   * @type {string}
+   * @private
+   */
+  this.geometryName_ = 'geometry';
+
+  /**
+   * User provided style.
+   * @private
+   * @type {ol.style.Style|Array.<ol.style.Style>|
+   *     ol.feature.FeatureStyleFunction}
+   */
+  this.style_ = null;
+
+  /**
+   * @private
+   * @type {ol.feature.FeatureStyleFunction|undefined}
+   */
+  this.styleFunction_ = undefined;
+
+  /**
+   * @private
+   * @type {goog.events.Key}
+   */
+  this.geometryChangeKey_ = null;
+
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(this.geometryName_),
+      this.handleGeometryChanged_, false, this);
+
+  if (goog.isDef(opt_geometryOrProperties)) {
+    if (opt_geometryOrProperties instanceof ol.geom.Geometry ||
+        goog.isNull(opt_geometryOrProperties)) {
+      var geometry = /** @type {ol.geom.Geometry} */ (opt_geometryOrProperties);
+      this.setGeometry(geometry);
+    } else {
+      goog.asserts.assert(goog.isObject(opt_geometryOrProperties));
+      var properties = /** @type {Object.<string, *>} */
+          (opt_geometryOrProperties);
+      this.setProperties(properties);
+    }
+  }
+};
+goog.inherits(ol.Feature, ol.Object);
+
+
+/**
+ * Clone this feature. If the original feature has a geometry it
+ * is also cloned. The feature id is not set in the clone.
+ * @return {ol.Feature} The clone.
+ * @api stable
+ */
+ol.Feature.prototype.clone = function() {
+  var clone = new ol.Feature(this.getProperties());
+  clone.setGeometryName(this.getGeometryName());
+  var geometry = this.getGeometry();
+  if (goog.isDefAndNotNull(geometry)) {
+    clone.setGeometry(geometry.clone());
+  }
+  var style = this.getStyle();
+  if (!goog.isNull(style)) {
+    clone.setStyle(style);
+  }
+  return clone;
+};
+
+
+/**
+ * @return {ol.geom.Geometry|undefined} Returns the Geometry associated
+ *     with this feature using the current geometry name property.  By
+ *     default, this is `geometry` but it may be changed by calling
+ *     `setGeometryName`.
+ * @api stable
+ * @observable
+ */
+ol.Feature.prototype.getGeometry = function() {
+  return /** @type {ol.geom.Geometry|undefined} */ (
+      this.get(this.geometryName_));
+};
+goog.exportProperty(
+    ol.Feature.prototype,
+    'getGeometry',
+    ol.Feature.prototype.getGeometry);
+
+
+/**
+ * @return {number|string|undefined} Id.
+ * @api stable
+ */
+ol.Feature.prototype.getId = function() {
+  return this.id_;
+};
+
+
+/**
+ * @return {string} Get the property name associated with the geometry for
+ *     this feature.  By default, this is `geometry` but it may be changed by
+ *     calling `setGeometryName`.
+ * @api stable
+ */
+ol.Feature.prototype.getGeometryName = function() {
+  return this.geometryName_;
+};
+
+
+/**
+ * @return {ol.style.Style|Array.<ol.style.Style>|
+ *     ol.feature.FeatureStyleFunction} Return the style as set by `setStyle`
+ * in the same format that it was provided in. If `setStyle` has not been
+ * called, or if it was called with `null`, then `getStyle()` will return
+ * `null`.
+ * @api stable
+ */
+ol.Feature.prototype.getStyle = function() {
+  return this.style_;
+};
+
+
+/**
+ * @return {ol.feature.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 (!goog.isNull(this.geometryChangeKey_)) {
+    goog.events.unlistenByKey(this.geometryChangeKey_);
+    this.geometryChangeKey_ = null;
+  }
+  var geometry = this.getGeometry();
+  if (goog.isDefAndNotNull(geometry)) {
+    this.geometryChangeKey_ = goog.events.listen(geometry,
+        goog.events.EventType.CHANGE, this.handleGeometryChange_, false, this);
+    this.changed();
+  }
+};
+
+
+/**
+ * @param {ol.geom.Geometry|undefined} geometry Set the geometry for this
+ * feature. This will update the property associated with the current
+ * geometry property name.  By default, this is `geometry` but it can be
+ * changed by calling `setGeometryName`.
+ * @api stable
+ * @observable
+ */
+ol.Feature.prototype.setGeometry = function(geometry) {
+  this.set(this.geometryName_, geometry);
+};
+goog.exportProperty(
+    ol.Feature.prototype,
+    'setGeometry',
+    ol.Feature.prototype.setGeometry);
+
+
+/**
+ * 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.feature.FeatureStyleFunction} style Style for this feature.
+ * @api stable
+ */
+ol.Feature.prototype.setStyle = function(style) {
+  this.style_ = style;
+  this.styleFunction_ = goog.isNull(style) ?
+      undefined : ol.feature.createFeatureStyleFunction(style);
+  this.changed();
+};
+
+
+/**
+ * @param {number|string|undefined} id Set a unique id for this feature.
+ * The id may be used to retrieve a feature from a vector source with the
+ * {@link ol.source.Vector#getFeatureById} method.
+ * @api stable
+ */
+ol.Feature.prototype.setId = function(id) {
+  this.id_ = id;
+  this.changed();
+};
+
+
+/**
+ * @param {string} name Set the property name from which this feature's
+ *     geometry will be fetched when calling `getGeometry`.
+ * @api stable
+ */
+ol.Feature.prototype.setGeometryName = function(name) {
+  goog.events.unlisten(
+      this, ol.Object.getChangeEventType(this.geometryName_),
+      this.handleGeometryChanged_, false, this);
+  this.geometryName_ = name;
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(this.geometryName_),
+      this.handleGeometryChanged_, false, this);
+  this.handleGeometryChanged_();
+};
+
+
+/**
+ * A function that takes a `{number}` representing the view's resolution. It
+ * returns an Array of {@link ol.style.Style}. This way individual features
+ * can be styled. The this keyword inside the function references the
+ * {@link ol.Feature} to be styled.
+ *
+ * @typedef {function(this: ol.Feature, number): Array.<ol.style.Style>}
+ * @api stable
+ */
+ol.feature.FeatureStyleFunction;
+
+
+/**
+ * Convert the provided object into a feature style function.  Functions passed
+ * through unchanged.  Arrays of ol.style.Style or single style objects wrapped
+ * in a new feature style function.
+ * @param {ol.feature.FeatureStyleFunction|!Array.<ol.style.Style>|
+ *     !ol.style.Style} obj A feature style function, a single style, or an
+ *     array of styles.
+ * @return {ol.feature.FeatureStyleFunction} A style function.
+ */
+ol.feature.createFeatureStyleFunction = function(obj) {
+  /**
+   * @type {ol.feature.FeatureStyleFunction}
+   */
+  var styleFunction;
+
+  if (goog.isFunction(obj)) {
+    styleFunction = /** @type {ol.feature.FeatureStyleFunction} */ (obj);
+  } else {
+    /**
+     * @type {Array.<ol.style.Style>}
+     */
+    var styles;
+    if (goog.isArray(obj)) {
+      styles = obj;
+    } else {
+      goog.asserts.assertInstanceof(obj, ol.style.Style);
+      styles = [obj];
+    }
+    styleFunction = goog.functions.constant(styles);
+  }
+  return styleFunction;
+};
+
+goog.provide('ol.FeatureOverlay');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Feature');
+goog.require('ol.render.EventType');
+goog.require('ol.renderer.vector');
+goog.require('ol.style.Style');
+
+
+
+/**
+ * @classdesc
+ * A mechanism for changing the style of a small number of features on a
+ * temporary basis, for example highlighting. This is necessary with the Canvas
+ * renderer, where, unlike in SVG, features cannot be individually referenced.
+ * See examples/vector-layers for an example: create a FeatureOverlay with a
+ * different style, copy the feature(s) you want rendered in this different
+ * style into it, and then remove them again when you're finished.
+ *
+ * @constructor
+ * @param {olx.FeatureOverlayOptions=} opt_options Options.
+ * @api
+ */
+ol.FeatureOverlay = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {ol.Collection.<ol.Feature>}
+   */
+  this.features_ = null;
+
+  /**
+   * @private
+   * @type {Array.<goog.events.Key>}
+   */
+  this.featuresListenerKeys_ = null;
+
+  /**
+   * @private
+   * @type {Object.<string, goog.events.Key>}
+   */
+  this.featureChangeListenerKeys_ = null;
+
+  /**
+   * @private
+   * @type {ol.Map}
+   */
+  this.map_ = null;
+
+  /**
+   * @private
+   * @type {goog.events.Key}
+   */
+  this.postComposeListenerKey_ = null;
+
+  /**
+   * @private
+   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
+   */
+  this.style_ = null;
+
+  /**
+   * @private
+   * @type {ol.style.StyleFunction|undefined}
+   */
+  this.styleFunction_ = undefined;
+
+  this.setStyle(goog.isDef(options.style) ?
+      options.style : ol.style.defaultStyleFunction);
+
+  if (goog.isDef(options.features)) {
+    if (goog.isArray(options.features)) {
+      this.setFeatures(new ol.Collection(goog.array.clone(options.features)));
+    } else {
+      goog.asserts.assertInstanceof(options.features, ol.Collection);
+      this.setFeatures(options.features);
+    }
+  } else {
+    this.setFeatures(new ol.Collection());
+  }
+
+  if (goog.isDef(options.map)) {
+    this.setMap(options.map);
+  }
+
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @api
+ */
+ol.FeatureOverlay.prototype.addFeature = function(feature) {
+  this.features_.push(feature);
+};
+
+
+/**
+ * @return {ol.Collection.<ol.Feature>} Features collection.
+ * @api
+ */
+ol.FeatureOverlay.prototype.getFeatures = function() {
+  return this.features_;
+};
+
+
+/**
+ * @private
+ */
+ol.FeatureOverlay.prototype.handleFeatureChange_ = function() {
+  this.render_();
+};
+
+
+/**
+ * @private
+ * @param {ol.CollectionEvent} collectionEvent Collection event.
+ */
+ol.FeatureOverlay.prototype.handleFeaturesAdd_ = function(collectionEvent) {
+  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
+  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
+  this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
+      goog.events.listen(feature, goog.events.EventType.CHANGE,
+      this.handleFeatureChange_, false, this);
+  this.render_();
+};
+
+
+/**
+ * @private
+ * @param {ol.CollectionEvent} collectionEvent Collection event.
+ */
+ol.FeatureOverlay.prototype.handleFeaturesRemove_ = function(collectionEvent) {
+  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
+  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
+  var key = goog.getUid(feature).toString();
+  goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]);
+  delete this.featureChangeListenerKeys_[key];
+  this.render_();
+};
+
+
+/**
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
+ * @private
+ */
+ol.FeatureOverlay.prototype.handleImageChange_ = function(event) {
+  this.render_();
+};
+
+
+/**
+ * @param {ol.render.Event} event Event.
+ * @private
+ */
+ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) {
+  if (goog.isNull(this.features_)) {
+    return;
+  }
+  var styleFunction = this.styleFunction_;
+  if (!goog.isDef(styleFunction)) {
+    styleFunction = ol.style.defaultStyleFunction;
+  }
+  var replayGroup = /** @type {ol.render.IReplayGroup} */
+      (event.replayGroup);
+  goog.asserts.assert(goog.isDef(replayGroup));
+  var frameState = event.frameState;
+  var pixelRatio = frameState.pixelRatio;
+  var resolution = frameState.viewState.resolution;
+  var squaredTolerance = ol.renderer.vector.getSquaredTolerance(resolution,
+      pixelRatio);
+  var i, ii, styles, featureStyleFunction;
+  this.features_.forEach(function(feature) {
+    featureStyleFunction = feature.getStyleFunction();
+    styles = goog.isDef(featureStyleFunction) ?
+        featureStyleFunction.call(feature, resolution) :
+        styleFunction(feature, resolution);
+
+    if (!goog.isDefAndNotNull(styles)) {
+      return;
+    }
+    ii = styles.length;
+    for (i = 0; i < ii; ++i) {
+      ol.renderer.vector.renderFeature(replayGroup, feature, styles[i],
+          squaredTolerance, this.handleImageChange_, this);
+    }
+  }, this);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @api
+ */
+ol.FeatureOverlay.prototype.removeFeature = function(feature) {
+  this.features_.remove(feature);
+};
+
+
+/**
+ * @private
+ */
+ol.FeatureOverlay.prototype.render_ = function() {
+  if (!goog.isNull(this.map_)) {
+    this.map_.render();
+  }
+};
+
+
+/**
+ * @param {ol.Collection.<ol.Feature>} features Features collection.
+ * @api
+ */
+ol.FeatureOverlay.prototype.setFeatures = function(features) {
+  if (!goog.isNull(this.featuresListenerKeys_)) {
+    goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey);
+    this.featuresListenerKeys_ = null;
+  }
+  if (!goog.isNull(this.featureChangeListenerKeys_)) {
+    goog.array.forEach(
+        goog.object.getValues(this.featureChangeListenerKeys_),
+        goog.events.unlistenByKey);
+    this.featureChangeListenerKeys_ = null;
+  }
+  this.features_ = features;
+  if (!goog.isNull(features)) {
+    this.featuresListenerKeys_ = [
+      goog.events.listen(features, ol.CollectionEventType.ADD,
+          this.handleFeaturesAdd_, false, this),
+      goog.events.listen(features, ol.CollectionEventType.REMOVE,
+          this.handleFeaturesRemove_, false, this)
+    ];
+    this.featureChangeListenerKeys_ = {};
+    features.forEach(function(feature) {
+      this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
+          goog.events.listen(feature, goog.events.EventType.CHANGE,
+          this.handleFeatureChange_, false, this);
+    }, this);
+  }
+  this.render_();
+};
+
+
+/**
+ * @param {ol.Map} map Map.
+ * @api
+ */
+ol.FeatureOverlay.prototype.setMap = function(map) {
+  if (!goog.isNull(this.postComposeListenerKey_)) {
+    goog.events.unlistenByKey(this.postComposeListenerKey_);
+    this.postComposeListenerKey_ = null;
+  }
+  this.render_();
+  this.map_ = map;
+  if (!goog.isNull(map)) {
+    this.postComposeListenerKey_ = goog.events.listen(
+        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
+        this);
+    map.render();
+  }
+};
+
+
+/**
+ * Set the style for features.  This can be a single style object, an array
+ * of styles, or a function that takes a feature and resolution and returns
+ * an array of styles.
+ * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} style
+ *     Overlay style.
+ * @api
+ */
+ol.FeatureOverlay.prototype.setStyle = function(style) {
+  this.style_ = style;
+  this.styleFunction_ = ol.style.createStyleFunction(style);
+  this.render_();
+};
+
+
+/**
+ * Get the style for features.  This returns whatever was passed to the `style`
+ * option at construction or to the `setStyle` method.
+ * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
+ *     Overlay style.
+ * @api
+ */
+ol.FeatureOverlay.prototype.getStyle = function() {
+  return this.style_;
+};
+
+
+/**
+ * Get the style function.
+ * @return {ol.style.StyleFunction|undefined} Style function.
+ * @api
+ */
+ol.FeatureOverlay.prototype.getStyleFunction = function() {
+  return this.styleFunction_;
+};
+
+goog.provide('ol.format.Feature');
+
+goog.require('goog.array');
+goog.require('ol.geom.Geometry');
+goog.require('ol.proj');
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for feature formats.
+ * {ol.format.Feature} subclasses provide the ability to decode and encode
+ * {@link ol.Feature} objects from a variety of commonly used geospatial
+ * file formats.  See the documentation for each format for more details.
+ *
+ * @constructor
+ * @api stable
+ */
+ol.format.Feature = function() {
+
+  /**
+   * @protected
+   * @type {ol.proj.Projection}
+   */
+  this.defaultDataProjection = null;
+};
+
+
+/**
+ * @return {Array.<string>} Extensions.
+ */
+ol.format.Feature.prototype.getExtensions = goog.abstractMethod;
+
+
+/**
+ * Adds the data projection to the read options.
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {olx.format.ReadOptions|undefined} Options.
+ * @protected
+ */
+ol.format.Feature.prototype.getReadOptions = function(
+    source, opt_options) {
+  var options;
+  if (goog.isDef(opt_options)) {
+    options = {
+      dataProjection: goog.isDef(opt_options.dataProjection) ?
+          opt_options.dataProjection : this.readProjection(source),
+      featureProjection: opt_options.featureProjection
+    };
+  }
+  return this.adaptOptions(options);
+};
+
+
+/**
+ * Sets the `defaultDataProjection` on the options, if no `dataProjection`
+ * is set.
+ * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options
+ *     Options.
+ * @protected
+ * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined}
+ *     Updated options.
+ */
+ol.format.Feature.prototype.adaptOptions = function(
+    options) {
+  var updatedOptions;
+  if (goog.isDef(options)) {
+    updatedOptions = {
+      featureProjection: options.featureProjection,
+      dataProjection: goog.isDefAndNotNull(options.dataProjection) ?
+          options.dataProjection : this.defaultDataProjection
+    };
+  }
+  return updatedOptions;
+};
+
+
+/**
+ * @return {ol.format.FormatType} Format.
+ */
+ol.format.Feature.prototype.getType = goog.abstractMethod;
+
+
+/**
+ * Read a single feature from a source.
+ *
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ */
+ol.format.Feature.prototype.readFeature = goog.abstractMethod;
+
+
+/**
+ * Read all features from a source.
+ *
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.Feature.prototype.readFeatures = goog.abstractMethod;
+
+
+/**
+ * Read a single geometry from a source.
+ *
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.Feature.prototype.readGeometry = goog.abstractMethod;
+
+
+/**
+ * Read the projection from a source.
+ *
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.Feature.prototype.readProjection = goog.abstractMethod;
+
+
+/**
+ * Encode a feature in this format.
+ *
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
+ */
+ol.format.Feature.prototype.writeFeature = goog.abstractMethod;
+
+
+/**
+ * Encode an array of features in this format.
+ *
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
+ */
+ol.format.Feature.prototype.writeFeatures = goog.abstractMethod;
+
+
+/**
+ * Write a single geometry in this format.
+ *
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
+ */
+ol.format.Feature.prototype.writeGeometry = goog.abstractMethod;
+
+
+/**
+ * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
+ * @param {boolean} write Set to true for writing, false for reading.
+ * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options
+ *     Options.
+ * @return {ol.geom.Geometry|ol.Extent} Transformed geometry.
+ * @protected
+ */
+ol.format.Feature.transformWithOptions = function(
+    geometry, write, opt_options) {
+  var featureProjection = goog.isDef(opt_options) ?
+      ol.proj.get(opt_options.featureProjection) : null;
+  var dataProjection = goog.isDef(opt_options) ?
+      ol.proj.get(opt_options.dataProjection) : null;
+  if (!goog.isNull(featureProjection) && !goog.isNull(dataProjection) &&
+      !ol.proj.equivalent(featureProjection, dataProjection)) {
+    if (geometry instanceof ol.geom.Geometry) {
+      return (write ? geometry.clone() : geometry).transform(
+          write ? featureProjection : dataProjection,
+          write ? dataProjection : featureProjection);
+    } else {
+      // FIXME this is necessary because ol.format.GML treats extents
+      // as geometries
+      return ol.proj.transformExtent(
+          write ? goog.array.clone(geometry) : geometry,
+          write ? featureProjection : dataProjection,
+          write ? dataProjection : featureProjection);
+    }
+  } else {
+    return geometry;
+  }
+};
+
+goog.provide('ol.format.FormatType');
+
+
+/**
+ * @enum {string}
+ */
+ol.format.FormatType = {
+  BINARY: 'binary',
+  JSON: 'json',
+  TEXT: 'text',
+  XML: 'xml'
+};
+
+goog.provide('ol.format.BinaryFeature');
+
+goog.require('goog.asserts');
+goog.require('ol.binary.Buffer');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+goog.require('ol.has');
+goog.require('ol.proj');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.format.Feature}
+ */
+ol.format.BinaryFeature = function() {
+  goog.base(this);
+};
+goog.inherits(ol.format.BinaryFeature, ol.format.Feature);
+
+
+/**
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @private
+ * @return {ol.binary.Buffer} Buffer.
+ */
+ol.format.BinaryFeature.getBuffer_ = function(source) {
+  if (ol.has.ARRAY_BUFFER && source instanceof ArrayBuffer) {
+    return new ol.binary.Buffer(source);
+  } else if (goog.isString(source)) {
+    return new ol.binary.Buffer(source);
+  } else {
+    goog.asserts.fail();
+    return null;
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.BinaryFeature.prototype.getType = function() {
+  return ol.format.FormatType.BINARY;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.BinaryFeature.prototype.readFeature = function(source) {
+  return this.readFeatureFromBuffer(ol.format.BinaryFeature.getBuffer_(source));
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.BinaryFeature.prototype.readFeatures = function(source) {
+  return this.readFeaturesFromBuffer(
+      ol.format.BinaryFeature.getBuffer_(source));
+};
+
+
+/**
+ * @param {ol.binary.Buffer} buffer Buffer.
+ * @protected
+ * @return {ol.Feature} Feature.
+ */
+ol.format.BinaryFeature.prototype.readFeatureFromBuffer = goog.abstractMethod;
+
+
+/**
+ * @param {ol.binary.Buffer} buffer Buffer.
+ * @protected
+ * @return {Array.<ol.Feature>} Feature.
+ */
+ol.format.BinaryFeature.prototype.readFeaturesFromBuffer = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.BinaryFeature.prototype.readGeometry = function(source) {
+  return this.readGeometryFromBuffer(
+      ol.format.BinaryFeature.getBuffer_(source));
+};
+
+
+/**
+ * @param {ol.binary.Buffer} buffer Buffer.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.BinaryFeature.prototype.readGeometryFromBuffer = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.BinaryFeature.prototype.readProjection = function(source) {
+  return this.readProjectionFromBuffer(
+      ol.format.BinaryFeature.getBuffer_(source));
+};
+
+
+/**
+ * @param {ol.binary.Buffer} buffer Buffer.
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.BinaryFeature.prototype.readProjectionFromBuffer =
+    goog.abstractMethod;
+
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview JSON utility functions.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+goog.provide('goog.json');
+goog.provide('goog.json.Replacer');
+goog.provide('goog.json.Reviver');
+goog.provide('goog.json.Serializer');
+
+
+/**
+ * @define {boolean} If true, use the native JSON parsing API.
+ * NOTE(ruilopes): EXPERIMENTAL, handle with care.  Setting this to true might
+ * break your code.  The default {@code goog.json.parse} implementation is able
+ * to handle invalid JSON, such as JSPB.
+ */
+goog.define('goog.json.USE_NATIVE_JSON', false);
+
+
+/**
+ * Tests if a string is an invalid JSON string. This only ensures that we are
+ * not using any invalid characters
+ * @param {string} s The string to test.
+ * @return {boolean} True if the input is a valid JSON string.
+ */
+goog.json.isValid = function(s) {
+  // All empty whitespace is not valid.
+  if (/^\s*$/.test(s)) {
+    return false;
+  }
+
+  // This is taken from http://www.json.org/json2.js which is released to the
+  // public domain.
+  // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
+  // inside strings.  We also treat \u2028 and \u2029 as whitespace which they
+  // are in the RFC but IE and Safari does not match \s to these so we need to
+  // include them in the reg exps in all places where whitespace is allowed.
+  // We allowed \x7f inside strings because some tools don't escape it,
+  // e.g. http://www.json.org/java/org/json/JSONObject.java
+
+  // Parsing happens in three stages. In the first stage, we run the text
+  // against regular expressions that look for non-JSON patterns. We are
+  // especially concerned with '()' and 'new' because they can cause invocation,
+  // and '=' because it can cause mutation. But just to be safe, we want to
+  // reject all unexpected forms.
+
+  // We split the first stage into 4 regexp operations in order to work around
+  // crippling inefficiencies in IE's and Safari's regexp engines. First we
+  // replace all backslash pairs with '@' (a non-JSON character). Second, we
+  // replace all simple value tokens with ']' characters. Third, we delete all
+  // open brackets that follow a colon or comma or that begin the text. Finally,
+  // we look to see that the remaining characters are only whitespace or ']' or
+  // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+  // Don't make these static since they have the global flag.
+  var backslashesRe = /\\["\\\/bfnrtu]/g;
+  var simpleValuesRe =
+      /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+  var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
+  var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
+
+  return remainderRe.test(s.replace(backslashesRe, '@').
+      replace(simpleValuesRe, ']').
+      replace(openBracketsRe, ''));
+};
+
+
+/**
+ * Parses a JSON string and returns the result. This throws an exception if
+ * the string is an invalid JSON string.
+ *
+ * Note that this is very slow on large strings. If you trust the source of
+ * the string then you should use unsafeParse instead.
+ *
+ * @param {*} s The JSON string to parse.
+ * @throws Error if s is invalid JSON.
+ * @return {Object} The object generated from the JSON string, or null.
+ */
+goog.json.parse = goog.json.USE_NATIVE_JSON ?
+    /** @type {function(*):Object} */ (goog.global['JSON']['parse']) :
+    function(s) {
+      var o = String(s);
+      if (goog.json.isValid(o)) {
+        /** @preserveTry */
+        try {
+          return /** @type {Object} */ (eval('(' + o + ')'));
+        } catch (ex) {
+        }
+      }
+      throw Error('Invalid JSON string: ' + o);
+    };
+
+
+/**
+ * Parses a JSON string and returns the result. This uses eval so it is open
+ * to security issues and it should only be used if you trust the source.
+ *
+ * @param {string} s The JSON string to parse.
+ * @return {Object} The object generated from the JSON string.
+ */
+goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ?
+    /** @type {function(string):Object} */ (goog.global['JSON']['parse']) :
+    function(s) {
+      return /** @type {Object} */ (eval('(' + s + ')'));
+    };
+
+
+/**
+ * JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
+ * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
+ *
+ * TODO(nicksantos): Array should also be a valid replacer.
+ *
+ * @typedef {function(this:Object, string, *): *}
+ */
+goog.json.Replacer;
+
+
+/**
+ * JSON reviver, as defined in Section 15.12.2 of the ES5 spec.
+ * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
+ *
+ * @typedef {function(this:Object, string, *): *}
+ */
+goog.json.Reviver;
+
+
+/**
+ * Serializes an object or a value to a JSON string.
+ *
+ * @param {*} object The object to serialize.
+ * @param {?goog.json.Replacer=} opt_replacer A replacer function
+ *     called for each (key, value) pair that determines how the value
+ *     should be serialized. By defult, this just returns the value
+ *     and allows default serialization to kick in.
+ * @throws Error if there are loops in the object graph.
+ * @return {string} A JSON string representation of the input.
+ */
+goog.json.serialize = goog.json.USE_NATIVE_JSON ?
+    /** @type {function(*, ?goog.json.Replacer=):string} */
+    (goog.global['JSON']['stringify']) :
+    function(object, opt_replacer) {
+      // NOTE(nicksantos): Currently, we never use JSON.stringify.
+      //
+      // The last time I evaluated this, JSON.stringify had subtle bugs and
+      // behavior differences on all browsers, and the performance win was not
+      // large enough to justify all the issues. This may change in the future
+      // as browser implementations get better.
+      //
+      // assertSerialize in json_test contains if branches for the cases
+      // that fail.
+      return new goog.json.Serializer(opt_replacer).serialize(object);
+    };
+
+
+
+/**
+ * Class that is used to serialize JSON objects to a string.
+ * @param {?goog.json.Replacer=} opt_replacer Replacer.
+ * @constructor
+ */
+goog.json.Serializer = function(opt_replacer) {
+  /**
+   * @type {goog.json.Replacer|null|undefined}
+   * @private
+   */
+  this.replacer_ = opt_replacer;
+};
+
+
+/**
+ * Serializes an object or a value to a JSON string.
+ *
+ * @param {*} object The object to serialize.
+ * @throws Error if there are loops in the object graph.
+ * @return {string} A JSON string representation of the input.
+ */
+goog.json.Serializer.prototype.serialize = function(object) {
+  var sb = [];
+  this.serializeInternal(object, sb);
+  return sb.join('');
+};
+
+
+/**
+ * Serializes a generic value to a JSON string
+ * @protected
+ * @param {*} object The object to serialize.
+ * @param {Array<string>} sb Array used as a string builder.
+ * @throws Error if there are loops in the object graph.
+ */
+goog.json.Serializer.prototype.serializeInternal = function(object, sb) {
+  switch (typeof object) {
+    case 'string':
+      this.serializeString_(/** @type {string} */ (object), sb);
+      break;
+    case 'number':
+      this.serializeNumber_(/** @type {number} */ (object), sb);
+      break;
+    case 'boolean':
+      sb.push(object);
+      break;
+    case 'undefined':
+      sb.push('null');
+      break;
+    case 'object':
+      if (object == null) {
+        sb.push('null');
+        break;
+      }
+      if (goog.isArray(object)) {
+        this.serializeArray(/** @type {!Array<?>} */ (object), sb);
+        break;
+      }
+      // should we allow new String, new Number and new Boolean to be treated
+      // as string, number and boolean? Most implementations do not and the
+      // need is not very big
+      this.serializeObject_(/** @type {Object} */ (object), sb);
+      break;
+    case 'function':
+      // Skip functions.
+      // TODO(user) Should we return something here?
+      break;
+    default:
+      throw Error('Unknown type: ' + typeof object);
+  }
+};
+
+
+/**
+ * Character mappings used internally for goog.string.quote
+ * @private
+ * @type {!Object}
+ */
+goog.json.Serializer.charToJsonCharCache_ = {
+  '\"': '\\"',
+  '\\': '\\\\',
+  '/': '\\/',
+  '\b': '\\b',
+  '\f': '\\f',
+  '\n': '\\n',
+  '\r': '\\r',
+  '\t': '\\t',
+
+  '\x0B': '\\u000b' // '\v' is not supported in JScript
+};
+
+
+/**
+ * Regular expression used to match characters that need to be replaced.
+ * The S60 browser has a bug where unicode characters are not matched by
+ * regular expressions. The condition below detects such behaviour and
+ * adjusts the regular expression accordingly.
+ * @private
+ * @type {!RegExp}
+ */
+goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
+    /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;
+
+
+/**
+ * Serializes a string to a JSON string
+ * @private
+ * @param {string} s The string to serialize.
+ * @param {Array<string>} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
+  // The official JSON implementation does not work with international
+  // characters.
+  sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
+    // caching the result improves performance by a factor 2-3
+    if (c in goog.json.Serializer.charToJsonCharCache_) {
+      return goog.json.Serializer.charToJsonCharCache_[c];
+    }
+
+    var cc = c.charCodeAt(0);
+    var rv = '\\u';
+    if (cc < 16) {
+      rv += '000';
+    } else if (cc < 256) {
+      rv += '00';
+    } else if (cc < 4096) { // \u1000
+      rv += '0';
+    }
+    return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16);
+  }), '"');
+};
+
+
+/**
+ * Serializes a number to a JSON string
+ * @private
+ * @param {number} n The number to serialize.
+ * @param {Array<string>} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
+  sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
+};
+
+
+/**
+ * Serializes an array to a JSON string
+ * @param {Array<string>} arr The array to serialize.
+ * @param {Array<string>} sb Array used as a string builder.
+ * @protected
+ */
+goog.json.Serializer.prototype.serializeArray = function(arr, sb) {
+  var l = arr.length;
+  sb.push('[');
+  var sep = '';
+  for (var i = 0; i < l; i++) {
+    sb.push(sep);
+
+    var value = arr[i];
+    this.serializeInternal(
+        this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
+        sb);
+
+    sep = ',';
+  }
+  sb.push(']');
+};
+
+
+/**
+ * Serializes an object to a JSON string
+ * @private
+ * @param {Object} obj The object to serialize.
+ * @param {Array<string>} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
+  sb.push('{');
+  var sep = '';
+  for (var key in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, key)) {
+      var value = obj[key];
+      // Skip functions.
+      // TODO(ptucker) Should we return something for function properties?
+      if (typeof value != 'function') {
+        sb.push(sep);
+        this.serializeString_(key, sb);
+        sb.push(':');
+
+        this.serializeInternal(
+            this.replacer_ ? this.replacer_.call(obj, key, value) : value,
+            sb);
+
+        sep = ',';
+      }
+    }
+  }
+  sb.push('}');
+};
+
+goog.provide('ol.format.JSONFeature');
+
+goog.require('goog.asserts');
+goog.require('goog.json');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for JSON feature formats.
+ *
+ * @constructor
+ * @extends {ol.format.Feature}
+ */
+ol.format.JSONFeature = function() {
+  goog.base(this);
+};
+goog.inherits(ol.format.JSONFeature, ol.format.Feature);
+
+
+/**
+ * @param {Document|Node|Object|string} source Source.
+ * @private
+ * @return {Object} Object.
+ */
+ol.format.JSONFeature.prototype.getObject_ = function(source) {
+  if (goog.isObject(source)) {
+    return source;
+  } else if (goog.isString(source)) {
+    var object = goog.json.parse(source);
+    return goog.isDef(object) ? object : null;
+  } else {
+    goog.asserts.fail();
+    return null;
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.getType = function() {
+  return ol.format.FormatType.JSON;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) {
+  return this.readFeatureFromObject(
+      this.getObject_(source), this.getReadOptions(source, opt_options));
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) {
+  return this.readFeaturesFromObject(
+      this.getObject_(source), this.getReadOptions(source, opt_options));
+};
+
+
+/**
+ * @param {Object} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.Feature} Feature.
+ */
+ol.format.JSONFeature.prototype.readFeatureFromObject = goog.abstractMethod;
+
+
+/**
+ * @param {Object} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.JSONFeature.prototype.readFeaturesFromObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) {
+  return this.readGeometryFromObject(
+      this.getObject_(source), this.getReadOptions(source, opt_options));
+};
+
+
+/**
+ * @param {Object} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.JSONFeature.prototype.readGeometryFromObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.readProjection = function(source) {
+  return this.readProjectionFromObject(this.getObject_(source));
+};
+
+
+/**
+ * @param {Object} object Object.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.JSONFeature.prototype.readProjectionFromObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) {
+  return goog.json.serialize(this.writeFeatureObject(feature, opt_options));
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
+ */
+ol.format.JSONFeature.prototype.writeFeatureObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.writeFeatures = function(
+    features, opt_options) {
+  return goog.json.serialize(this.writeFeaturesObject(features, opt_options));
+};
+
+
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
+ */
+ol.format.JSONFeature.prototype.writeFeaturesObject = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.JSONFeature.prototype.writeGeometry = function(
+    geometry, opt_options) {
+  return goog.json.serialize(this.writeGeometryObject(geometry, opt_options));
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} Object.
+ */
+ol.format.JSONFeature.prototype.writeGeometryObject = goog.abstractMethod;
+
+// FIXME coordinate order
+// FIXME reprojection
+
+goog.provide('ol.format.GeoJSON');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.JSONFeature');
+goog.require('ol.geom.GeometryCollection');
+goog.require('ol.geom.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.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 = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this);
+
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get(
+      goog.isDefAndNotNull(options.defaultDataProjection) ?
+          options.defaultDataProjection : 'EPSG:4326');
+
+
+  /**
+   * Name of the geometry attribute for features.
+   * @type {string|undefined}
+   * @private
+   */
+  this.geometryName_ = options.geometryName;
+
+};
+goog.inherits(ol.format.GeoJSON, ol.format.JSONFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.GeoJSON.EXTENSIONS_ = ['.geojson'];
+
+
+/**
+ * @param {GeoJSONObject} 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 (goog.isNull(object)) {
+    return null;
+  }
+  var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type];
+  goog.asserts.assert(goog.isDef(geometryReader));
+  return /** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(
+          geometryReader(object), false, opt_options));
+};
+
+
+/**
+ * @param {GeoJSONGeometryCollection} object Object.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @private
+ * @return {ol.geom.GeometryCollection} Geometry collection.
+ */
+ol.format.GeoJSON.readGeometryCollectionGeometry_ = function(
+    object, opt_options) {
+  goog.asserts.assert(object.type == 'GeometryCollection');
+  var geometries = goog.array.map(object.geometries,
+      /**
+       * @param {GeoJSONObject} geometry Geometry.
+       * @return {ol.geom.Geometry} geometry Geometry.
+       */
+      function(geometry) {
+        return ol.format.GeoJSON.readGeometry_(geometry, opt_options);
+      });
+  return new ol.geom.GeometryCollection(geometries);
+};
+
+
+/**
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Point} Point.
+ */
+ol.format.GeoJSON.readPointGeometry_ = function(object) {
+  goog.asserts.assert(object.type == 'Point');
+  return new ol.geom.Point(object.coordinates);
+};
+
+
+/**
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.LineString} LineString.
+ */
+ol.format.GeoJSON.readLineStringGeometry_ = function(object) {
+  goog.asserts.assert(object.type == 'LineString');
+  return new ol.geom.LineString(object.coordinates);
+};
+
+
+/**
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiLineString} MultiLineString.
+ */
+ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) {
+  goog.asserts.assert(object.type == 'MultiLineString');
+  return new ol.geom.MultiLineString(object.coordinates);
+};
+
+
+/**
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiPoint} MultiPoint.
+ */
+ol.format.GeoJSON.readMultiPointGeometry_ = function(object) {
+  goog.asserts.assert(object.type == 'MultiPoint');
+  return new ol.geom.MultiPoint(object.coordinates);
+};
+
+
+/**
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.MultiPolygon} MultiPolygon.
+ */
+ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) {
+  goog.asserts.assert(object.type == 'MultiPolygon');
+  return new ol.geom.MultiPolygon(object.coordinates);
+};
+
+
+/**
+ * @param {GeoJSONGeometry} object Object.
+ * @private
+ * @return {ol.geom.Polygon} Polygon.
+ */
+ol.format.GeoJSON.readPolygonGeometry_ = function(object) {
+  goog.asserts.assert(object.type == 'Polygon');
+  return new ol.geom.Polygon(object.coordinates);
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry.
+ */
+ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) {
+  var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()];
+  goog.asserts.assert(goog.isDef(geometryWriter));
+  return geometryWriter(/** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(geometry, true, opt_options)));
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection.
+ */
+ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) {
+  return /** @type {GeoJSONGeometryCollection} */ ({
+    'type': 'GeometryCollection',
+    'geometries': []
+  });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @private
+ * @return {GeoJSONGeometryCollection} GeoJSON geometry collection.
+ */
+ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function(
+    geometry, opt_options) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection);
+  var geometries = goog.array.map(
+      geometry.getGeometriesArray(), function(geometry) {
+        return ol.format.GeoJSON.writeGeometry_(geometry, opt_options);
+      });
+  return /** @type {GeoJSONGeometryCollection} */ ({
+    'type': 'GeometryCollection',
+    'geometries': geometries
+  });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+  return /** @type {GeoJSONGeometry} */ ({
+    'type': 'LineString',
+    'coordinates': geometry.getCoordinates()
+  });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
+  goog.asserts.assert(
+      geometry.getType() == ol.geom.GeometryType.MULTI_LINE_STRING);
+  return /** @type {GeoJSONGeometry} */ ({
+    'type': 'MultiLineString',
+    'coordinates': geometry.getCoordinates()
+  });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint);
+  return /** @type {GeoJSONGeometry} */ ({
+    'type': 'MultiPoint',
+    'coordinates': geometry.getCoordinates()
+  });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon);
+  return /** @type {GeoJSONGeometry} */ ({
+    'type': 'MultiPolygon',
+    'coordinates': geometry.getCoordinates()
+  });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+ol.format.GeoJSON.writePointGeometry_ = function(geometry) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.Point);
+  return /** @type {GeoJSONGeometry} */ ({
+    'type': 'Point',
+    'coordinates': geometry.getCoordinates()
+  });
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @private
+ * @return {GeoJSONGeometry} GeoJSON geometry.
+ */
+ol.format.GeoJSON.writePolygonGeometry_ = function(geometry) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
+  return /** @type {GeoJSONGeometry} */ ({
+    'type': 'Polygon',
+    'coordinates': geometry.getCoordinates()
+  });
+};
+
+
+/**
+ * @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): (GeoJSONGeometry|GeoJSONGeometryCollection)>}
+ */
+ol.format.GeoJSON.GEOMETRY_WRITERS_ = {
+  'Point': ol.format.GeoJSON.writePointGeometry_,
+  'LineString': ol.format.GeoJSON.writeLineStringGeometry_,
+  'Polygon': ol.format.GeoJSON.writePolygonGeometry_,
+  'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_,
+  'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_,
+  'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_,
+  'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_,
+  'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.getExtensions = function() {
+  return ol.format.GeoJSON.EXTENSIONS_;
+};
+
+
+/**
+ * Read a feature from a GeoJSON Feature source.  Only works for Feature,
+ * use `readFeatures` to read FeatureCollection source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api stable
+ */
+ol.format.GeoJSON.prototype.readFeature;
+
+
+/**
+ * Read all features from a GeoJSON source.  Works with both Feature and
+ * FeatureCollection sources.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.format.GeoJSON.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.readFeatureFromObject = function(
+    object, opt_options) {
+  var geoJSONFeature = /** @type {GeoJSONFeature} */ (object);
+  goog.asserts.assert(geoJSONFeature.type == 'Feature');
+  var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry,
+      opt_options);
+  var feature = new ol.Feature();
+  if (goog.isDef(this.geometryName_)) {
+    feature.setGeometryName(this.geometryName_);
+  }
+  feature.setGeometry(geometry);
+  if (goog.isDef(geoJSONFeature.id)) {
+    feature.setId(geoJSONFeature.id);
+  }
+  if (goog.isDef(geoJSONFeature.properties)) {
+    feature.setProperties(geoJSONFeature.properties);
+  }
+  return feature;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.readFeaturesFromObject = function(
+    object, opt_options) {
+  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
+  if (geoJSONObject.type == 'Feature') {
+    return [this.readFeatureFromObject(object, opt_options)];
+  } else if (geoJSONObject.type == 'FeatureCollection') {
+    var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */
+        (object);
+    /** @type {Array.<ol.Feature>} */
+    var features = [];
+    var geoJSONFeatures = geoJSONFeatureCollection.features;
+    var i, ii;
+    for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) {
+      features.push(this.readFeatureFromObject(geoJSONFeatures[i],
+          opt_options));
+    }
+    return features;
+  } else {
+    goog.asserts.fail();
+    return [];
+  }
+};
+
+
+/**
+ * Read a geometry from a GeoJSON source.
+ *
+ * @function
+ * @param {ArrayBuffer|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 {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.GeoJSON.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) {
+  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
+  var crs = geoJSONObject.crs;
+  if (goog.isDefAndNotNull(crs)) {
+    if (crs.type == 'name') {
+      return ol.proj.get(crs.properties.name);
+    } else if (crs.type == 'EPSG') {
+      // 'EPSG' is not part of the GeoJSON specification, but is generated by
+      // GeoServer.
+      // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996
+      // is fixed and widely deployed.
+      return ol.proj.get('EPSG:' + crs.properties.code);
+    } else {
+      goog.asserts.fail();
+      return null;
+    }
+  } else {
+    return this.defaultDataProjection;
+  }
+};
+
+
+/**
+ * Encode a feature as a GeoJSON Feature string.
+ *
+ * @function
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions} 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.
+ * @api
+ * @return {Object} Object.
+ */
+ol.format.GeoJSON.prototype.writeFeatureObject = function(
+    feature, opt_options) {
+  opt_options = this.adaptOptions(opt_options);
+  var object = {
+    'type': 'Feature'
+  };
+  var id = feature.getId();
+  if (goog.isDefAndNotNull(id)) {
+    goog.object.set(object, 'id', id);
+  }
+  var geometry = feature.getGeometry();
+  if (goog.isDefAndNotNull(geometry)) {
+    goog.object.set(
+        object, 'geometry',
+        ol.format.GeoJSON.writeGeometry_(geometry, opt_options));
+  }
+  var properties = feature.getProperties();
+  goog.object.remove(properties, feature.getGeometryName());
+  if (!goog.object.isEmpty(properties)) {
+    goog.object.set(object, 'properties', properties);
+  }
+  return object;
+};
+
+
+/**
+ * Encode an array of features as GeoJSON.
+ *
+ * @function
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions} options Write options.
+ * @return {string} GeoJSON.
+ * @api stable
+ */
+ol.format.GeoJSON.prototype.writeFeatures;
+
+
+/**
+ * Encode an array of features as a GeoJSON object.
+ *
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {Object} GeoJSON Object.
+ * @api
+ */
+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} 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
+ */
+ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry,
+    opt_options) {
+  return ol.format.GeoJSON.writeGeometry_(geometry,
+      this.adaptOptions(opt_options));
+};
+
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview
+ * XML utilities.
+ *
+ */
+
+goog.provide('goog.dom.xml');
+
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+
+
+/**
+ * Max XML size for MSXML2.  Used to prevent potential DoS attacks.
+ * @type {number}
+ */
+goog.dom.xml.MAX_XML_SIZE_KB = 2 * 1024;  // In kB
+
+
+/**
+ * Max XML size for MSXML2.  Used to prevent potential DoS attacks.
+ * @type {number}
+ */
+goog.dom.xml.MAX_ELEMENT_DEPTH = 256; // Same default as MSXML6.
+
+
+/**
+ * Creates an XML document appropriate for the current JS runtime
+ * @param {string=} opt_rootTagName The root tag name.
+ * @param {string=} opt_namespaceUri Namespace URI of the document element.
+ * @return {Document} The new document.
+ */
+goog.dom.xml.createDocument = function(opt_rootTagName, opt_namespaceUri) {
+  if (opt_namespaceUri && !opt_rootTagName) {
+    throw Error("Can't create document with namespace and no root tag");
+  }
+  if (document.implementation && document.implementation.createDocument) {
+    return document.implementation.createDocument(opt_namespaceUri || '',
+                                                  opt_rootTagName || '',
+                                                  null);
+  } else if (typeof ActiveXObject != 'undefined') {
+    var doc = goog.dom.xml.createMsXmlDocument_();
+    if (doc) {
+      if (opt_rootTagName) {
+        doc.appendChild(doc.createNode(goog.dom.NodeType.ELEMENT,
+                                       opt_rootTagName,
+                                       opt_namespaceUri || ''));
+      }
+      return doc;
+    }
+  }
+  throw Error('Your browser does not support creating new documents');
+};
+
+
+/**
+ * Creates an XML document from a string
+ * @param {string} xml The text.
+ * @return {Document} XML document from the text.
+ */
+goog.dom.xml.loadXml = function(xml) {
+  if (typeof DOMParser != 'undefined') {
+    return new DOMParser().parseFromString(xml, 'application/xml');
+  } else if (typeof ActiveXObject != 'undefined') {
+    var doc = goog.dom.xml.createMsXmlDocument_();
+    doc.loadXML(xml);
+    return doc;
+  }
+  throw Error('Your browser does not support loading xml documents');
+};
+
+
+/**
+ * Serializes an XML document or subtree to string.
+ * @param {Document|Element} xml The document or the root node of the subtree.
+ * @return {string} The serialized XML.
+ */
+goog.dom.xml.serialize = function(xml) {
+  // Compatible with Firefox, Opera and WebKit.
+  if (typeof XMLSerializer != 'undefined') {
+    return new XMLSerializer().serializeToString(xml);
+  }
+  // Compatible with Internet Explorer.
+  var text = xml.xml;
+  if (text) {
+    return text;
+  }
+  throw Error('Your browser does not support serializing XML documents');
+};
+
+
+/**
+ * Selects a single node using an Xpath expression and a root node
+ * @param {Node} node The root node.
+ * @param {string} path Xpath selector.
+ * @return {Node} The selected node, or null if no matching node.
+ */
+goog.dom.xml.selectSingleNode = function(node, path) {
+  if (typeof node.selectSingleNode != 'undefined') {
+    var doc = goog.dom.getOwnerDocument(node);
+    if (typeof doc.setProperty != 'undefined') {
+      doc.setProperty('SelectionLanguage', 'XPath');
+    }
+    return node.selectSingleNode(path);
+  } else if (document.implementation.hasFeature('XPath', '3.0')) {
+    var doc = goog.dom.getOwnerDocument(node);
+    var resolver = doc.createNSResolver(doc.documentElement);
+    var result = doc.evaluate(path, node, resolver,
+        XPathResult.FIRST_ORDERED_NODE_TYPE, null);
+    return result.singleNodeValue;
+  }
+  return null;
+};
+
+
+/**
+ * Selects multiple nodes using an Xpath expression and a root node
+ * @param {Node} node The root node.
+ * @param {string} path Xpath selector.
+ * @return {(NodeList,Array<Node>)} The selected nodes, or empty array if no
+ *     matching nodes.
+ */
+goog.dom.xml.selectNodes = function(node, path) {
+  if (typeof node.selectNodes != 'undefined') {
+    var doc = goog.dom.getOwnerDocument(node);
+    if (typeof doc.setProperty != 'undefined') {
+      doc.setProperty('SelectionLanguage', 'XPath');
+    }
+    return node.selectNodes(path);
+  } else if (document.implementation.hasFeature('XPath', '3.0')) {
+    var doc = goog.dom.getOwnerDocument(node);
+    var resolver = doc.createNSResolver(doc.documentElement);
+    var nodes = doc.evaluate(path, node, resolver,
+        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    var results = [];
+    var count = nodes.snapshotLength;
+    for (var i = 0; i < count; i++) {
+      results.push(nodes.snapshotItem(i));
+    }
+    return results;
+  } else {
+    return [];
+  }
+};
+
+
+/**
+ * Sets multiple attributes on an element. Differs from goog.dom.setProperties
+ * in that it exclusively uses the element's setAttributes method. Use this
+ * when you need to ensure that the exact property is available as an attribute
+ * and can be read later by the native getAttribute method.
+ * @param {!Element} element XML or DOM element to set attributes on.
+ * @param {!Object<string, string>} attributes Map of property:value pairs.
+ */
+goog.dom.xml.setAttributes = function(element, attributes) {
+  for (var key in attributes) {
+    if (attributes.hasOwnProperty(key)) {
+      element.setAttribute(key, attributes[key]);
+    }
+  }
+};
+
+
+/**
+ * Creates an instance of the MSXML2.DOMDocument.
+ * @return {Document} The new document.
+ * @private
+ */
+goog.dom.xml.createMsXmlDocument_ = function() {
+  var doc = new ActiveXObject('MSXML2.DOMDocument');
+  if (doc) {
+    // Prevent potential vulnerabilities exposed by MSXML2, see
+    // http://b/1707300 and http://wiki/Main/ISETeamXMLAttacks for details.
+    doc.resolveExternals = false;
+    doc.validateOnParse = false;
+    // Add a try catch block because accessing these properties will throw an
+    // error on unsupported MSXML versions. This affects Windows machines
+    // running IE6 or IE7 that are on XP SP2 or earlier without MSXML updates.
+    // See http://msdn.microsoft.com/en-us/library/ms766391(VS.85).aspx for
+    // specific details on which MSXML versions support these properties.
+    try {
+      doc.setProperty('ProhibitDTD', true);
+      doc.setProperty('MaxXMLSize', goog.dom.xml.MAX_XML_SIZE_KB);
+      doc.setProperty('MaxElementDepth', goog.dom.xml.MAX_ELEMENT_DEPTH);
+    } catch (e) {
+      // No-op.
+    }
+  }
+  return doc;
+};
+
+// FIXME Remove ol.xml.makeParsersNS, and use ol.xml.makeStructureNS instead.
+
+goog.provide('ol.xml');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.xml');
+goog.require('goog.object');
+goog.require('goog.userAgent');
+
+
+/**
+ * When using {@link ol.xml.makeChildAppender} or
+ * {@link ol.xml.makeSimpleNodeFactory}, the top `objectStack` item needs to
+ * have this structure.
+ * @typedef {{node:Node}}
+ */
+ol.xml.NodeStackItem;
+
+
+/**
+ * @typedef {function(Node, Array.<*>)}
+ */
+ol.xml.Parser;
+
+
+/**
+ * @typedef {function(Node, *, Array.<*>)}
+ */
+ol.xml.Serializer;
+
+
+/**
+ * This document should be used when creating nodes for XML serializations. This
+ * document is also used by {@link ol.xml.createElementNS} and
+ * {@link ol.xml.setAttributeNS}
+ * @const
+ * @type {Document}
+ */
+ol.xml.DOCUMENT = goog.dom.xml.createDocument();
+
+
+/**
+ * @param {string} namespaceURI Namespace URI.
+ * @param {string} qualifiedName Qualified name.
+ * @return {Node} Node.
+ * @private
+ */
+ol.xml.createElementNS_ = function(namespaceURI, qualifiedName) {
+  return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName);
+};
+
+
+/**
+ * @param {string} namespaceURI Namespace URI.
+ * @param {string} qualifiedName Qualified name.
+ * @return {Node} Node.
+ * @private
+ */
+ol.xml.createElementNSActiveX_ = function(namespaceURI, qualifiedName) {
+  if (goog.isNull(namespaceURI)) {
+    namespaceURI = '';
+  }
+  return ol.xml.DOCUMENT.createNode(1, qualifiedName, namespaceURI);
+};
+
+
+/**
+ * @param {string} namespaceURI Namespace URI.
+ * @param {string} qualifiedName Qualified name.
+ * @return {Node} Node.
+ */
+ol.xml.createElementNS =
+    (document.implementation && document.implementation.createDocument) ?
+        ol.xml.createElementNS_ : ol.xml.createElementNSActiveX_;
+
+
+/**
+ * Recursively grab all text content of child nodes into a single string.
+ * @param {Node} node Node.
+ * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
+ * breaks.
+ * @return {string} All text content.
+ * @api
+ */
+ol.xml.getAllTextContent = function(node, normalizeWhitespace) {
+  return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join('');
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
+ * breaks.
+ * @param {Array.<String|string>} accumulator Accumulator.
+ * @private
+ * @return {Array.<String|string>} Accumulator.
+ */
+ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) {
+  if (node.nodeType == goog.dom.NodeType.CDATA_SECTION ||
+      node.nodeType == goog.dom.NodeType.TEXT) {
+    if (normalizeWhitespace) {
+      // FIXME understand why goog.dom.getTextContent_ uses String here
+      accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
+    } else {
+      accumulator.push(node.nodeValue);
+    }
+  } else {
+    var n;
+    for (n = node.firstChild; !goog.isNull(n); n = n.nextSibling) {
+      ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator);
+    }
+  }
+  return accumulator;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string} Local name.
+ */
+ol.xml.getLocalName_ = function(node) {
+  return node.localName;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string} Local name.
+ */
+ol.xml.getLocalNameIE_ = function(node) {
+  var localName = node.localName;
+  if (goog.isDef(localName)) {
+    return localName;
+  }
+  var baseName = node.baseName;
+  goog.asserts.assert(goog.isDefAndNotNull(baseName));
+  return baseName;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {string} Local name.
+ */
+ol.xml.getLocalName = goog.userAgent.IE ?
+    ol.xml.getLocalNameIE_ : ol.xml.getLocalName_;
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is document.
+ */
+ol.xml.isDocument_ = function(value) {
+  return value instanceof Document;
+};
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is document.
+ */
+ol.xml.isDocumentIE_ = function(value) {
+  return goog.isObject(value) && value.nodeType == goog.dom.NodeType.DOCUMENT;
+};
+
+
+/**
+ * @param {?} value Value.
+ * @return {boolean} Is document.
+ */
+ol.xml.isDocument = goog.userAgent.IE ?
+    ol.xml.isDocumentIE_ : ol.xml.isDocument_;
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is node.
+ */
+ol.xml.isNode_ = function(value) {
+  return value instanceof Node;
+};
+
+
+/**
+ * @param {?} value Value.
+ * @private
+ * @return {boolean} Is node.
+ */
+ol.xml.isNodeIE_ = function(value) {
+  return goog.isObject(value) && goog.isDef(value.nodeType);
+};
+
+
+/**
+ * @param {?} value Value.
+ * @return {boolean} Is node.
+ */
+ol.xml.isNode = goog.userAgent.IE ? ol.xml.isNodeIE_ : ol.xml.isNode_;
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
+ * @private
+ */
+ol.xml.getAttributeNS_ = function(node, namespaceURI, name) {
+  return node.getAttributeNS(namespaceURI, name) || '';
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
+ * @private
+ */
+ol.xml.getAttributeNSActiveX_ = function(node, namespaceURI, name) {
+  var attributeValue = '';
+  var attributeNode = ol.xml.getAttributeNodeNS(node, namespaceURI, name);
+  if (goog.isDef(attributeNode)) {
+    attributeValue = attributeNode.nodeValue;
+  }
+  return attributeValue;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
+ */
+ol.xml.getAttributeNS =
+    (document.implementation && document.implementation.createDocument) ?
+        ol.xml.getAttributeNS_ : ol.xml.getAttributeNSActiveX_;
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {?Node} Attribute node or null if none found.
+ * @private
+ */
+ol.xml.getAttributeNodeNS_ = function(node, namespaceURI, name) {
+  return node.getAttributeNodeNS(namespaceURI, name);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {?Node} Attribute node or null if none found.
+ * @private
+ */
+ol.xml.getAttributeNodeNSActiveX_ = function(node, namespaceURI, name) {
+  var attributeNode = null;
+  var attributes = node.attributes;
+  var potentialNode, fullName;
+  for (var i = 0, len = attributes.length; i < len; ++i) {
+    potentialNode = attributes[i];
+    if (potentialNode.namespaceURI == namespaceURI) {
+      fullName = (potentialNode.prefix) ?
+          (potentialNode.prefix + ':' + name) : name;
+      if (fullName == potentialNode.nodeName) {
+        attributeNode = potentialNode;
+        break;
+      }
+    }
+  }
+  return attributeNode;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {?Node} Attribute node or null if none found.
+ */
+ol.xml.getAttributeNodeNS =
+    (document.implementation && document.implementation.createDocument) ?
+        ol.xml.getAttributeNodeNS_ : ol.xml.getAttributeNodeNSActiveX_;
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @param {string|number} value Value.
+ * @private
+ */
+ol.xml.setAttributeNS_ = function(node, namespaceURI, name, value) {
+  node.setAttributeNS(namespaceURI, name, value);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @param {string|number} value Value.
+ * @private
+ */
+ol.xml.setAttributeNSActiveX_ = function(node, namespaceURI, name, value) {
+  if (!goog.isNull(namespaceURI)) {
+    var attribute = node.ownerDocument.createNode(2, name, namespaceURI);
+    attribute.nodeValue = value;
+    node.setAttributeNode(attribute);
+  } else {
+    node.setAttribute(name, value);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @param {string|number} value Value.
+ */
+ol.xml.setAttributeNS =
+    (document.implementation && document.implementation.createDocument) ?
+        ol.xml.setAttributeNS_ : ol.xml.setAttributeNSActiveX_;
+
+
+/**
+ * Parse an XML string to a XML Document
+ * @param {string} xml XML.
+ * @return {Document} Document.
+ * @api
+ */
+ol.xml.parse = function(xml) {
+  return new DOMParser().parseFromString(xml, 'application/xml');
+};
+
+
+/**
+ * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)}
+ *     valueReader Value reader.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {ol.xml.Parser} Parser.
+ * @template T
+ */
+ol.xml.makeArrayExtender = function(valueReader, opt_this) {
+  return (
+      /**
+       * @param {Node} node Node.
+       * @param {Array.<*>} objectStack Object stack.
+       */
+      function(node, objectStack) {
+        var value = valueReader.call(opt_this, node, objectStack);
+        if (goog.isDef(value)) {
+          goog.asserts.assert(goog.isArray(value));
+          var array = /** @type {Array.<*>} */
+              (objectStack[objectStack.length - 1]);
+          goog.asserts.assert(goog.isArray(array));
+          goog.array.extend(array, value);
+        }
+      });
+};
+
+
+/**
+ * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {ol.xml.Parser} Parser.
+ * @template T
+ */
+ol.xml.makeArrayPusher = function(valueReader, opt_this) {
+  return (
+      /**
+       * @param {Node} node Node.
+       * @param {Array.<*>} objectStack Object stack.
+       */
+      function(node, objectStack) {
+        var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this,
+            node, objectStack);
+        if (goog.isDef(value)) {
+          var array = objectStack[objectStack.length - 1];
+          goog.asserts.assert(goog.isArray(array));
+          array.push(value);
+        }
+      });
+};
+
+
+/**
+ * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {ol.xml.Parser} Parser.
+ * @template T
+ */
+ol.xml.makeReplacer = function(valueReader, opt_this) {
+  return (
+      /**
+       * @param {Node} node Node.
+       * @param {Array.<*>} objectStack Object stack.
+       */
+      function(node, objectStack) {
+        var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this,
+            node, objectStack);
+        if (goog.isDef(value)) {
+          objectStack[objectStack.length - 1] = value;
+        }
+      });
+};
+
+
+/**
+ * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
+ * @param {string=} opt_property Property.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {ol.xml.Parser} Parser.
+ * @template T
+ */
+ol.xml.makeObjectPropertyPusher =
+    function(valueReader, opt_property, opt_this) {
+  goog.asserts.assert(goog.isDef(valueReader));
+  return (
+      /**
+       * @param {Node} node Node.
+       * @param {Array.<*>} objectStack Object stack.
+       */
+      function(node, objectStack) {
+        var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this,
+            node, objectStack);
+        if (goog.isDef(value)) {
+          var object = /** @type {Object} */
+              (objectStack[objectStack.length - 1]);
+          var property = goog.isDef(opt_property) ?
+              opt_property : node.localName;
+          goog.asserts.assert(goog.isObject(object));
+          var array = goog.object.setIfUndefined(object, property, []);
+          array.push(value);
+        }
+      });
+};
+
+
+/**
+ * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
+ * @param {string=} opt_property Property.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {ol.xml.Parser} Parser.
+ * @template T
+ */
+ol.xml.makeObjectPropertySetter =
+    function(valueReader, opt_property, opt_this) {
+  goog.asserts.assert(goog.isDef(valueReader));
+  return (
+      /**
+       * @param {Node} node Node.
+       * @param {Array.<*>} objectStack Object stack.
+       */
+      function(node, objectStack) {
+        var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this,
+            node, objectStack);
+        if (goog.isDef(value)) {
+          var object = /** @type {Object} */
+              (objectStack[objectStack.length - 1]);
+          var property = goog.isDef(opt_property) ?
+              opt_property : node.localName;
+          goog.asserts.assert(goog.isObject(object));
+          goog.object.set(object, property, value);
+        }
+      });
+};
+
+
+/**
+ * @param {Array.<string>} namespaceURIs Namespace URIs.
+ * @param {Object.<string, ol.xml.Parser>} parsers Parsers.
+ * @param {Object.<string, Object.<string, ol.xml.Parser>>=} opt_parsersNS
+ *     ParsersNS.
+ * @return {Object.<string, Object.<string, ol.xml.Parser>>} Parsers NS.
+ */
+ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) {
+  return /** @type {Object.<string, Object.<string, ol.xml.Parser>>} */ (
+      ol.xml.makeStructureNS(namespaceURIs, parsers, opt_parsersNS));
+};
+
+
+/**
+ * Creates a serializer that appends nodes written by its `nodeWriter` to its
+ * designated parent. The parent is the `node` of the
+ * {@link ol.xml.NodeStackItem} at the top of the `objectStack`.
+ * @param {function(this: T, Node, V, Array.<*>)}
+ *     nodeWriter Node writer.
+ * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
+ * @return {ol.xml.Serializer} Serializer.
+ * @template T, V
+ */
+ol.xml.makeChildAppender = function(nodeWriter, opt_this) {
+  return function(node, value, objectStack) {
+    nodeWriter.call(goog.isDef(opt_this) ? opt_this : this,
+        node, value, objectStack);
+    var parent = objectStack[objectStack.length - 1];
+    goog.asserts.assert(goog.isObject(parent));
+    var parentNode = parent.node;
+    goog.asserts.assert(ol.xml.isNode(parentNode) ||
+        ol.xml.isDocument(parentNode));
+    parentNode.appendChild(node);
+  };
+};
+
+
+/**
+ * Creates a serializer that calls the provided `nodeWriter` from
+ * {@link ol.xml.serialize}. This can be used by the parent writer to have the
+ * 'nodeWriter' called with an array of values when the `nodeWriter` was
+ * designed to serialize a single item. An example would be a LineString
+ * geometry writer, which could be reused for writing MultiLineString
+ * geometries.
+ * @param {function(this: T, Node, V, Array.<*>)}
+ *     nodeWriter Node writer.
+ * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
+ * @return {ol.xml.Serializer} Serializer.
+ * @template T, V
+ */
+ol.xml.makeArraySerializer = function(nodeWriter, opt_this) {
+  var serializersNS, nodeFactory;
+  return function(node, value, objectStack) {
+    if (!goog.isDef(serializersNS)) {
+      serializersNS = {};
+      var serializers = {};
+      goog.object.set(serializers, node.localName, nodeWriter);
+      goog.object.set(serializersNS, node.namespaceURI, serializers);
+      nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName);
+    }
+    ol.xml.serialize(serializersNS, nodeFactory, value, objectStack);
+  };
+};
+
+
+/**
+ * Creates a node factory which can use the `opt_keys` passed to
+ * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names,
+ * or a fixed node name. The namespace of the created nodes can either be fixed,
+ * or the parent namespace will be used.
+ * @param {string=} opt_nodeName Fixed node name which will be used for all
+ *     created nodes. If not provided, the 3rd argument to the resulting node
+ *     factory needs to be provided and will be the nodeName.
+ * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for
+ *     all created nodes. If not provided, the namespace of the parent node will
+ *     be used.
+ * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory.
+ */
+ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) {
+  var fixedNodeName = opt_nodeName;
+  return (
+      /**
+       * @param {*} value Value.
+       * @param {Array.<*>} objectStack Object stack.
+       * @param {string=} opt_nodeName Node name.
+       * @return {Node} Node.
+       */
+      function(value, objectStack, opt_nodeName) {
+        var context = objectStack[objectStack.length - 1];
+        var node = context.node;
+        goog.asserts.assert(ol.xml.isNode(node) || ol.xml.isDocument(node));
+        var nodeName = fixedNodeName;
+        if (!goog.isDef(nodeName)) {
+          nodeName = opt_nodeName;
+        }
+        var namespaceURI = opt_namespaceURI;
+        if (!goog.isDef(opt_namespaceURI)) {
+          namespaceURI = node.namespaceURI;
+        }
+        goog.asserts.assert(goog.isDef(nodeName));
+        return ol.xml.createElementNS(namespaceURI, nodeName);
+      }
+  );
+};
+
+
+/**
+ * A node factory that creates a node using the parent's `namespaceURI` and the
+ * `nodeName` passed by {@link ol.xml.serialize} or
+ * {@link ol.xml.pushSerializeAndPop} to the node factory.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ */
+ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory();
+
+
+/**
+ * Creates 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;
+};
+
+
+/**
+ * Creates 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 = goog.isDef(opt_structureNS) ? opt_structureNS : {};
+  var i, ii;
+  for (i = 0, ii = namespaceURIs.length; i < ii; ++i) {
+    structureNS[namespaceURIs[i]] = structure;
+  }
+  return structureNS;
+};
+
+
+/**
+ * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS
+ *     Parsers by namespace.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {*=} opt_this The object to use as `this`.
+ */
+ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) {
+  var n;
+  for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) {
+    var parsers = parsersNS[n.namespaceURI];
+    if (goog.isDef(parsers)) {
+      var parser = parsers[n.localName];
+      if (goog.isDef(parser)) {
+        parser.call(opt_this, n, objectStack);
+      }
+    }
+  }
+};
+
+
+/**
+ * @param {T} object Object.
+ * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS
+ *     Parsers by namespace.
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {*=} opt_this The object to use as `this`.
+ * @return {T|undefined} 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();
+};
+
+
+/**
+ * Walks through an array of `values` and calls a serializer for each value.
+ * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS
+ *     Namespaced serializers.
+ * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
+ *     Node factory. The `nodeFactory` creates the node whose namespace and name
+ *     will be used to choose a node writer from `serializersNS`. This
+ *     separation allows us to decide what kind of node to create, depending on
+ *     the value we want to serialize. An example for this would be different
+ *     geometry writers based on the geometry type.
+ * @param {Array.<*>} values Values to serialize. An example would be an array
+ *     of {@link ol.Feature} instances.
+ * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
+ *     `nodeFactory`. This is used for serializing object literals where the
+ *     node name relates to the property key. The array length of `opt_keys` has
+ *     to match the length of `values`. For serializing a sequence, `opt_keys`
+ *     determines the order of the sequence.
+ * @param {T=} opt_this The object to use as `this` for the node factory and
+ *     serializers.
+ * @template T
+ */
+ol.xml.serialize = function(
+    serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
+  var length = (goog.isDef(opt_keys) ? opt_keys : values).length;
+  var value, node;
+  for (var i = 0; i < length; ++i) {
+    value = values[i];
+    if (goog.isDef(value)) {
+      node = nodeFactory.call(opt_this, value, objectStack,
+          goog.isDef(opt_keys) ? opt_keys[i] : undefined);
+      if (goog.isDef(node)) {
+        serializersNS[node.namespaceURI][node.localName]
+            .call(opt_this, node, value, objectStack);
+      }
+    }
+  }
+};
+
+
+/**
+ * @param {O} object Object.
+ * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS
+ *     Namespaced serializers.
+ * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
+ *     Node factory. The `nodeFactory` creates the node whose namespace and name
+ *     will be used to choose a node writer from `serializersNS`. This
+ *     separation allows us to decide what kind of node to create, depending on
+ *     the value we want to serialize. An example for this would be different
+ *     geometry writers based on the geometry type.
+ * @param {Array.<*>} values Values to serialize. An example would be an array
+ *     of {@link ol.Feature} instances.
+ * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
+ *     `nodeFactory`. This is used for serializing object literals where the
+ *     node name relates to the property key. The array length of `opt_keys` has
+ *     to match the length of `values`. For serializing a sequence, `opt_keys`
+ *     determines the order of the sequence.
+ * @param {T=} opt_this The object to use as `this` for the node factory and
+ *     serializers.
+ * @return {O|undefined} Object.
+ * @template O, T
+ */
+ol.xml.pushSerializeAndPop = function(object,
+    serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
+  objectStack.push(object);
+  ol.xml.serialize(
+      serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this);
+  return objectStack.pop();
+};
+
+goog.provide('ol.format.XMLFeature');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.xml');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+goog.require('ol.proj');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for XML feature formats.
+ *
+ * @constructor
+ * @extends {ol.format.Feature}
+ */
+ol.format.XMLFeature = function() {
+  goog.base(this);
+};
+goog.inherits(ol.format.XMLFeature, ol.format.Feature);
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.getType = function() {
+  return ol.format.FormatType.XML;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) {
+  if (ol.xml.isDocument(source)) {
+    return this.readFeatureFromDocument(
+        /** @type {Document} */ (source), opt_options);
+  } else if (ol.xml.isNode(source)) {
+    return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options);
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readFeatureFromDocument(doc, opt_options);
+  } else {
+    goog.asserts.fail();
+    return null;
+  }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {ol.Feature} Feature.
+ */
+ol.format.XMLFeature.prototype.readFeatureFromDocument = function(
+    doc, opt_options) {
+  var features = this.readFeaturesFromDocument(doc, opt_options);
+  if (features.length > 0) {
+    return features[0];
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {ol.Feature} Feature.
+ */
+ol.format.XMLFeature.prototype.readFeatureFromNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) {
+  if (ol.xml.isDocument(source)) {
+    return this.readFeaturesFromDocument(
+        /** @type {Document} */ (source), opt_options);
+  } else if (ol.xml.isNode(source)) {
+    return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options);
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readFeaturesFromDocument(doc, opt_options);
+  } else {
+    goog.asserts.fail();
+    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; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      goog.array.extend(features, this.readFeaturesFromNode(n, opt_options));
+    }
+  }
+  return features;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.XMLFeature.prototype.readFeaturesFromNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) {
+  if (ol.xml.isDocument(source)) {
+    return this.readGeometryFromDocument(
+        /** @type {Document} */ (source), opt_options);
+  } else if (ol.xml.isNode(source)) {
+    return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options);
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readGeometryFromDocument(doc, opt_options);
+  } else {
+    goog.asserts.fail();
+    return null;
+  }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.XMLFeature.prototype.readGeometryFromDocument = goog.abstractMethod;
+
+
+/**
+ * @param {Node} node Node.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.XMLFeature.prototype.readGeometryFromNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.readProjection = function(source) {
+  if (ol.xml.isDocument(source)) {
+    return this.readProjectionFromDocument(/** @type {Document} */ (source));
+  } else if (ol.xml.isNode(source)) {
+    return this.readProjectionFromNode(/** @type {Node} */ (source));
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readProjectionFromDocument(doc);
+  } else {
+    goog.asserts.fail();
+    return null;
+  }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.XMLFeature.prototype.readProjectionFromDocument = goog.abstractMethod;
+
+
+/**
+ * @param {Node} node Node.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.XMLFeature.prototype.readProjectionFromNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) {
+  var node = this.writeFeatureNode(feature, opt_options);
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  return goog.dom.xml.serialize(/** @type {Element} */(node));
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @protected
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeFeatureNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) {
+  var node = this.writeFeaturesNode(features, opt_options);
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  return goog.dom.xml.serialize(/** @type {Element} */(node));
+};
+
+
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeFeaturesNode = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) {
+  var node = this.writeGeometryNode(geometry, opt_options);
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  return goog.dom.xml.serialize(/** @type {Element} */(node));
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ */
+ol.format.XMLFeature.prototype.writeGeometryNode = goog.abstractMethod;
+
+// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part
+// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect
+// envelopes/extents, only geometries!
+goog.provide('ol.format.GMLBase');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.LinearRing');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.proj');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Feature base format for reading and writing data in the GML format.
+ * This class cannot be instantiate, 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}
+ * @api
+ */
+ol.format.GMLBase = function(opt_options) {
+  var options = /** @type {olx.format.GMLOptions} */
+      (goog.isDef(opt_options) ? opt_options : {});
+
+  /**
+   * @protected
+   * @type {string}
+   */
+  this.featureType = options.featureType;
+
+  /**
+   * @protected
+   * @type {string}
+   */
+  this.featureNS = options.featureNS;
+
+  /**
+   * @protected
+   * @type {string}
+   */
+  this.srsName = options.srsName;
+
+  /**
+   * @protected
+   * @type {string}
+   */
+  this.schemaLocation = '';
+
+  goog.base(this);
+};
+goog.inherits(ol.format.GMLBase, ol.format.XMLFeature);
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<ol.Feature>} Features.
+ * @private
+ */
+ol.format.GMLBase.prototype.readFeatures_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  var localName = ol.xml.getLocalName(node);
+  var features;
+  if (localName == 'FeatureCollection') {
+    features = ol.xml.pushParseAndPop(null,
+        this.FEATURE_COLLECTION_PARSERS, node,
+        objectStack, this);
+  } else if (localName == 'featureMembers' || localName == 'featureMember') {
+    var context = objectStack[0];
+    goog.asserts.assert(goog.isObject(context));
+    var featureType = goog.object.get(context, 'featureType');
+    if (!goog.isDef(featureType) && !goog.isNull(node.firstElementChild)) {
+      var member = node.firstElementChild;
+      featureType = member.nodeName.split(':').pop();
+      goog.object.set(context, 'featureType', featureType);
+      goog.object.set(context, 'featureNS', member.namespaceURI);
+    }
+    var parsers = {};
+    var parsersNS = {};
+    parsers[featureType] = (localName == 'featureMembers') ?
+        ol.xml.makeArrayPusher(this.readFeatureElement, this) :
+        ol.xml.makeReplacer(this.readFeatureElement, this);
+    parsersNS[goog.object.get(context, 'featureNS')] = parsers;
+    features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack);
+  }
+  if (!goog.isDef(features)) {
+    features = [];
+  }
+  return features;
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, Object>>}
+ */
+ol.format.GMLBase.prototype.FEATURE_COLLECTION_PARSERS = Object({
+  'http://www.opengis.net/gml': {
+    'featureMember': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readFeatures_),
+    'featureMembers': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readFeatures_)
+  }
+});
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Geometry|undefined} Geometry.
+ */
+ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) {
+  var context = objectStack[0];
+  goog.asserts.assert(goog.isObject(context));
+  goog.object.set(context, 'srsName',
+      node.firstElementChild.getAttribute('srsName'));
+  var geometry = ol.xml.pushParseAndPop(/** @type {ol.geom.Geometry} */(null),
+      this.GEOMETRY_PARSERS_, node, objectStack, this);
+  if (goog.isDefAndNotNull(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, 'http://www.opengis.net/gml', 'id');
+  var values = {}, geometryName;
+  for (n = node.firstElementChild; !goog.isNull(n);
+      n = n.nextElementSibling) {
+    var localName = ol.xml.getLocalName(n);
+    // Assume attribute elements have one child node and that the child
+    // is a text node.  Otherwise assume it is a geometry node.
+    if (n.childNodes.length === 0 ||
+        (n.childNodes.length === 1 &&
+        n.firstChild.nodeType === 3)) {
+      var value = ol.xml.getAllTextContent(n, false);
+      if (goog.string.isEmpty(value)) {
+        value = undefined;
+      }
+      values[localName] = value;
+    } else {
+      // boundedBy is an extent and must not be considered as a geometry
+      if (localName !== 'boundedBy') {
+        geometryName = localName;
+      }
+      values[localName] = this.readGeometryElement(n, objectStack);
+    }
+  }
+  var feature = new ol.Feature(values);
+  if (goog.isDef(geometryName)) {
+    feature.setGeometryName(geometryName);
+  }
+  if (fid) {
+    feature.setId(fid);
+  }
+  return feature;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Point|undefined} Point.
+ */
+ol.format.GMLBase.prototype.readPoint = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Point');
+  var flatCoordinates =
+      this.readFlatCoordinatesFromNode_(node, objectStack);
+  if (goog.isDefAndNotNull(flatCoordinates)) {
+    var point = new ol.geom.Point(null);
+    goog.asserts.assert(flatCoordinates.length == 3);
+    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+    return point;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.MultiPoint|undefined} MultiPoint.
+ */
+ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'MultiPoint');
+  var coordinates = ol.xml.pushParseAndPop(
+      /** @type {Array.<Array.<number>>} */ ([]),
+      this.MULTIPOINT_PARSERS_, node, objectStack, this);
+  if (goog.isDef(coordinates)) {
+    return new ol.geom.MultiPoint(coordinates);
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ */
+ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'MultiLineString');
+  var lineStrings = ol.xml.pushParseAndPop(
+      /** @type {Array.<ol.geom.LineString>} */ ([]),
+      this.MULTILINESTRING_PARSERS_, node, objectStack, this);
+  if (goog.isDef(lineStrings)) {
+    var multiLineString = new ol.geom.MultiLineString(null);
+    multiLineString.setLineStrings(lineStrings);
+    return multiLineString;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
+ */
+ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'MultiPolygon');
+  var polygons = ol.xml.pushParseAndPop(
+      /** @type {Array.<ol.geom.Polygon>} */ ([]),
+      this.MULTIPOLYGON_PARSERS_, node, objectStack, this);
+  if (goog.isDef(polygons)) {
+    var multiPolygon = new ol.geom.MultiPolygon(null);
+    multiPolygon.setPolygons(polygons);
+    return multiPolygon;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'pointMember' ||
+      node.localName == 'pointMembers');
+  ol.xml.parseNode(this.POINTMEMBER_PARSERS_,
+      node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GMLBase.prototype.lineStringMemberParser_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'lineStringMember' ||
+      node.localName == 'lineStringMembers');
+  ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_,
+      node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GMLBase.prototype.polygonMemberParser_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'polygonMember' ||
+      node.localName == 'polygonMembers');
+  ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node,
+      objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.LineString|undefined} LineString.
+ */
+ol.format.GMLBase.prototype.readLineString = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LineString');
+  var flatCoordinates =
+      this.readFlatCoordinatesFromNode_(node, objectStack);
+  if (goog.isDefAndNotNull(flatCoordinates)) {
+    var lineString = new ol.geom.LineString(null);
+    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+    return lineString;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} LinearRing flat coordinates.
+ */
+ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LinearRing');
+  var ring = ol.xml.pushParseAndPop(/** @type {Array.<number>} */(null),
+      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
+      objectStack, this);
+  if (goog.isDefAndNotNull(ring)) {
+    return ring;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.LinearRing|undefined} LinearRing.
+ */
+ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LinearRing');
+  var flatCoordinates =
+      this.readFlatCoordinatesFromNode_(node, objectStack);
+  if (goog.isDef(flatCoordinates)) {
+    var ring = new ol.geom.LinearRing(null);
+    ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+    return ring;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.geom.Polygon|undefined} Polygon.
+ */
+ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Polygon');
+  var flatLinearRings = ol.xml.pushParseAndPop(
+      /** @type {Array.<Array.<number>>} */ ([null]),
+      this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
+  if (goog.isDef(flatLinearRings) &&
+      !goog.isNull(flatLinearRings[0])) {
+    var polygon = new ol.geom.Polygon(null);
+    var flatCoordinates = flatLinearRings[0];
+    var ends = [flatCoordinates.length];
+    var i, ii;
+    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
+      goog.array.extend(flatCoordinates, flatLinearRings[i]);
+      ends.push(flatCoordinates.length);
+    }
+    polygon.setFlatCoordinates(
+        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
+    return polygon;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
+ */
+ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(
+      null,
+      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
+      objectStack, this));
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'pointMember': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.pointMemberParser_),
+    'pointMembers': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.pointMemberParser_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'lineStringMember': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.lineStringMemberParser_),
+    'lineStringMembers': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.lineStringMemberParser_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'polygonMember': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.polygonMemberParser_),
+    'polygonMembers': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.polygonMemberParser_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'Point': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'LineString': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readLineString)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'Polygon': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readPolygon)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @protected
+ */
+ol.format.GMLBase.prototype.RING_PARSERS = Object({
+  'http://www.opengis.net/gml' : {
+    'LinearRing': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readFlatLinearRing_)
+  }
+});
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GMLBase.prototype.readGeometryFromNode =
+    function(node, opt_options) {
+  var geometry = this.readGeometryElement(node,
+      [this.getReadOptions(node, goog.isDef(opt_options) ? opt_options : {})]);
+  return goog.isDef(geometry) ? geometry : null;
+};
+
+
+/**
+ * Read all features from a GML FeatureCollection.
+ *
+ * @function
+ * @param {ArrayBuffer|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 (goog.isDef(opt_options)) {
+    goog.object.extend(options, this.getReadOptions(node, opt_options));
+  }
+  return this.readFeatures_(node, [options]);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GMLBase.prototype.readProjectionFromNode = function(node) {
+  return ol.proj.get(goog.isDef(this.srsName_) ? this.srsName_ :
+      node.firstElementChild.getAttribute('srsName'));
+};
+
+goog.provide('ol.format.XSD');
+
+goog.require('goog.asserts');
+goog.require('goog.string');
+goog.require('ol.xml');
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema';
+
+
+/**
+ * @param {Node} node Node.
+ * @return {boolean|undefined} Boolean.
+ */
+ol.format.XSD.readBoolean = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  return ol.format.XSD.readBooleanString(s);
+};
+
+
+/**
+ * @param {string} string String.
+ * @return {boolean|undefined} Boolean.
+ */
+ol.format.XSD.readBooleanString = function(string) {
+  var m = /^\s*(true|1)|(false|0)\s*$/.exec(string);
+  if (m) {
+    return goog.isDef(m[1]) || false;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {number|undefined} DateTime in seconds.
+ */
+ol.format.XSD.readDateTime = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  var re =
+      /^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/;
+  var m = re.exec(s);
+  if (m) {
+    var year = parseInt(m[1], 10);
+    var month = parseInt(m[2], 10) - 1;
+    var day = parseInt(m[3], 10);
+    var hour = parseInt(m[4], 10);
+    var minute = parseInt(m[5], 10);
+    var second = parseInt(m[6], 10);
+    var dateTime = Date.UTC(year, month, day, hour, minute, second) / 1000;
+    if (m[7] != 'Z') {
+      var sign = m[8] == '-' ? -1 : 1;
+      dateTime += sign * 60 * parseInt(m[9], 10);
+      if (goog.isDef(m[10])) {
+        dateTime += sign * 60 * 60 * parseInt(m[10], 10);
+      }
+    }
+    return dateTime;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {number|undefined} Decimal.
+ */
+ol.format.XSD.readDecimal = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  return ol.format.XSD.readDecimalString(s);
+};
+
+
+/**
+ * @param {string} string String.
+ * @return {number|undefined} Decimal.
+ */
+ol.format.XSD.readDecimalString = function(string) {
+  // FIXME check spec
+  var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string);
+  if (m) {
+    return parseFloat(m[1]);
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {number|undefined} Non negative integer.
+ */
+ol.format.XSD.readNonNegativeInteger = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  return ol.format.XSD.readNonNegativeIntegerString(s);
+};
+
+
+/**
+ * @param {string} string String.
+ * @return {number|undefined} Non negative integer.
+ */
+ol.format.XSD.readNonNegativeIntegerString = function(string) {
+  var m = /^\s*(\d+)\s*$/.exec(string);
+  if (m) {
+    return parseInt(m[1], 10);
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {string|undefined} String.
+ */
+ol.format.XSD.readString = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  return goog.string.trim(s);
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the boolean to.
+ * @param {boolean} bool Boolean.
+ */
+ol.format.XSD.writeBooleanTextNode = function(node, bool) {
+  ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0');
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the dateTime to.
+ * @param {number} dateTime DateTime in seconds.
+ */
+ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) {
+  var date = new Date(dateTime * 1000);
+  var string = date.getUTCFullYear() + '-' +
+      goog.string.padNumber(date.getUTCMonth() + 1, 2) + '-' +
+      goog.string.padNumber(date.getUTCDate(), 2) + 'T' +
+      goog.string.padNumber(date.getUTCHours(), 2) + ':' +
+      goog.string.padNumber(date.getUTCMinutes(), 2) + ':' +
+      goog.string.padNumber(date.getUTCSeconds(), 2) + 'Z';
+  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the decimal to.
+ * @param {number} decimal Decimal.
+ */
+ol.format.XSD.writeDecimalTextNode = function(node, decimal) {
+  var string = decimal.toPrecision();
+  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the decimal to.
+ * @param {number} nonNegativeInteger Non negative integer.
+ */
+ol.format.XSD.writeNonNegativeIntegerTextNode =
+    function(node, nonNegativeInteger) {
+  goog.asserts.assert(nonNegativeInteger >= 0);
+  goog.asserts.assert(nonNegativeInteger == (nonNegativeInteger | 0));
+  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.GML');
+goog.provide('ol.format.GML3');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol.Feature');
+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.LineString');
+goog.require('ol.geom.LinearRing');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.proj');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Feature format for reading and writing data in the GML format
+ * version 3.1.1.
+ * Currently only supports GML 3.1.1 Simple Features profile.
+ *
+ * @constructor
+ * @param {olx.format.GMLOptions=} opt_options
+ *     Optional configuration object.
+ * @extends {ol.format.GMLBase}
+ * @api
+ */
+ol.format.GML3 = function(opt_options) {
+  var options = /** @type {olx.format.GMLOptions} */
+      (goog.isDef(opt_options) ? opt_options : {});
+
+  goog.base(this, options);
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.surface_ = goog.isDef(options.surface) ?
+      options.surface : false;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.curve_ = goog.isDef(options.curve) ?
+      options.curve : false;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.multiCurve_ = goog.isDef(options.multiCurve) ?
+      options.multiCurve : true;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.multiSurface_ = goog.isDef(options.multiSurface) ?
+      options.multiSurface : true;
+
+  /**
+   * @inheritDoc
+   */
+  this.schemaLocation = goog.isDef(options.schemaLocation) ?
+      options.schemaLocation : ol.format.GML3.schemaLocation_;
+
+};
+goog.inherits(ol.format.GML3, ol.format.GMLBase);
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.GML3.schemaLocation_ = 'http://www.opengis.net/gml ' +
+    'http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
+    '1.0.0/gmlsf.xsd';
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ */
+ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'MultiCurve');
+  var lineStrings = ol.xml.pushParseAndPop(
+      /** @type {Array.<ol.geom.LineString>} */ ([]),
+      this.MULTICURVE_PARSERS_, node, objectStack, this);
+  if (goog.isDef(lineStrings)) {
+    var multiLineString = new ol.geom.MultiLineString(null);
+    multiLineString.setLineStrings(lineStrings);
+    return multiLineString;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
+ */
+ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'MultiSurface');
+  var polygons = ol.xml.pushParseAndPop(
+      /** @type {Array.<ol.geom.Polygon>} */ ([]),
+      this.MULTISURFACE_PARSERS_, node, objectStack, this);
+  if (goog.isDef(polygons)) {
+    var multiPolygon = new ol.geom.MultiPolygon(null);
+    multiPolygon.setPolygons(polygons);
+    return multiPolygon;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'curveMember' ||
+      node.localName == 'curveMembers');
+  ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'surfaceMember' ||
+      node.localName == 'surfaceMembers');
+  ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_,
+      node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
+ */
+ol.format.GML3.prototype.readPatch_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'patches');
+  return ol.xml.pushParseAndPop(
+      /** @type {Array.<Array.<number>>} */ ([null]),
+      this.PATCHES_PARSERS_, node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} flat coordinates.
+ */
+ol.format.GML3.prototype.readSegment_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'segments');
+  return ol.xml.pushParseAndPop(
+      /** @type {Array.<number>} */ ([null]),
+      this.SEGMENTS_PARSERS_, node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
+ */
+ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'PolygonPatch');
+  return ol.xml.pushParseAndPop(
+      /** @type {Array.<Array.<number>>} */ ([null]),
+      this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} flat coordinates.
+ */
+ol.format.GML3.prototype.readLineStringSegment_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LineStringSegment');
+  return ol.xml.pushParseAndPop(
+      /** @type {Array.<number>} */ ([null]),
+      this.GEOMETRY_FLAT_COORDINATES_PARSERS_,
+      node, objectStack, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'interior');
+  var flatLinearRing = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>|undefined} */ (undefined),
+      this.RING_PARSERS, node, objectStack, this);
+  if (goog.isDef(flatLinearRing)) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    goog.asserts.assert(goog.isArray(flatLinearRings));
+    goog.asserts.assert(flatLinearRings.length > 0);
+    flatLinearRings.push(flatLinearRing);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'exterior');
+  var flatLinearRing = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>|undefined} */ (undefined),
+      this.RING_PARSERS, node, objectStack, this);
+  if (goog.isDef(flatLinearRing)) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    goog.asserts.assert(goog.isArray(flatLinearRings));
+    goog.asserts.assert(flatLinearRings.length > 0);
+    flatLinearRings[0] = flatLinearRing;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
+ */
+ol.format.GML3.prototype.readSurface_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Surface');
+  var flatLinearRings = ol.xml.pushParseAndPop(
+      /** @type {Array.<Array.<number>>} */ ([null]),
+      this.SURFACE_PARSERS_, node, objectStack, this);
+  if (goog.isDef(flatLinearRings) &&
+      !goog.isNull(flatLinearRings[0])) {
+    var polygon = new ol.geom.Polygon(null);
+    var flatCoordinates = flatLinearRings[0];
+    var ends = [flatCoordinates.length];
+    var i, ii;
+    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
+      goog.array.extend(flatCoordinates, flatLinearRings[i]);
+      ends.push(flatCoordinates.length);
+    }
+    polygon.setFlatCoordinates(
+        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
+    return polygon;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
+ */
+ol.format.GML3.prototype.readCurve_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Curve');
+  var flatCoordinates = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>} */ ([null]),
+      this.CURVE_PARSERS_, node, objectStack, this);
+  if (goog.isDef(flatCoordinates)) {
+    var lineString = new ol.geom.LineString(null);
+    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+    return lineString;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Extent|undefined} Envelope.
+ */
+ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Envelope');
+  var flatCoordinates = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>} */ ([null]),
+      this.ENVELOPE_PARSERS_, node, objectStack, this);
+  return ol.extent.createOrUpdate(flatCoordinates[1][0],
+      flatCoordinates[1][1], flatCoordinates[2][0],
+      flatCoordinates[2][1]);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
+ */
+ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) {
+  var s = ol.xml.getAllTextContent(node, false);
+  var re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/;
+  /** @type {Array.<number>} */
+  var flatCoordinates = [];
+  var m;
+  while ((m = re.exec(s))) {
+    flatCoordinates.push(parseFloat(m[1]));
+    s = s.substr(m[0].length);
+  }
+  if (s !== '') {
+    return undefined;
+  }
+  var context = objectStack[0];
+  goog.asserts.assert(goog.isObject(context));
+  var containerSrs = goog.object.get(context, 'srsName');
+  var axisOrientation = 'enu';
+  if (!goog.isNull(containerSrs)) {
+    var proj = ol.proj.get(containerSrs);
+    axisOrientation = proj.getAxisOrientation();
+  }
+  if (axisOrientation === 'neu') {
+    var i, ii;
+    for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) {
+      var y = flatCoordinates[i];
+      var x = flatCoordinates[i + 1];
+      flatCoordinates[i] = x;
+      flatCoordinates[i + 1] = y;
+    }
+  }
+  var len = flatCoordinates.length;
+  if (len == 2) {
+    flatCoordinates.push(0);
+  }
+  if (len === 0) {
+    return undefined;
+  }
+  return flatCoordinates;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
+ */
+ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) {
+  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
+  var context = objectStack[0];
+  goog.asserts.assert(goog.isObject(context));
+  var containerSrs = goog.object.get(context, 'srsName');
+  var containerDimension = node.parentNode.getAttribute('srsDimension');
+  var axisOrientation = 'enu';
+  if (!goog.isNull(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 (!goog.isNull(node.getAttribute('srsDimension'))) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(
+        node.getAttribute('srsDimension'));
+  } else if (!goog.isNull(node.getAttribute('dimension'))) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(
+        node.getAttribute('dimension'));
+  } else if (!goog.isNull(containerDimension)) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
+  }
+  var x, y, z;
+  var flatCoordinates = [];
+  for (var i = 0, ii = coords.length; i < ii; i += dim) {
+    x = parseFloat(coords[i]);
+    y = parseFloat(coords[i + 1]);
+    z = (dim === 3) ? parseFloat(coords[i + 2]) : 0;
+    if (axisOrientation.substr(0, 2) === 'en') {
+      flatCoordinates.push(x, y, z);
+    } else {
+      flatCoordinates.push(y, x, z);
+    }
+  }
+  return flatCoordinates;
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_),
+    'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'interior': ol.format.GML3.prototype.interiorParser_,
+    'exterior': ol.format.GML3.prototype.exteriorParser_
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.GEOMETRY_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
+    'MultiPoint': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readMultiPoint),
+    'LineString': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readLineString),
+    'MultiLineString': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readMultiLineString),
+    'LinearRing' : ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readLinearRing),
+    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
+    'MultiPolygon': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readMultiPolygon),
+    'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_),
+    'MultiSurface': ol.xml.makeReplacer(
+        ol.format.GML3.prototype.readMultiSurface_),
+    'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_),
+    'MultiCurve': ol.xml.makeReplacer(
+        ol.format.GML3.prototype.readMultiCurve_),
+    'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.MULTICURVE_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'curveMember': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.curveMemberParser_),
+    'curveMembers': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.curveMemberParser_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'surfaceMember': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.surfaceMemberParser_),
+    'surfaceMembers': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.surfaceMemberParser_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'LineString': ol.xml.makeArrayPusher(
+        ol.format.GMLBase.prototype.readLineString),
+    'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon),
+    'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.SURFACE_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.CURVE_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.ENVELOPE_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'lowerCorner': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.readFlatPosList_),
+    'upperCorner': ol.xml.makeArrayPusher(
+        ol.format.GML3.prototype.readFlatPosList_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.PATCHES_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'PolygonPatch': ol.xml.makeReplacer(
+        ol.format.GML3.prototype.readPolygonPatch_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML3.prototype.SEGMENTS_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'LineStringSegment': ol.xml.makeReplacer(
+        ol.format.GML3.prototype.readLineStringSegment_)
+  }
+});
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} value Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  var axisOrientation = 'enu';
+  if (goog.isDefAndNotNull(srsName)) {
+    axisOrientation = ol.proj.get(srsName).getAxisOrientation();
+  }
+  var point = value.getCoordinates();
+  var coords;
+  // only 2d for simple features profile
+  if (axisOrientation.substr(0, 2) === 'en') {
+    coords = (point[0] + ' ' + point[1]);
+  } else {
+    coords = (point[1] + ' ' + point[0]);
+  }
+  ol.format.XSD.writeStringTextNode(node, coords);
+};
+
+
+/**
+ * @param {Array.<number>} point Point geometry.
+ * @param {string=} opt_srsName Optional srsName
+ * @return {string}
+ * @private
+ */
+ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) {
+  var axisOrientation = 'enu';
+  if (goog.isDefAndNotNull(opt_srsName)) {
+    axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
+  }
+  return ((axisOrientation.substr(0, 2) === 'en') ?
+      point[0] + ' ' + point[1] :
+      point[1] + ' ' + point[0]);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  // only 2d for simple features profile
+  var points = value.getCoordinates();
+  var len = points.length;
+  var parts = new Array(len);
+  var point;
+  for (var i = 0; i < len; ++i) {
+    point = points[i];
+    parts[i] = this.getCoords_(point, srsName);
+  }
+  ol.format.XSD.writeStringTextNode(node, parts.join(' '));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} geometry Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  if (goog.isDefAndNotNull(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  var pos = ol.xml.createElementNS(node.namespaceURI, 'pos');
+  node.appendChild(pos);
+  this.writePos_(pos, geometry, objectStack);
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GML3.ENVELOPE_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+    'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Extent} extent Extent.
+ * @param {Array.<*>} objectStack Node stack.
+ */
+ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) {
+  goog.asserts.assert(extent.length == 4);
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  if (goog.isDef(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  var keys = ['lowerCorner', 'upperCorner'];
+  var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]];
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
+      ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values,
+      objectStack, keys, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} geometry LinearRing geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeLinearRing_ =
+    function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  if (goog.isDefAndNotNull(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
+  node.appendChild(posList);
+  this.writePosList_(posList, geometry, objectStack);
+};
+
+
+/**
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node} Node.
+ * @private
+ */
+ol.format.GML3.prototype.RING_NODE_FACTORY_ =
+    function(value, objectStack, opt_nodeName) {
+  var context = objectStack[objectStack.length - 1];
+  var parentNode = context.node;
+  goog.asserts.assert(goog.isObject(context));
+  var exteriorWritten = goog.object.get(context, 'exteriorWritten');
+  if (!goog.isDef(exteriorWritten)) {
+    goog.object.set(context, 'exteriorWritten', true);
+  }
+  return ol.xml.createElementNS(parentNode.namespaceURI,
+      goog.isDef(exteriorWritten) ? 'interior' : 'exterior');
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} geometry Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeSurfaceOrPolygon_ =
+    function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  if (node.nodeName !== 'PolygonPatch' && goog.isDefAndNotNull(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') {
+    var rings = geometry.getLinearRings();
+    ol.xml.pushSerializeAndPop(
+        {node: node, srsName: srsName},
+        ol.format.GML3.RING_SERIALIZERS_,
+        this.RING_NODE_FACTORY_,
+        rings, objectStack, undefined, this);
+  } else if (node.nodeName === 'Surface') {
+    var patches = ol.xml.createElementNS(node.namespaceURI, 'patches');
+    node.appendChild(patches);
+    this.writeSurfacePatches_(
+        patches, geometry, objectStack);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} geometry LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeCurveOrLineString_ =
+    function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  if (node.nodeName !== 'LineStringSegment' &&
+      goog.isDefAndNotNull(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  if (node.nodeName === 'LineString' ||
+      node.nodeName === 'LineStringSegment') {
+    var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
+    node.appendChild(posList);
+    this.writePosList_(posList, geometry, objectStack);
+  } else if (node.nodeName === 'Curve') {
+    var segments = ol.xml.createElementNS(node.namespaceURI, 'segments');
+    node.appendChild(segments);
+    this.writeCurveSegments_(segments,
+        geometry, objectStack);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ =
+    function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  var surface = goog.object.get(context, 'surface');
+  if (goog.isDefAndNotNull(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  var polygons = geometry.getPolygons();
+  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface},
+      ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_,
+      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons,
+      objectStack, undefined, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiPoint} geometry MultiPoint geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry,
+    objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  if (goog.isDefAndNotNull(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  var points = geometry.getPoints();
+  ol.xml.pushSerializeAndPop({node: node, srsName: srsName},
+      ol.format.GML3.POINTMEMBER_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory('pointMember'), points,
+      objectStack, undefined, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.MultiLineString} geometry MultiLineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeMultiCurveOrLineString_ =
+    function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var srsName = goog.object.get(context, 'srsName');
+  var curve = goog.object.get(context, 'curve');
+  if (goog.isDefAndNotNull(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  var lines = geometry.getLineStrings();
+  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve},
+      ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_,
+      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines,
+      objectStack, undefined, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} ring LinearRing geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) {
+  var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing');
+  node.appendChild(linearRing);
+  this.writeLinearRing_(linearRing, ring, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ =
+    function(node, polygon, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var child = this.GEOMETRY_NODE_FACTORY_(
+      polygon, objectStack);
+  if (goog.isDef(child)) {
+    node.appendChild(child);
+    this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Point} point Point geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writePointMember_ =
+    function(node, point, objectStack) {
+  var child = ol.xml.createElementNS(node.namespaceURI, 'Point');
+  node.appendChild(child);
+  this.writePoint_(child, point, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} line LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeLineStringOrCurveMember_ =
+    function(node, line, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack);
+  if (goog.isDef(child)) {
+    node.appendChild(child);
+    this.writeCurveOrLineString_(child, line, objectStack);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeSurfacePatches_ =
+    function(node, polygon, objectStack) {
+  var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch');
+  node.appendChild(child);
+  this.writeSurfaceOrPolygon_(child, polygon, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} line LineString geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeCurveSegments_ =
+    function(node, line, objectStack) {
+  var child = ol.xml.createElementNS(node.namespaceURI,
+      'LineStringSegment');
+  node.appendChild(child);
+  this.writeCurveOrLineString_(child, line, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
+ * @param {Array.<*>} objectStack Node stack.
+ */
+ol.format.GML3.prototype.writeGeometryElement =
+    function(node, geometry, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var item = goog.object.clone(context);
+  item.node = node;
+  var value;
+  if (goog.isArray(geometry)) {
+    if (goog.isDef(context.dataProjection)) {
+      value = ol.proj.transformExtent(
+          geometry, context.featureProjection, context.dataProjection);
+    } else {
+      value = geometry;
+    }
+  } else {
+    goog.asserts.assertInstanceof(geometry, ol.geom.Geometry);
+    value =
+        ol.format.Feature.transformWithOptions(geometry, true, context);
+  }
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
+      (item), ol.format.GML3.GEOMETRY_SERIALIZERS_,
+      this.GEOMETRY_NODE_FACTORY_, [value],
+      objectStack, undefined, this);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ */
+ol.format.GML3.prototype.writeFeatureElement =
+    function(node, feature, objectStack) {
+  var fid = feature.getId();
+  if (goog.isDef(fid)) {
+    node.setAttribute('fid', fid);
+  }
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var featureNS = goog.object.get(context, 'featureNS');
+  var geometryName = feature.getGeometryName();
+  if (!goog.isDef(context.serializers)) {
+    context.serializers = {};
+    context.serializers[featureNS] = {};
+  }
+  var properties = feature.getProperties();
+  var keys = [], values = [];
+  for (var key in properties) {
+    var value = properties[key];
+    if (!goog.isNull(value)) {
+      keys.push(key);
+      values.push(value);
+      if (key == geometryName) {
+        if (!(key in context.serializers[featureNS])) {
+          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
+              this.writeGeometryElement, this);
+        }
+      } else {
+        if (!(key in context.serializers[featureNS])) {
+          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
+              ol.format.XSD.writeStringTextNode);
+        }
+      }
+    }
+  }
+  var item = goog.object.clone(context);
+  item.node = node;
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
+      (item), context.serializers,
+      ol.xml.makeSimpleNodeFactory(undefined, featureNS),
+      values,
+      objectStack, keys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GML3.prototype.writeFeatureMembers_ =
+    function(node, features, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var featureType = goog.object.get(context, 'featureType');
+  var featureNS = goog.object.get(context, 'featureNS');
+  var serializers = {};
+  serializers[featureNS] = {};
+  serializers[featureNS][featureType] = ol.xml.makeChildAppender(
+      this.writeFeatureElement, this);
+  var item = goog.object.clone(context);
+  item.node = node;
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
+      (item),
+      serializers,
+      ol.xml.makeSimpleNodeFactory(featureType, featureNS), features,
+      objectStack);
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'surfaceMember': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeSurfaceOrPolygonMember_),
+    'polygonMember': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeSurfaceOrPolygonMember_)
+  }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GML3.POINTMEMBER_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'pointMember': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writePointMember_)
+  }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'lineStringMember': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeLineStringOrCurveMember_),
+    'curveMember': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeLineStringOrCurveMember_)
+  }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GML3.RING_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'exterior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_),
+    'interior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_)
+  }
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GML3.GEOMETRY_SERIALIZERS_ = {
+  'http://www.opengis.net/gml': {
+    'Curve': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeCurveOrLineString_),
+    'MultiCurve': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeMultiCurveOrLineString_),
+    'Point': ol.xml.makeChildAppender(ol.format.GML3.prototype.writePoint_),
+    'MultiPoint': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeMultiPoint_),
+    'LineString': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeCurveOrLineString_),
+    'MultiLineString': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeMultiCurveOrLineString_),
+    'LinearRing': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeLinearRing_),
+    'Polygon': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeSurfaceOrPolygon_),
+    'MultiPolygon': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_),
+    'Surface': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeSurfaceOrPolygon_),
+    'MultiSurface': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_),
+    'Envelope': ol.xml.makeChildAppender(
+        ol.format.GML3.prototype.writeEnvelope)
+  }
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, string>}
+ * @private
+ */
+ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = {
+  'MultiLineString': 'lineStringMember',
+  'MultiCurve': 'curveMember',
+  'MultiPolygon': 'polygonMember',
+  'MultiSurface': 'surfaceMember'
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ =
+    function(value, objectStack, opt_nodeName) {
+  var parentNode = objectStack[objectStack.length - 1].node;
+  goog.asserts.assert(ol.xml.isNode(parentNode));
+  return ol.xml.createElementNS('http://www.opengis.net/gml',
+      ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]);
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ =
+    function(value, objectStack, opt_nodeName) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var multiSurface = goog.object.get(context, 'multiSurface');
+  var surface = goog.object.get(context, 'surface');
+  var curve = goog.object.get(context, 'curve');
+  var multiCurve = goog.object.get(context, 'multiCurve');
+  var parentNode = objectStack[objectStack.length - 1].node;
+  goog.asserts.assert(ol.xml.isNode(parentNode));
+  var nodeName;
+  if (!goog.isArray(value)) {
+    goog.asserts.assertInstanceof(value, ol.geom.Geometry);
+    nodeName = value.getType();
+    if (nodeName === 'MultiPolygon' && multiSurface === true) {
+      nodeName = 'MultiSurface';
+    } else if (nodeName === 'Polygon' && surface === true) {
+      nodeName = 'Surface';
+    } else if (nodeName === 'LineString' && curve === true) {
+      nodeName = 'Curve';
+    } else if (nodeName === 'MultiLineString' && multiCurve === true) {
+      nodeName = 'MultiCurve';
+    }
+  } else {
+    nodeName = 'Envelope';
+  }
+  return ol.xml.createElementNS('http://www.opengis.net/gml',
+      nodeName);
+};
+
+
+/**
+ * Encode a geometry in GML 3.1.1 Simple Features.
+ *
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @api
+ */
+ol.format.GML3.prototype.writeGeometryNode = function(geometry, opt_options) {
+  opt_options = this.adaptOptions(opt_options);
+  var geom = ol.xml.createElementNS('http://www.opengis.net/gml', 'geom');
+  var context = {node: geom, srsName: this.srsName,
+    curve: this.curve_, surface: this.surface_,
+    multiSurface: this.multiSurface_, multiCurve: this.multiCurve_};
+  if (goog.isDef(opt_options)) {
+    goog.object.extend(context, opt_options);
+  }
+  this.writeGeometryElement(geom, geometry, [context]);
+  return geom;
+};
+
+
+/**
+ * Encode an array of features in GML 3.1.1 Simple Features.
+ *
+ * @function
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {string} Result.
+ * @api stable
+ */
+ol.format.GML3.prototype.writeFeatures;
+
+
+/**
+ * Encode an array of features in the GML 3.1.1 format as an XML node.
+ *
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @api
+ */
+ol.format.GML3.prototype.writeFeaturesNode = function(features, opt_options) {
+  opt_options = this.adaptOptions(opt_options);
+  var node = ol.xml.createElementNS('http://www.opengis.net/gml',
+      'featureMembers');
+  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
+      'xsi:schemaLocation', this.schemaLocation);
+  var context = {
+    srsName: this.srsName,
+    curve: this.curve_,
+    surface: this.surface_,
+    multiSurface: this.multiSurface_,
+    multiCurve: this.multiCurve_,
+    featureNS: this.featureNS,
+    featureType: this.featureType
+  };
+  if (goog.isDef(opt_options)) {
+    goog.object.extend(context, opt_options);
+  }
+  this.writeFeatureMembers_(node, features, [context]);
+  return node;
+};
+
+
+
+/**
+ * @classdesc
+ * Feature format for reading and writing data in the GML format
+ * version 3.1.1.
+ * Currently only supports GML 3.1.1 Simple Features profile.
+ *
+ * @constructor
+ * @param {olx.format.GMLOptions=} opt_options
+ *     Optional configuration object.
+ * @extends {ol.format.GMLBase}
+ * @api stable
+ */
+ol.format.GML = ol.format.GML3;
+
+
+/**
+ * Encode an array of features in GML 3.1.1 Simple Features.
+ *
+ * @function
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {string} Result.
+ * @api stable
+ */
+ol.format.GML.prototype.writeFeatures;
+
+
+/**
+ * Encode an array of features in the GML 3.1.1 format as an XML node.
+ *
+ * @function
+ * @param {ol.Feature} feature Feature.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @api
+ */
+ol.format.GML.prototype.writeFeaturesNode;
+
+goog.provide('ol.format.GML2');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol.extent');
+goog.require('ol.format.GML');
+goog.require('ol.format.GMLBase');
+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} */
+      (goog.isDef(opt_options) ? opt_options : {});
+
+  goog.base(this, options);
+
+  /**
+   * @inheritDoc
+   */
+  this.schemaLocation = goog.isDef(options.schemaLocation) ?
+      options.schemaLocation : ol.format.GML2.schemaLocation_;
+
+};
+goog.inherits(ol.format.GML2, ol.format.GMLBase);
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.GML2.schemaLocation_ = 'http://www.opengis.net/gml ' +
+    'http://schemas.opengis.net/gml/2.1.2/feature.xsd';
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
+ */
+ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) {
+  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
+  var context = objectStack[0];
+  goog.asserts.assert(goog.isObject(context));
+  var containerSrs = goog.object.get(context, 'srsName');
+  var containerDimension = node.parentNode.getAttribute('srsDimension');
+  var axisOrientation = 'enu';
+  if (!goog.isNull(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 (!goog.isNull(node.getAttribute('srsDimension'))) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(
+        node.getAttribute('srsDimension'));
+  } else if (!goog.isNull(node.getAttribute('dimension'))) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(
+        node.getAttribute('dimension'));
+  } else if (!goog.isNull(containerDimension)) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
+  }
+  var x, y, z;
+  var flatCoordinates = [];
+  for (var i = 0, ii = coords.length; i < ii; i += dim) {
+    x = parseFloat(coords[i]);
+    y = parseFloat(coords[i + 1]);
+    z = (dim === 3) ? parseFloat(coords[i + 2]) : 0;
+    if (axisOrientation.substr(0, 2) === 'en') {
+      flatCoordinates.push(x, y, z);
+    } else {
+      flatCoordinates.push(y, x, z);
+    }
+  }
+  return flatCoordinates;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Extent|undefined} Envelope.
+ */
+ol.format.GML2.prototype.readBox_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Box');
+  var flatCoordinates = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>} */ ([null]),
+      this.BOX_PARSERS_, node, objectStack, this);
+  return ol.extent.createOrUpdate(flatCoordinates[1][0],
+      flatCoordinates[1][1], flatCoordinates[1][3],
+      flatCoordinates[1][4]);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GML2.prototype.innerBoundaryIsParser_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'innerBoundaryIs');
+  var flatLinearRing = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>|undefined} */ (undefined),
+      this.RING_PARSERS, node, objectStack, this);
+  if (goog.isDef(flatLinearRing)) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    goog.asserts.assert(goog.isArray(flatLinearRings));
+    goog.asserts.assert(flatLinearRings.length > 0);
+    flatLinearRings.push(flatLinearRing);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GML2.prototype.outerBoundaryIsParser_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'outerBoundaryIs');
+  var flatLinearRing = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>|undefined} */ (undefined),
+      this.RING_PARSERS, node, objectStack, this);
+  if (goog.isDef(flatLinearRing)) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    goog.asserts.assert(goog.isArray(flatLinearRings));
+    goog.asserts.assert(flatLinearRings.length > 0);
+    flatLinearRings[0] = flatLinearRing;
+  }
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'coordinates': ol.xml.makeReplacer(
+        ol.format.GML2.prototype.readFlatCoordinates_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_,
+    'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML2.prototype.BOX_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'coordinates': ol.xml.makeArrayPusher(
+        ol.format.GML2.prototype.readFlatCoordinates_)
+  }
+});
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GML2.prototype.GEOMETRY_PARSERS_ = Object({
+  'http://www.opengis.net/gml' : {
+    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
+    'MultiPoint': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readMultiPoint),
+    'LineString': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readLineString),
+    'MultiLineString': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readMultiLineString),
+    'LinearRing' : ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readLinearRing),
+    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
+    'MultiPolygon': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readMultiPolygon),
+    'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_)
+  }
+});
+
+goog.provide('ol.format.GPX');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.format.XSD');
+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 = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this);
+
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+
+  /**
+   * @type {function(ol.Feature, Node)|undefined}
+   * @private
+   */
+  this.readExtensions_ = options.readExtensions;
+};
+goog.inherits(ol.format.GPX, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.GPX.NAMESPACE_URIS_ = [
+  null,
+  'http://www.topografix.com/GPX/1/0',
+  'http://www.topografix.com/GPX/1/1'
+];
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Node} node Node.
+ * @param {Object} values Values.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
+ */
+ol.format.GPX.appendCoordinate_ = function(flatCoordinates, node, values) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  flatCoordinates.push(
+      parseFloat(node.getAttribute('lon')),
+      parseFloat(node.getAttribute('lat')));
+  if (goog.object.containsKey(values, 'ele')) {
+    flatCoordinates.push(
+        /** @type {number} */ (goog.object.get(values, 'ele')));
+    goog.object.remove(values, 'ele');
+  } else {
+    flatCoordinates.push(0);
+  }
+  if (goog.object.containsKey(values, 'time')) {
+    flatCoordinates.push(
+        /** @type {number} */ (goog.object.get(values, 'time')));
+    goog.object.remove(values, 'time');
+  } else {
+    flatCoordinates.push(0);
+  }
+  return flatCoordinates;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.parseLink_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'link');
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var href = node.getAttribute('href');
+  if (!goog.isNull(href)) {
+    goog.object.set(values, 'link', href);
+  }
+  ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.parseExtensions_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'extensions');
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  goog.object.set(values, 'extensionsNode_', node);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.parseRtePt_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'rtept');
+  var values = ol.xml.pushParseAndPop(
+      {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack);
+  if (goog.isDef(values)) {
+    var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+    var flatCoordinates = /** @type {Array.<number>} */
+        (goog.object.get(rteValues, 'flatCoordinates'));
+    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.parseTrkPt_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'trkpt');
+  var values = ol.xml.pushParseAndPop(
+      {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack);
+  if (goog.isDef(values)) {
+    var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+    var flatCoordinates = /** @type {Array.<number>} */
+        (goog.object.get(trkValues, 'flatCoordinates'));
+    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.parseTrkSeg_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'trkseg');
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack);
+  var flatCoordinates = /** @type {Array.<number>} */
+      (goog.object.get(values, 'flatCoordinates'));
+  var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends'));
+  ends.push(flatCoordinates.length);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+ol.format.GPX.readRte_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'rte');
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var values = ol.xml.pushParseAndPop({
+    'flatCoordinates': []
+  }, ol.format.GPX.RTE_PARSERS_, node, objectStack);
+  if (!goog.isDef(values)) {
+    return undefined;
+  }
+  var flatCoordinates = /** @type {Array.<number>} */
+      (goog.object.get(values, 'flatCoordinates'));
+  goog.object.remove(values, 'flatCoordinates');
+  var geometry = new ol.geom.LineString(null);
+  geometry.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setProperties(values);
+  return feature;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+ol.format.GPX.readTrk_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'trk');
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var values = ol.xml.pushParseAndPop({
+    'flatCoordinates': [],
+    'ends': []
+  }, ol.format.GPX.TRK_PARSERS_, node, objectStack);
+  if (!goog.isDef(values)) {
+    return undefined;
+  }
+  var flatCoordinates = /** @type {Array.<number>} */
+      (goog.object.get(values, 'flatCoordinates'));
+  goog.object.remove(values, 'flatCoordinates');
+  var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends'));
+  goog.object.remove(values, 'ends');
+  var geometry = new ol.geom.MultiLineString(null);
+  geometry.setFlatCoordinates(
+      ol.geom.GeometryLayout.XYZM, flatCoordinates, ends);
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setProperties(values);
+  return feature;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Waypoint.
+ */
+ol.format.GPX.readWpt_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'wpt');
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var values = ol.xml.pushParseAndPop(
+      {}, ol.format.GPX.WPT_PARSERS_, node, objectStack);
+  if (!goog.isDef(values)) {
+    return undefined;
+  }
+  var coordinates = ol.format.GPX.appendCoordinate_([], node, values);
+  var geometry = new ol.geom.Point(
+      coordinates, ol.geom.GeometryLayout.XYZM);
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setProperties(values);
+  return feature;
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>}
+ * @private
+ */
+ol.format.GPX.FEATURE_READER_ = {
+  'rte': ol.format.GPX.readRte_,
+  'trk': ol.format.GPX.readTrk_,
+  'wpt': ol.format.GPX.readWpt_
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.GPX_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_),
+      'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_),
+      'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.LINK_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'text':
+          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'),
+      'type':
+          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.RTE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'link': ol.format.GPX.parseLink_,
+      'number':
+          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
+      'extensions': ol.format.GPX.parseExtensions_,
+      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'rtept': ol.format.GPX.parseRtePt_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.TRK_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'link': ol.format.GPX.parseLink_,
+      'number':
+          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
+      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'extensions': ol.format.GPX.parseExtensions_,
+      'trkseg': ol.format.GPX.parseTrkSeg_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'trkpt': ol.format.GPX.parseTrkPt_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.GPX.WPT_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime),
+      'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'link': ol.format.GPX.parseLink_,
+      'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'sat': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'ageofdgpsdata':
+          ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'dgpsid':
+          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
+      'extensions': ol.format.GPX.parseExtensions_
+    });
+
+
+/**
+ * @param {Array.<ol.Feature>} features
+ * @private
+ */
+ol.format.GPX.prototype.handleReadExtensions_ = function(features) {
+  if (goog.isNull(features)) {
+    features = [];
+  }
+  for (var i = 0, ii = features.length; i < ii; ++i) {
+    var feature = features[i];
+    if (goog.isDef(this.readExtensions_)) {
+      var extensionsNode = feature.get('extensionsNode_') || null;
+      this.readExtensions_(feature, extensionsNode);
+    }
+    feature.set('extensionsNode_', undefined);
+  }
+};
+
+
+/**
+ * Read the first feature from a GPX source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api stable
+ */
+ol.format.GPX.prototype.readFeature;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
+    return null;
+  }
+  var featureReader = ol.format.GPX.FEATURE_READER_[node.localName];
+  if (!goog.isDef(featureReader)) {
+    return null;
+  }
+  var feature = featureReader(node, [this.getReadOptions(node, opt_options)]);
+  if (!goog.isDef(feature)) {
+    return null;
+  }
+  this.handleReadExtensions_([feature]);
+  return feature;
+};
+
+
+/**
+ * Read all features from a GPX source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.format.GPX.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
+    return [];
+  }
+  if (node.localName == 'gpx') {
+    var features = ol.xml.pushParseAndPop(
+        /** @type {Array.<ol.Feature>} */ ([]), ol.format.GPX.GPX_PARSERS_,
+        node, [this.getReadOptions(node, opt_options)]);
+    if (goog.isDef(features)) {
+      this.handleReadExtensions_(features);
+      return features;
+    } else {
+      return [];
+    }
+  }
+  return [];
+};
+
+
+/**
+ * Read the projection from a GPX source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.GPX.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GPX.prototype.readProjectionFromDocument = function(doc) {
+  return this.defaultDataProjection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.GPX.prototype.readProjectionFromNode = function(node) {
+  return this.defaultDataProjection;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {string} value Value for the link's `href` attribute.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.GPX.writeLink_ = function(node, value, objectStack) {
+  node.setAttribute('href', value);
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var properties = goog.object.get(context, 'properties');
+  var link = [
+    goog.object.get(properties, 'linkText'),
+    goog.object.get(properties, 'linkType')
+  ];
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}),
+      ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      link, objectStack, ol.format.GPX.LINK_SEQUENCE_);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var parentNode = context.node;
+  goog.asserts.assert(ol.xml.isNode(parentNode));
+  var namespaceURI = parentNode.namespaceURI;
+  var properties = goog.object.get(context, 'properties');
+  //FIXME Projection handling
+  ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]);
+  ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]);
+  var geometryLayout = goog.object.get(context, 'geometryLayout');
+  /* jshint -W086 */
+  switch (geometryLayout) {
+    case ol.geom.GeometryLayout.XYZM:
+      if (coordinate[3] !== 0) {
+        goog.object.set(properties, 'time', coordinate[3]);
+      }
+    case ol.geom.GeometryLayout.XYZ:
+      if (coordinate[2] !== 0) {
+        goog.object.set(properties, 'ele', coordinate[2]);
+      }
+      break;
+    case ol.geom.GeometryLayout.XYM:
+      if (coordinate[2] !== 0) {
+        goog.object.set(properties, 'time', coordinate[2]);
+      }
+  }
+  /* jshint +W086 */
+  var orderedKeys = ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
+      ({node: node, 'properties': properties}),
+      ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.writeRte_ = function(node, feature, objectStack) {
+  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
+  var properties = feature.getProperties();
+  var context = {node: node, 'properties': properties};
+  var geometry = feature.getGeometry();
+  if (goog.isDef(geometry)) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+    geometry = /** @type {ol.geom.LineString} */
+        (ol.format.Feature.transformWithOptions(geometry, true, options));
+    goog.object.set(context, 'geometryLayout', geometry.getLayout());
+    goog.object.set(properties, 'rtept', geometry.getCoordinates());
+  }
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context),
+      ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.writeTrk_ = function(node, feature, objectStack) {
+  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
+  var properties = feature.getProperties();
+  var context = {node: node, 'properties': properties};
+  var geometry = feature.getGeometry();
+  if (goog.isDef(geometry)) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
+    geometry = /** @type {ol.geom.MultiLineString} */
+        (ol.format.Feature.transformWithOptions(geometry, true, options));
+    goog.object.set(properties, 'trkseg', geometry.getLineStrings());
+  }
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context),
+      ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LineString} lineString LineString.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) {
+  var context = {node: node, 'geometryLayout': lineString.getLayout(),
+    'properties': {}};
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context),
+      ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_,
+      lineString.getCoordinates(), objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.GPX.writeWpt_ = function(node, feature, objectStack) {
+  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  goog.object.set(context, 'properties', feature.getProperties());
+  var geometry = feature.getGeometry();
+  if (goog.isDef(geometry)) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
+    geometry = /** @type {ol.geom.Point} */
+        (ol.format.Feature.transformWithOptions(geometry, true, options));
+    goog.object.set(context, 'geometryLayout', geometry.getLayout());
+    ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack);
+  }
+};
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type'];
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, [
+      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
+      'number': ol.xml.makeChildAppender(
+          ol.format.XSD.writeNonNegativeIntegerTextNode),
+      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
+          ol.format.GPX.writeWptType_))
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, [
+      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
+      'number': ol.xml.makeChildAppender(
+          ol.format.XSD.writeNonNegativeIntegerTextNode),
+      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
+          ol.format.GPX.writeTrkSeg_))
+    });
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, [
+      'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src',
+      'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop',
+      'ageofdgpsdata', 'dgpsid'
+    ]);
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+      'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode),
+      'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+      'geoidheight': ol.xml.makeChildAppender(
+          ol.format.XSD.writeDecimalTextNode),
+      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
+      'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'sat': ol.xml.makeChildAppender(
+          ol.format.XSD.writeNonNegativeIntegerTextNode),
+      'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+      'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+      'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+      'ageofdgpsdata': ol.xml.makeChildAppender(
+          ol.format.XSD.writeDecimalTextNode),
+      'dgpsid': ol.xml.makeChildAppender(
+          ol.format.XSD.writeNonNegativeIntegerTextNode)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, string>}
+ * @private
+ */
+ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = {
+  'Point': 'wpt',
+  'LineString': 'rte',
+  'MultiLineString': 'trk'
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  goog.asserts.assertInstanceof(value, ol.Feature);
+  var geometry = value.getGeometry();
+  if (goog.isDef(geometry)) {
+    var parentNode = objectStack[objectStack.length - 1].node;
+    goog.asserts.assert(ol.xml.isNode(parentNode));
+    return ol.xml.createElementNS(parentNode.namespaceURI,
+        ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]);
+  }
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.GPX.NAMESPACE_URIS_, {
+      'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_),
+      'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_),
+      'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_)
+    });
+
+
+/**
+ * Encode an array of features in the GPX format.
+ *
+ * @function
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} Result.
+ * @api stable
+ */
+ol.format.GPX.prototype.writeFeatures;
+
+
+/**
+ * Encode an array of features in the GPX format as an XML node.
+ *
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @api
+ */
+ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) {
+  opt_options = this.adaptOptions(opt_options);
+  //FIXME Serialize metadata
+  var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx');
+
+  ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */
+      ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_,
+      ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]);
+  return gpx;
+};
+
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for string newlines.
+ * @author nnaze@google.com (Nathan Naze)
+ */
+
+
+/**
+ * Namespace for string utilities
+ */
+goog.provide('goog.string.newlines');
+goog.provide('goog.string.newlines.Line');
+
+goog.require('goog.array');
+
+
+/**
+ * Splits a string into lines, properly handling universal newlines.
+ * @param {string} str String to split.
+ * @param {boolean=} opt_keepNewlines Whether to keep the newlines in the
+ *     resulting strings. Defaults to false.
+ * @return {!Array<string>} String split into lines.
+ */
+goog.string.newlines.splitLines = function(str, opt_keepNewlines) {
+  var lines = goog.string.newlines.getLines(str);
+  return goog.array.map(lines, function(line) {
+    return opt_keepNewlines ? line.getFullLine() : line.getContent();
+  });
+};
+
+
+
+/**
+ * Line metadata class that records the start/end indicies of lines
+ * in a string.  Can be used to implement common newline use cases such as
+ * splitLines() or determining line/column of an index in a string.
+ * Also implements methods to get line contents.
+ *
+ * Indexes are expressed as string indicies into string.substring(), inclusive
+ * at the start, exclusive at the end.
+ *
+ * Create an array of these with goog.string.newlines.getLines().
+ * @param {string} string The original string.
+ * @param {number} startLineIndex The index of the start of the line.
+ * @param {number} endContentIndex The index of the end of the line, excluding
+ *     newlines.
+ * @param {number} endLineIndex The index of the end of the line, index
+ *     newlines.
+ * @constructor
+ * @struct
+ * @final
+ */
+goog.string.newlines.Line = function(string, startLineIndex,
+                                     endContentIndex, endLineIndex) {
+  /**
+   * The original string.
+   * @type {string}
+   */
+  this.string = string;
+
+  /**
+   * Index of the start of the line.
+   * @type {number}
+   */
+  this.startLineIndex = startLineIndex;
+
+  /**
+   * Index of the end of the line, excluding any newline characters.
+   * Index is the first character after the line, suitable for
+   * String.substring().
+   * @type {number}
+   */
+  this.endContentIndex = endContentIndex;
+
+  /**
+   * Index of the end of the line, excluding any newline characters.
+   * Index is the first character after the line, suitable for
+   * String.substring().
+   * @type {number}
+   */
+
+  this.endLineIndex = endLineIndex;
+};
+
+
+/**
+ * @return {string} The content of the line, excluding any newline characters.
+ */
+goog.string.newlines.Line.prototype.getContent = function() {
+  return this.string.substring(this.startLineIndex, this.endContentIndex);
+};
+
+
+/**
+ * @return {string} The full line, including any newline characters.
+ */
+goog.string.newlines.Line.prototype.getFullLine = function() {
+  return this.string.substring(this.startLineIndex, this.endLineIndex);
+};
+
+
+/**
+ * @return {string} The newline characters, if any ('\n', \r', '\r\n', '', etc).
+ */
+goog.string.newlines.Line.prototype.getNewline = function() {
+  return this.string.substring(this.endContentIndex, this.endLineIndex);
+};
+
+
+/**
+ * Splits a string into an array of line metadata.
+ * @param {string} str String to split.
+ * @return {!Array<!goog.string.newlines.Line>} Array of line metadata.
+ */
+goog.string.newlines.getLines = function(str) {
+  // We use the constructor because literals are evaluated only once in
+  // < ES 3.1.
+  // See http://www.mail-archive.com/es-discuss@mozilla.org/msg01796.html
+  var re = RegExp('\r\n|\r|\n', 'g');
+  var sliceIndex = 0;
+  var result;
+  var lines = [];
+
+  while (result = re.exec(str)) {
+    var line = new goog.string.newlines.Line(
+        str, sliceIndex, result.index, result.index + result[0].length);
+    lines.push(line);
+
+    // remember where to start the slice from
+    sliceIndex = re.lastIndex;
+  }
+
+  // If the string does not end with a newline, add the last line.
+  if (sliceIndex < str.length) {
+    var line = new goog.string.newlines.Line(
+        str, sliceIndex, str.length, str.length);
+    lines.push(line);
+  }
+
+  return lines;
+};
+
+goog.provide('ol.format.TextFeature');
+
+goog.require('goog.asserts');
+goog.require('ol.format.Feature');
+goog.require('ol.format.FormatType');
+
+
+
+/**
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for text feature formats.
+ *
+ * @constructor
+ * @extends {ol.format.Feature}
+ */
+ol.format.TextFeature = function() {
+  goog.base(this);
+};
+goog.inherits(ol.format.TextFeature, ol.format.Feature);
+
+
+/**
+ * @param {Document|Node|Object|string} source Source.
+ * @private
+ * @return {string} Text.
+ */
+ol.format.TextFeature.prototype.getText_ = function(source) {
+  if (goog.isString(source)) {
+    return source;
+  } else {
+    goog.asserts.fail();
+    return '';
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.getType = function() {
+  return ol.format.FormatType.TEXT;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readFeature = function(source, opt_options) {
+  return this.readFeatureFromText(
+      this.getText_(source), this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.Feature} Feature.
+ */
+ol.format.TextFeature.prototype.readFeatureFromText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) {
+  return this.readFeaturesFromText(
+      this.getText_(source), this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.format.TextFeature.prototype.readFeaturesFromText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) {
+  return this.readGeometryFromText(
+      this.getText_(source), this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @protected
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.TextFeature.prototype.readGeometryFromText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.readProjection = function(source) {
+  return this.readProjectionFromText(this.getText_(source));
+};
+
+
+/**
+ * @param {string} text Text.
+ * @protected
+ * @return {ol.proj.Projection} Projection.
+ */
+ol.format.TextFeature.prototype.readProjectionFromText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) {
+  return this.writeFeatureText(feature, this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {ol.Feature} feature Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @protected
+ * @return {string} Text.
+ */
+ol.format.TextFeature.prototype.writeFeatureText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.writeFeatures = function(
+    features, opt_options) {
+  return this.writeFeaturesText(features, this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @protected
+ * @return {string} Text.
+ */
+ol.format.TextFeature.prototype.writeFeaturesText = goog.abstractMethod;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TextFeature.prototype.writeGeometry = function(
+    geometry, opt_options) {
+  return this.writeGeometryText(geometry, this.adaptOptions(opt_options));
+};
+
+
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @protected
+ * @return {string} Text.
+ */
+ol.format.TextFeature.prototype.writeGeometryText = goog.abstractMethod;
+
+goog.provide('ol.format.IGC');
+goog.provide('ol.format.IGCZ');
+
+goog.require('goog.asserts');
+goog.require('goog.string');
+goog.require('goog.string.newlines');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.TextFeature');
+goog.require('ol.geom.LineString');
+goog.require('ol.proj');
+
+
+/**
+ * IGC altitude/z. One of 'barometric', 'gps', 'none'.
+ * @enum {string}
+ * @api
+ */
+ol.format.IGCZ = {
+  BAROMETRIC: 'barometric',
+  GPS: 'gps',
+  NONE: 'none'
+};
+
+
+
+/**
+ * @classdesc
+ * Feature format for `*.igc` flight recording files.
+ *
+ * @constructor
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.IGCOptions=} opt_options Options.
+ * @api
+ */
+ol.format.IGC = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this);
+
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+
+  /**
+   * @private
+   * @type {ol.format.IGCZ}
+   */
+  this.altitudeMode_ = goog.isDef(options.altitudeMode) ?
+      options.altitudeMode : ol.format.IGCZ.NONE;
+
+};
+goog.inherits(ol.format.IGC, ol.format.TextFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.IGC.EXTENSIONS_ = ['.igc'];
+
+
+/**
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.format.IGC.B_RECORD_RE_ =
+    /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/;
+
+
+/**
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;
+
+
+/**
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.IGC.prototype.getExtensions = function() {
+  return ol.format.IGC.EXTENSIONS_;
+};
+
+
+/**
+ * Read the feature from the IGC source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api
+ */
+ol.format.IGC.prototype.readFeature;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) {
+  var altitudeMode = this.altitudeMode_;
+  var lines = goog.string.newlines.splitLines(text);
+  /** @type {Object.<string, string>} */
+  var properties = {};
+  var flatCoordinates = [];
+  var year = 2000;
+  var month = 0;
+  var day = 1;
+  var i, ii;
+  for (i = 0, ii = lines.length; i < ii; ++i) {
+    var line = lines[i];
+    var m;
+    if (line.charAt(0) == 'B') {
+      m = ol.format.IGC.B_RECORD_RE_.exec(line);
+      if (m) {
+        var hour = parseInt(m[1], 10);
+        var minute = parseInt(m[2], 10);
+        var second = parseInt(m[3], 10);
+        var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000;
+        if (m[6] == 'S') {
+          y = -y;
+        }
+        var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000;
+        if (m[9] == 'W') {
+          x = -x;
+        }
+        flatCoordinates.push(x, y);
+        if (altitudeMode != ol.format.IGCZ.NONE) {
+          var z;
+          if (altitudeMode == ol.format.IGCZ.GPS) {
+            z = parseInt(m[11], 10);
+          } else if (altitudeMode == ol.format.IGCZ.BAROMETRIC) {
+            z = parseInt(m[12], 10);
+          } else {
+            goog.asserts.fail();
+            z = 0;
+          }
+          flatCoordinates.push(z);
+        }
+        var dateTime = Date.UTC(year, month, day, hour, minute, second);
+        flatCoordinates.push(dateTime / 1000);
+      }
+    } else if (line.charAt(0) == 'H') {
+      m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line);
+      if (m) {
+        day = parseInt(m[1], 10);
+        month = parseInt(m[2], 10) - 1;
+        year = 2000 + parseInt(m[3], 10);
+      } else {
+        m = ol.format.IGC.H_RECORD_RE_.exec(line);
+        if (m) {
+          properties[m[1]] = goog.string.trim(m[2]);
+          m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line);
+        }
+      }
+    }
+  }
+  if (flatCoordinates.length === 0) {
+    return null;
+  }
+  var lineString = new ol.geom.LineString(null);
+  var layout = altitudeMode == ol.format.IGCZ.NONE ?
+      ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM;
+  lineString.setFlatCoordinates(layout, flatCoordinates);
+  var feature = new ol.Feature(ol.format.Feature.transformWithOptions(
+      lineString, false, opt_options));
+  feature.setProperties(properties);
+  return feature;
+};
+
+
+/**
+ * Read the feature from the source. As IGC sources contain a single
+ * feature, this will return the feature in an array.
+ *
+ * @function
+ * @param {ArrayBuffer|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 (!goog.isNull(feature)) {
+    return [feature];
+  } else {
+    return [];
+  }
+};
+
+
+/**
+ * Read the projection from the IGC source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
+ */
+ol.format.IGC.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.IGC.prototype.readProjectionFromText = function(text) {
+  return this.defaultDataProjection;
+};
+
+goog.provide('ol.style.Text');
+
+
+
+/**
+ * @classdesc
+ * Set text style for vector features.
+ *
+ * @constructor
+ * @param {olx.style.TextOptions=} opt_options Options.
+ * @api
+ */
+ol.style.Text = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.font_ = options.font;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.rotation_ = options.rotation;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.scale_ = options.scale;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.text_ = options.text;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.textAlign_ = options.textAlign;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.textBaseline_ = options.textBaseline;
+
+  /**
+   * @private
+   * @type {ol.style.Fill}
+   */
+  this.fill_ = goog.isDef(options.fill) ? options.fill : null;
+
+  /**
+   * @private
+   * @type {ol.style.Stroke}
+   */
+  this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.offsetX_ = goog.isDef(options.offsetX) ? options.offsetX : 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.offsetY_ = goog.isDef(options.offsetY) ? options.offsetY : 0;
+};
+
+
+/**
+ * @return {string|undefined} Font.
+ * @api
+ */
+ol.style.Text.prototype.getFont = function() {
+  return this.font_;
+};
+
+
+/**
+ * @return {number} Horizontal text offset.
+ * @api
+ */
+ol.style.Text.prototype.getOffsetX = function() {
+  return this.offsetX_;
+};
+
+
+/**
+ * @return {number} Vertical text offset.
+ * @api
+ */
+ol.style.Text.prototype.getOffsetY = function() {
+  return this.offsetY_;
+};
+
+
+/**
+ * @return {ol.style.Fill} Fill style.
+ * @api
+ */
+ol.style.Text.prototype.getFill = function() {
+  return this.fill_;
+};
+
+
+/**
+ * @return {number|undefined} Rotation.
+ * @api
+ */
+ol.style.Text.prototype.getRotation = function() {
+  return this.rotation_;
+};
+
+
+/**
+ * @return {number|undefined} Scale.
+ * @api
+ */
+ol.style.Text.prototype.getScale = function() {
+  return this.scale_;
+};
+
+
+/**
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.getStroke = function() {
+  return this.stroke_;
+};
+
+
+/**
+ * @return {string|undefined} Text.
+ * @api
+ */
+ol.style.Text.prototype.getText = function() {
+  return this.text_;
+};
+
+
+/**
+ * @return {string|undefined} Text align.
+ * @api
+ */
+ol.style.Text.prototype.getTextAlign = function() {
+  return this.textAlign_;
+};
+
+
+/**
+ * @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.
+ */
+ol.style.Text.prototype.setOffsetX = function(offsetX) {
+  this.offsetX_ = offsetX;
+};
+
+
+/**
+ * Set the y offset.
+ *
+ * @param {number} offsetY Vertical text offset.
+ */
+ol.style.Text.prototype.setOffsetY = function(offsetY) {
+  this.offsetY_ = offsetY;
+};
+
+
+/**
+ * Set the fill.
+ *
+ * @param {ol.style.Fill} fill Fill style.
+ * @api
+ */
+ol.style.Text.prototype.setFill = function(fill) {
+  this.fill_ = fill;
+};
+
+
+/**
+ * Set the rotation.
+ *
+ * @param {number|undefined} rotation Rotation.
+ * @api
+ */
+ol.style.Text.prototype.setRotation = function(rotation) {
+  this.rotation_ = rotation;
+};
+
+
+/**
+ * Set the scale.
+ *
+ * @param {number|undefined} scale Scale.
+ * @api
+ */
+ol.style.Text.prototype.setScale = function(scale) {
+  this.scale_ = scale;
+};
+
+
+/**
+ * Set the stroke.
+ *
+ * @param {ol.style.Stroke} stroke Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.setStroke = function(stroke) {
+  this.stroke_ = stroke;
+};
+
+
+/**
+ * Set the text.
+ *
+ * @param {string|undefined} text Text.
+ * @api
+ */
+ol.style.Text.prototype.setText = function(text) {
+  this.text_ = text;
+};
+
+
+/**
+ * Set the text alignment.
+ *
+ * @param {string|undefined} textAlign Text align.
+ * @api
+ */
+ol.style.Text.prototype.setTextAlign = function(textAlign) {
+  this.textAlign_ = textAlign;
+};
+
+
+/**
+ * Set the text baseline.
+ *
+ * @param {string|undefined} textBaseline Text baseline.
+ * @api
+ */
+ol.style.Text.prototype.setTextBaseline = function(textBaseline) {
+  this.textBaseline_ = textBaseline;
+};
+
+// FIXME http://earth.google.com/kml/1.0 namespace?
+// FIXME why does node.getAttribute return an unknown type?
+// FIXME text
+// FIXME serialize arbitrary feature properties
+// FIXME don't parse style if extractStyles is false
+
+goog.provide('ol.format.KML');
+
+goog.require('goog.Uri');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.math');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('ol.Feature');
+goog.require('ol.color');
+goog.require('ol.feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.format.XSD');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryCollection');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.LinearRing');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.proj');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Icon');
+goog.require('ol.style.IconAnchorUnits');
+goog.require('ol.style.IconOrigin');
+goog.require('ol.style.Image');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Style');
+goog.require('ol.style.Text');
+goog.require('ol.xml');
+
+
+/**
+ * @typedef {{x: number, xunits: (ol.style.IconAnchorUnits|undefined),
+ *            y: number, yunits: (ol.style.IconAnchorUnits|undefined)}}
+ */
+ol.format.KMLVec2_;
+
+
+/**
+ * @typedef {{flatCoordinates: Array.<number>,
+ *            whens: Array.<number>}}
+ */
+ol.format.KMLGxTrackObject_;
+
+
+
+/**
+ * @classdesc
+ * Feature format for reading and writing data in the KML format.
+ *
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @param {olx.format.KMLOptions=} opt_options Options.
+ * @api stable
+ */
+ol.format.KML = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this);
+
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+
+  var defaultStyle = goog.isDef(options.defaultStyle) ?
+      options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_;
+
+  /** @type {Object.<string, (Array.<ol.style.Style>|string)>} */
+  var sharedStyles = {};
+
+  var findStyle =
+      /**
+       * @param {Array.<ol.style.Style>|string|undefined} styleValue Style
+       *     value.
+       * @return {Array.<ol.style.Style>} Style.
+       */
+      function(styleValue) {
+    if (goog.isArray(styleValue)) {
+      return styleValue;
+    } else if (goog.isString(styleValue)) {
+      // KML files in the wild occasionally forget the leading `#` on styleUrls
+      // defined in the same document.  Add a leading `#` if it enables to find
+      // a style.
+      if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) {
+        styleValue = '#' + styleValue;
+      }
+      return findStyle(sharedStyles[styleValue]);
+    } else {
+      return defaultStyle;
+    }
+  };
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.extractStyles_ = goog.isDef(options.extractStyles) ?
+      options.extractStyles : true;
+
+  /**
+   * @private
+   * @type {Object.<string, (Array.<ol.style.Style>|string)>}
+   */
+  this.sharedStyles_ = sharedStyles;
+
+  /**
+   * @private
+   * @type {ol.feature.FeatureStyleFunction}
+   */
+  this.featureStyleFunction_ =
+      /**
+       * @param {number} resolution Resolution.
+       * @return {Array.<ol.style.Style>} Style.
+       * @this {ol.Feature}
+       */
+      function(resolution) {
+    var style = /** @type {Array.<ol.style.Style>|undefined} */
+        (this.get('Style'));
+    if (goog.isDef(style)) {
+      return style;
+    }
+    var styleUrl = /** @type {string|undefined} */ (this.get('styleUrl'));
+    if (goog.isDef(styleUrl)) {
+      return findStyle(styleUrl);
+    }
+    return defaultStyle;
+  };
+
+};
+goog.inherits(ol.format.KML, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.KML.EXTENSIONS_ = ['.kml'];
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.KML.GX_NAMESPACE_URIS_ = [
+  'http://www.google.com/kml/ext/2.2'
+];
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.KML.NAMESPACE_URIS_ = [
+  null,
+  'http://earth.google.com/kml/2.0',
+  'http://earth.google.com/kml/2.1',
+  'http://earth.google.com/kml/2.2',
+  'http://www.opengis.net/kml/2.2'
+];
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' +
+    'https://developers.google.com/kml/schema/kml22gx.xsd';
+
+
+/**
+ * @const
+ * @type {ol.Color}
+ * @private
+ */
+ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1];
+
+
+/**
+ * @const
+ * @type {ol.style.Fill}
+ * @private
+ */
+ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({
+  color: ol.format.KML.DEFAULT_COLOR_
+});
+
+
+/**
+ * @const
+ * @type {ol.Size}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [2, 20]; // FIXME maybe [8, 32] ?
+
+
+/**
+ * @const
+ * @type {ol.style.IconAnchorUnits}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ =
+    ol.style.IconAnchorUnits.PIXELS;
+
+
+/**
+ * @const
+ * @type {ol.style.IconAnchorUnits}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ =
+    ol.style.IconAnchorUnits.PIXELS;
+
+
+/**
+ * @const
+ * @type {ol.Size}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [32, 32];
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
+    'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
+
+
+/**
+ * @const
+ * @type {ol.style.Image}
+ * @private
+ */
+ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({
+  anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_,
+  anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_,
+  anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_,
+  crossOrigin: 'anonymous',
+  rotation: 0,
+  scale: 1,
+  size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_,
+  src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_
+});
+
+
+/**
+ * @const
+ * @type {ol.style.Stroke}
+ * @private
+ */
+ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
+  color: ol.format.KML.DEFAULT_COLOR_,
+  width: 1
+});
+
+
+/**
+ * @const
+ * @type {ol.style.Text}
+ * @private
+ */
+ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({
+  font: 'normal 16px Helvetica',
+  fill: ol.format.KML.DEFAULT_FILL_STYLE_,
+  stroke: ol.format.KML.DEFAULT_STROKE_STYLE_,
+  scale: 1
+});
+
+
+/**
+ * @const
+ * @type {ol.style.Style}
+ * @private
+ */
+ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({
+  fill: ol.format.KML.DEFAULT_FILL_STYLE_,
+  image: ol.format.KML.DEFAULT_IMAGE_STYLE_,
+  text: ol.format.KML.DEFAULT_TEXT_STYLE_,
+  stroke: ol.format.KML.DEFAULT_STROKE_STYLE_,
+  zIndex: 0
+});
+
+
+/**
+ * @const
+ * @type {Array.<ol.style.Style>}
+ * @private
+ */
+ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_];
+
+
+/**
+ * @const
+ * @type {Object.<string, ol.style.IconAnchorUnits>}
+ * @private
+ */
+ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = {
+  'fraction': ol.style.IconAnchorUnits.FRACTION,
+  'pixels': ol.style.IconAnchorUnits.PIXELS
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {ol.Color|undefined} Color.
+ */
+ol.format.KML.readColor_ = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  // The KML specification states that colors should not include a leading `#`
+  // but we tolerate them.
+  var m = /^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(s);
+  if (m) {
+    var hexColor = m[1];
+    return [
+      parseInt(hexColor.substr(6, 2), 16),
+      parseInt(hexColor.substr(4, 2), 16),
+      parseInt(hexColor.substr(2, 2), 16),
+      parseInt(hexColor.substr(0, 2), 16) / 255
+    ];
+
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {Array.<number>|undefined} Flat coordinates.
+ */
+ol.format.KML.readFlatCoordinates_ = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  var flatCoordinates = [];
+  // The KML specification states that coordinate tuples should not include
+  // spaces, but we tolerate them.
+  var re =
+      /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i;
+  var m;
+  while ((m = re.exec(s))) {
+    var x = parseFloat(m[1]);
+    var y = parseFloat(m[2]);
+    var z = m[3] ? parseFloat(m[3]) : 0;
+    flatCoordinates.push(x, y, z);
+    s = s.substr(m[0].length);
+  }
+  if (s !== '') {
+    return undefined;
+  }
+  return flatCoordinates;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string|undefined} Style URL.
+ */
+ol.format.KML.readStyleUrl_ = function(node) {
+  var s = goog.string.trim(ol.xml.getAllTextContent(node, false));
+  if (goog.isDefAndNotNull(node.baseURI)) {
+    return goog.Uri.resolve(node.baseURI, s).toString();
+  } else {
+    return s;
+  }
+
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {string} URI.
+ */
+ol.format.KML.readURI_ = function(node) {
+  var s = ol.xml.getAllTextContent(node, false);
+  if (goog.isDefAndNotNull(node.baseURI)) {
+    return goog.Uri.resolve(node.baseURI, goog.string.trim(s)).toString();
+  } else {
+    return goog.string.trim(s);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {ol.format.KMLVec2_} Vec2.
+ */
+ol.format.KML.readVec2_ = function(node) {
+  var xunits = node.getAttribute('xunits');
+  var yunits = node.getAttribute('yunits');
+  return {
+    x: parseFloat(node.getAttribute('x')),
+    xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits],
+    y: parseFloat(node.getAttribute('y')),
+    yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits]
+  };
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @private
+ * @return {number|undefined} Scale.
+ */
+ol.format.KML.readScale_ = function(node) {
+  var number = ol.format.XSD.readDecimal(node);
+  if (goog.isDef(number)) {
+    return Math.sqrt(number);
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<ol.style.Style>|string|undefined} StyleMap.
+ */
+ol.format.KML.readStyleMapValue_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      /** @type {Array.<ol.style.Style>|string|undefined} */ (undefined),
+      ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.IconStyleParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == '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 (!goog.isDef(object)) {
+    return;
+  }
+  var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  goog.asserts.assert(goog.isObject(styleObject));
+  var IconObject = /** @type {Object} */ (goog.object.get(object, 'Icon', {}));
+  var src;
+  var href = /** @type {string|undefined} */
+      (goog.object.get(IconObject, 'href'));
+  if (goog.isDef(href)) {
+    src = href;
+  } else {
+    src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_;
+  }
+  var anchor, anchorXUnits, anchorYUnits;
+  var hotSpot = /** @type {ol.format.KMLVec2_|undefined} */
+      (goog.object.get(object, 'hotSpot'));
+  if (goog.isDef(hotSpot)) {
+    anchor = [hotSpot.x, hotSpot.y];
+    anchorXUnits = hotSpot.xunits;
+    anchorYUnits = hotSpot.yunits;
+  } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
+    anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_;
+    anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_;
+    anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_;
+  } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) {
+    anchor = [0.5, 0];
+    anchorXUnits = ol.style.IconAnchorUnits.FRACTION;
+    anchorYUnits = ol.style.IconAnchorUnits.FRACTION;
+  }
+
+  var offset;
+  var x = /** @type {number|undefined} */
+      (goog.object.get(IconObject, 'x'));
+  var y = /** @type {number|undefined} */
+      (goog.object.get(IconObject, 'y'));
+  if (goog.isDef(x) && goog.isDef(y)) {
+    offset = [x, y];
+  }
+
+  var size;
+  var w = /** @type {number|undefined} */
+      (goog.object.get(IconObject, 'w'));
+  var h = /** @type {number|undefined} */
+      (goog.object.get(IconObject, 'h'));
+  if (goog.isDef(w) && goog.isDef(h)) {
+    size = [w, h];
+  }
+
+  var rotation;
+  var heading = /** @type {number|undefined} */
+      (goog.object.get(object, 'heading'));
+  if (goog.isDef(heading)) {
+    rotation = goog.math.toRadians(heading);
+  }
+
+  var scale = /** @type {number|undefined} */
+      (goog.object.get(object, 'scale'));
+  if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
+    size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
+  }
+
+  var imageStyle = new ol.style.Icon({
+    anchor: anchor,
+    anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
+    anchorXUnits: anchorXUnits,
+    anchorYUnits: anchorYUnits,
+    crossOrigin: 'anonymous', // FIXME should this be configurable?
+    offset: offset,
+    offsetOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
+    rotation: rotation,
+    scale: scale,
+    size: size,
+    src: src
+  });
+  goog.object.set(styleObject, 'imageStyle', imageStyle);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.LabelStyleParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LabelStyle');
+  // FIXME colorMode
+  var object = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack);
+  if (!goog.isDef(object)) {
+    return;
+  }
+  var styleObject = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(styleObject));
+  var textStyle = new ol.style.Text({
+    fill: new ol.style.Fill({
+      color: /** @type {ol.Color} */
+          (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_))
+    }),
+    scale: /** @type {number|undefined} */
+        (goog.object.get(object, 'scale'))
+  });
+  goog.object.set(styleObject, 'textStyle', textStyle);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.LineStyleParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == '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 (!goog.isDef(object)) {
+    return;
+  }
+  var styleObject = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(styleObject));
+  var strokeStyle = new ol.style.Stroke({
+    color: /** @type {ol.Color} */
+        (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)),
+    width: /** @type {number} */ (goog.object.get(object, 'width', 1))
+  });
+  goog.object.set(styleObject, 'strokeStyle', strokeStyle);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.PolyStyleParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'PolyStyle');
+  // FIXME colorMode
+  var object = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack);
+  if (!goog.isDef(object)) {
+    return;
+  }
+  var styleObject = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(styleObject));
+  var fillStyle = new ol.style.Fill({
+    color: /** @type {ol.Color} */
+        (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_))
+  });
+  goog.object.set(styleObject, 'fillStyle', fillStyle);
+  var fill = /** @type {boolean|undefined} */ (goog.object.get(object, 'fill'));
+  if (goog.isDef(fill)) {
+    goog.object.set(styleObject, 'fill', fill);
+  }
+  var outline =
+      /** @type {boolean|undefined} */ (goog.object.get(object, 'outline'));
+  if (goog.isDef(outline)) {
+    goog.object.set(styleObject, 'outline', outline);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} LinearRing flat coordinates.
+ */
+ol.format.KML.readFlatLinearRing_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LinearRing');
+  return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(
+      null, ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.gxCoordParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(goog.array.contains(
+      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI));
+  goog.asserts.assert(node.localName == 'coord');
+  var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */
+      (objectStack[objectStack.length - 1]);
+  goog.asserts.assert(goog.isObject(gxTrackObject));
+  var flatCoordinates = gxTrackObject.flatCoordinates;
+  var s = ol.xml.getAllTextContent(node, false);
+  var re =
+      /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i;
+  var m = re.exec(s);
+  if (m) {
+    var x = parseFloat(m[1]);
+    var y = parseFloat(m[2]);
+    var z = parseFloat(m[3]);
+    flatCoordinates.push(x, y, z, 0);
+  } else {
+    flatCoordinates.push(0, 0, 0, 0);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.MultiLineString|undefined} MultiLineString.
+ */
+ol.format.KML.readGxMultiTrack_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(goog.array.contains(
+      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI));
+  goog.asserts.assert(node.localName == 'MultiTrack');
+  var lineStrings = ol.xml.pushParseAndPop(
+      /** @type {Array.<ol.geom.LineString>} */ ([]),
+      ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack);
+  if (!goog.isDef(lineStrings)) {
+    return undefined;
+  }
+  var multiLineString = new ol.geom.MultiLineString(null);
+  multiLineString.setLineStrings(lineStrings);
+  return multiLineString;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
+ */
+ol.format.KML.readGxTrack_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(goog.array.contains(
+      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI));
+  goog.asserts.assert(node.localName == 'Track');
+  var gxTrackObject = ol.xml.pushParseAndPop(
+      /** @type {ol.format.KMLGxTrackObject_} */ ({
+        flatCoordinates: [],
+        whens: []
+      }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack);
+  if (!goog.isDef(gxTrackObject)) {
+    return undefined;
+  }
+  var flatCoordinates = gxTrackObject.flatCoordinates;
+  var whens = gxTrackObject.whens;
+  goog.asserts.assert(flatCoordinates.length / 4 == whens.length);
+  var i, ii;
+  for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii;
+       ++i) {
+    flatCoordinates[4 * i + 3] = whens[i];
+  }
+  var lineString = new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
+  return lineString;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object} Icon object.
+ */
+ol.format.KML.readIcon_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Icon');
+  var iconObject = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.ICON_PARSERS_, node, objectStack);
+  if (goog.isDef(iconObject)) {
+    return iconObject;
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<number>} Flat coordinates.
+ */
+ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(null,
+      ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.LineString|undefined} LineString.
+ */
+ol.format.KML.readLineString_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LineString');
+  var flatCoordinates =
+      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
+  if (goog.isDef(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.geom.Polygon|undefined} Polygon.
+ */
+ol.format.KML.readLinearRing_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'LinearRing');
+  var flatCoordinates =
+      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
+  if (goog.isDef(flatCoordinates)) {
+    var polygon = new ol.geom.Polygon(null);
+    polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates,
+        [flatCoordinates.length]);
+    return polygon;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.KML.readMultiGeometry_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'MultiGeometry');
+  var geometries = ol.xml.pushParseAndPop(
+      /** @type {Array.<ol.geom.Geometry>} */ ([]),
+      ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack);
+  if (!goog.isDef(geometries)) {
+    return null;
+  }
+  if (geometries.length === 0) {
+    return new ol.geom.GeometryCollection(geometries);
+  }
+  var homogeneous = true;
+  var type = geometries[0].getType();
+  var geometry, i, ii;
+  for (i = 1, ii = geometries.length; i < ii; ++i) {
+    geometry = geometries[i];
+    if (geometry.getType() != type) {
+      homogeneous = false;
+      break;
+    }
+  }
+  if (homogeneous) {
+    /** @type {ol.geom.GeometryLayout} */
+    var layout;
+    /** @type {Array.<number>} */
+    var flatCoordinates;
+    if (type == ol.geom.GeometryType.POINT) {
+      var point = geometries[0];
+      goog.asserts.assertInstanceof(point, ol.geom.Point);
+      layout = point.getLayout();
+      flatCoordinates = point.getFlatCoordinates();
+      for (i = 1, ii = geometries.length; i < ii; ++i) {
+        geometry = geometries[i];
+        goog.asserts.assertInstanceof(geometry, ol.geom.Point);
+        goog.asserts.assert(geometry.getLayout() == layout);
+        goog.array.extend(flatCoordinates, geometry.getFlatCoordinates());
+      }
+      var multiPoint = new ol.geom.MultiPoint(null);
+      multiPoint.setFlatCoordinates(layout, flatCoordinates);
+      return multiPoint;
+    } else if (type == ol.geom.GeometryType.LINE_STRING) {
+      var multiLineString = new ol.geom.MultiLineString(null);
+      multiLineString.setLineStrings(geometries);
+      return multiLineString;
+    } else if (type == ol.geom.GeometryType.POLYGON) {
+      var multiPolygon = new ol.geom.MultiPolygon(null);
+      multiPolygon.setPolygons(geometries);
+      return multiPolygon;
+    } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
+      return new ol.geom.GeometryCollection(geometries);
+    } else {
+      goog.asserts.fail();
+      return null;
+    }
+  } else {
+    return new ol.geom.GeometryCollection(geometries);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Point|undefined} Point.
+ */
+ol.format.KML.readPoint_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Point');
+  var flatCoordinates =
+      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
+  if (goog.isDefAndNotNull(flatCoordinates)) {
+    var point = new ol.geom.Point(null);
+    goog.asserts.assert(flatCoordinates.length == 3);
+    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
+    return point;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.geom.Polygon|undefined} Polygon.
+ */
+ol.format.KML.readPolygon_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Polygon');
+  var flatLinearRings = ol.xml.pushParseAndPop(
+      /** @type {Array.<Array.<number>>} */ ([null]),
+      ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack);
+  if (goog.isDefAndNotNull(flatLinearRings) &&
+      !goog.isNull(flatLinearRings[0])) {
+    var polygon = new ol.geom.Polygon(null);
+    var flatCoordinates = flatLinearRings[0];
+    var ends = [flatCoordinates.length];
+    var i, ii;
+    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
+      goog.array.extend(flatCoordinates, flatLinearRings[i]);
+      ends.push(flatCoordinates.length);
+    }
+    polygon.setFlatCoordinates(
+        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
+    return polygon;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<ol.style.Style>} Style.
+ */
+ol.format.KML.readStyle_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Style');
+  var styleObject = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.STYLE_PARSERS_, node, objectStack);
+  if (!goog.isDef(styleObject)) {
+    return null;
+  }
+  var fillStyle = /** @type {ol.style.Fill} */ (goog.object.get(
+      styleObject, 'fillStyle', ol.format.KML.DEFAULT_FILL_STYLE_));
+  var fill = /** @type {boolean|undefined} */
+      (goog.object.get(styleObject, 'fill'));
+  if (goog.isDef(fill) && !fill) {
+    fillStyle = null;
+  }
+  var imageStyle = /** @type {ol.style.Image} */ (goog.object.get(
+      styleObject, 'imageStyle', ol.format.KML.DEFAULT_IMAGE_STYLE_));
+  var textStyle = /** @type {ol.style.Text} */ (goog.object.get(
+      styleObject, 'textStyle', ol.format.KML.DEFAULT_TEXT_STYLE_));
+  var strokeStyle = /** @type {ol.style.Stroke} */ (goog.object.get(
+      styleObject, 'strokeStyle', ol.format.KML.DEFAULT_STROKE_STYLE_));
+  var outline = /** @type {boolean|undefined} */
+      (goog.object.get(styleObject, 'outline'));
+  if (goog.isDef(outline) && !outline) {
+    strokeStyle = null;
+  }
+  return [new ol.style.Style({
+    fill: fillStyle,
+    image: imageStyle,
+    stroke: strokeStyle,
+    text: textStyle,
+    zIndex: undefined // FIXME
+  })];
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.DataParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Data');
+  var name = node.getAttribute('name');
+  if (!goog.isNull(name)) {
+    var data = ol.xml.pushParseAndPop(
+        undefined, ol.format.KML.DATA_PARSERS_, node, objectStack);
+    if (goog.isDef(data)) {
+      var featureObject =
+          /** @type {Object} */ (objectStack[objectStack.length - 1]);
+      goog.asserts.assert(goog.isObject(featureObject));
+      goog.object.set(featureObject, name, data);
+    }
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.ExtendedDataParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ExtendedData');
+  ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.PairDataParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Pair');
+  var pairObject = ol.xml.pushParseAndPop(
+      {}, ol.format.KML.PAIR_PARSERS_, node, objectStack);
+  if (!goog.isDef(pairObject)) {
+    return;
+  }
+  var key = /** @type {string|undefined} */
+      (goog.object.get(pairObject, 'key'));
+  if (goog.isDef(key) && key == 'normal') {
+    var styleUrl = /** @type {string|undefined} */
+        (goog.object.get(pairObject, 'styleUrl'));
+    if (goog.isDef(styleUrl)) {
+      objectStack[objectStack.length - 1] = styleUrl;
+    }
+    var Style = /** @type {ol.style.Style} */
+        (goog.object.get(pairObject, 'Style'));
+    if (goog.isDef(Style)) {
+      objectStack[objectStack.length - 1] = Style;
+    }
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'StyleMap');
+  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
+  if (!goog.isDef(styleMapValue)) {
+    return;
+  }
+  var placemarkObject = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(placemarkObject));
+  if (goog.isArray(styleMapValue)) {
+    goog.object.set(placemarkObject, 'Style', styleMapValue);
+  } else if (goog.isString(styleMapValue)) {
+    goog.object.set(placemarkObject, 'styleUrl', styleMapValue);
+  } else {
+    goog.asserts.fail();
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.SchemaDataParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'SchemaData');
+  ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.SimpleDataParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'SimpleData');
+  var name = node.getAttribute('name');
+  if (!goog.isNull(name)) {
+    var data = ol.format.XSD.readString(node);
+    var featureObject =
+        /** @type {Object} */ (objectStack[objectStack.length - 1]);
+    goog.object.set(featureObject, name, data);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'innerBoundaryIs');
+  var flatLinearRing = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>|undefined} */ (undefined),
+      ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack);
+  if (goog.isDef(flatLinearRing)) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    goog.asserts.assert(goog.isArray(flatLinearRings));
+    goog.asserts.assert(flatLinearRings.length > 0);
+    flatLinearRings.push(flatLinearRing);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'outerBoundaryIs');
+  var flatLinearRing = ol.xml.pushParseAndPop(
+      /** @type {Array.<number>|undefined} */ (undefined),
+      ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack);
+  if (goog.isDef(flatLinearRing)) {
+    var flatLinearRings = /** @type {Array.<Array.<number>>} */
+        (objectStack[objectStack.length - 1]);
+    goog.asserts.assert(goog.isArray(flatLinearRings));
+    goog.asserts.assert(flatLinearRings.length > 0);
+    flatLinearRings[0] = flatLinearRing;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.whenParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'when');
+  var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */
+      (objectStack[objectStack.length - 1]);
+  goog.asserts.assert(goog.isObject(gxTrackObject));
+  var whens = gxTrackObject.whens;
+  var s = ol.xml.getAllTextContent(node, false);
+  var re =
+      /^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/;
+  var m = re.exec(s);
+  if (m) {
+    var year = parseInt(m[1], 10);
+    var month = goog.isDef(m[3]) ? parseInt(m[3], 10) - 1 : 0;
+    var day = goog.isDef(m[5]) ? parseInt(m[5], 10) : 1;
+    var hour = goog.isDef(m[7]) ? parseInt(m[7], 10) : 0;
+    var minute = goog.isDef(m[8]) ? parseInt(m[8], 10) : 0;
+    var second = goog.isDef(m[9]) ? parseInt(m[9], 10) : 0;
+    var when = Date.UTC(year, month, day, hour, minute, second);
+    if (goog.isDef(m[10]) && m[10] != 'Z') {
+      var sign = m[11] == '-' ? -1 : 1;
+      when += sign * 60 * parseInt(m[12], 10);
+      if (goog.isDef(m[13])) {
+        when += sign * 60 * 60 * parseInt(m[13], 10);
+      }
+    }
+    whens.push(when);
+  } else {
+    whens.push(0);
+  }
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.DATA_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'value': ol.xml.makeReplacer(ol.format.XSD.readString)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Data': ol.format.KML.DataParser_,
+      'SchemaData': ol.format.KML.SchemaDataParser_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_,
+      'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'when': ol.format.KML.whenParser_
+    }, ol.xml.makeParsersNS(
+        ol.format.KML.GX_NAMESPACE_URIS_, {
+          'coord': ol.format.KML.gxCoordParser_
+        }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.ICON_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
+    }, ol.xml.makeParsersNS(
+        ol.format.KML.GX_NAMESPACE_URIS_, {
+          'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+          'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+          'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+          'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
+        }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_),
+      'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
+      'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_),
+      'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
+      'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
+      'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LineString': ol.xml.makeArrayPusher(ol.format.KML.readLineString_),
+      'LinearRing': ol.xml.makeArrayPusher(ol.format.KML.readLinearRing_),
+      'MultiGeometry': ol.xml.makeArrayPusher(ol.format.KML.readMultiGeometry_),
+      'Point': ol.xml.makeArrayPusher(ol.format.KML.readPoint_),
+      'Polygon': ol.xml.makeArrayPusher(ol.format.KML.readPolygon_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.GX_NAMESPACE_URIS_, {
+      'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.PAIR_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_),
+      'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyleUrl_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeParsersNS(
+    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.makeParsersNS(
+        ol.format.KML.GX_NAMESPACE_URIS_, {
+          'MultiTrack': ol.xml.makeObjectPropertySetter(
+              ol.format.KML.readGxMultiTrack_, 'geometry'),
+          'Track': ol.xml.makeObjectPropertySetter(
+              ol.format.KML.readGxTrack_, 'geometry')
+        }
+    ));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
+      'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
+      'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'SimpleData': ol.format.KML.SimpleDataParser_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.STYLE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'IconStyle': ol.format.KML.IconStyleParser_,
+      'LabelStyle': ol.format.KML.LabelStyleParser_,
+      'LineStyle': ol.format.KML.LineStyleParser_,
+      'PolyStyle': ol.format.KML.PolyStyleParser_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Pair': ol.format.KML.PairDataParser_
+    });
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.KML.prototype.getExtensions = function() {
+  return ol.format.KML.EXTENSIONS_;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<ol.Feature>|undefined} Features.
+ */
+ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  var localName = ol.xml.getLocalName(node);
+  goog.asserts.assert(localName == 'Document' || localName == 'Folder');
+  // FIXME use scope somehow
+  var parsersNS = ol.xml.makeParsersNS(
+      ol.format.KML.NAMESPACE_URIS_, {
+        'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this),
+        'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this),
+        'Style': goog.bind(this.readSharedStyle_, this),
+        'StyleMap': goog.bind(this.readSharedStyleMap_, this)
+      });
+  var features = ol.xml.pushParseAndPop(/** @type {Array.<ol.Feature>} */ ([]),
+      parsersNS, node, objectStack, this);
+  if (goog.isDef(features)) {
+    return features;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Feature.
+ */
+ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Placemark');
+  var object = ol.xml.pushParseAndPop({'geometry': null},
+      ol.format.KML.PLACEMARK_PARSERS_, node, objectStack);
+  if (!goog.isDef(object)) {
+    return undefined;
+  }
+  var feature = new ol.Feature();
+  var id = node.getAttribute('id');
+  if (!goog.isNull(id)) {
+    feature.setId(id);
+  }
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  if (goog.isDefAndNotNull(object.geometry)) {
+    ol.format.Feature.transformWithOptions(object.geometry, false, options);
+  }
+  feature.setProperties(object);
+  if (this.extractStyles_) {
+    feature.setStyle(this.featureStyleFunction_);
+  }
+  return feature;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Style');
+  var id = node.getAttribute('id');
+  if (!goog.isNull(id)) {
+    var style = ol.format.KML.readStyle_(node, objectStack);
+    if (goog.isDef(style)) {
+      var styleUri;
+      if (goog.isDefAndNotNull(node.baseURI)) {
+        styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString();
+      } else {
+        styleUri = '#' + id;
+      }
+      this.sharedStyles_[styleUri] = style;
+    }
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'StyleMap');
+  var id = node.getAttribute('id');
+  if (goog.isNull(id)) {
+    return;
+  }
+  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
+  if (!goog.isDef(styleMapValue)) {
+    return;
+  }
+  var styleUri;
+  if (goog.isDefAndNotNull(node.baseURI)) {
+    styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString();
+  } else {
+    styleUri = '#' + id;
+  }
+  this.sharedStyles_[styleUri] = styleMapValue;
+};
+
+
+/**
+ * Read the first feature from a KML source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api stable
+ */
+ol.format.KML.prototype.readFeature;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
+    return null;
+  }
+  goog.asserts.assert(node.localName == 'Placemark');
+  var feature = this.readPlacemark_(
+      node, [this.getReadOptions(node, opt_options)]);
+  if (goog.isDef(feature)) {
+    return feature;
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * Read all features from a KML source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.format.KML.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
+    return [];
+  }
+  var features;
+  var localName = ol.xml.getLocalName(node);
+  if (localName == 'Document' || localName == 'Folder') {
+    features = this.readDocumentOrFolder_(
+        node, [this.getReadOptions(node, opt_options)]);
+    if (goog.isDef(features)) {
+      return features;
+    } else {
+      return [];
+    }
+  } else if (localName == 'Placemark') {
+    var feature = this.readPlacemark_(
+        node, [this.getReadOptions(node, opt_options)]);
+    if (goog.isDef(feature)) {
+      return [feature];
+    } else {
+      return [];
+    }
+  } else if (localName == 'kml') {
+    features = [];
+    var n;
+    for (n = node.firstElementChild; !goog.isNull(n);
+         n = n.nextElementSibling) {
+      var fs = this.readFeaturesFromNode(n, opt_options);
+      if (goog.isDef(fs)) {
+        goog.array.extend(features, fs);
+      }
+    }
+    return features;
+  } else {
+    return [];
+  }
+};
+
+
+/**
+ * @param {Document|Node|string} source Souce.
+ * @return {string|undefined} Name.
+ * @api stable
+ */
+ol.format.KML.prototype.readName = function(source) {
+  if (ol.xml.isDocument(source)) {
+    return this.readNameFromDocument(/** @type {Document} */ (source));
+  } else if (ol.xml.isNode(source)) {
+    return this.readNameFromNode(/** @type {Node} */ (source));
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readNameFromDocument(doc);
+  } else {
+    goog.asserts.fail();
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {string|undefined} Name.
+ */
+ol.format.KML.prototype.readNameFromDocument = function(doc) {
+  var n;
+  for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      var name = this.readNameFromNode(n);
+      if (goog.isDef(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; !goog.isNull(n); n = n.nextElementSibling) {
+    if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+        n.localName == 'name') {
+      return ol.format.XSD.readString(n);
+    }
+  }
+  for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) {
+    var localName = ol.xml.getLocalName(n);
+    if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+        (localName == 'Document' ||
+         localName == 'Folder' ||
+         localName == 'Placemark' ||
+         localName == 'kml')) {
+      var name = this.readNameFromNode(n);
+      if (goog.isDef(name)) {
+        return name;
+      }
+    }
+  }
+  return undefined;
+};
+
+
+/**
+ * Read the projection from a KML source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.KML.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.KML.prototype.readProjectionFromDocument = function(doc) {
+  return this.defaultDataProjection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.KML.prototype.readProjectionFromNode = function(node) {
+  return this.defaultDataProjection;
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the color to.
+ * @param {ol.Color|string} color Color.
+ * @private
+ */
+ol.format.KML.writeColorTextNode_ = function(node, color) {
+  var rgba = ol.color.asArray(color);
+  var opacity = (rgba.length == 4) ? rgba[3] : 1;
+  var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]];
+  var i;
+  for (i = 0; i < 4; ++i) {
+    var hex = parseInt(abgr[i], 10).toString(16);
+    abgr[i] = (hex.length == 1) ? '0' + hex : hex;
+  }
+  ol.format.XSD.writeStringTextNode(node, abgr.join(''));
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the coordinates to.
+ * @param {Array.<number>} coordinates Coordinates.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeCoordinatesTextNode_ =
+    function(node, coordinates, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+
+  var layout = goog.object.get(context, 'layout');
+  var stride = goog.object.get(context, 'stride');
+
+  var dimension;
+  if (layout == ol.geom.GeometryLayout.XY ||
+      layout == ol.geom.GeometryLayout.XYM) {
+    dimension = 2;
+  } else if (layout == ol.geom.GeometryLayout.XYZ ||
+      layout == ol.geom.GeometryLayout.XYZM) {
+    dimension = 3;
+  } else {
+    goog.asserts.fail();
+  }
+
+  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.
+ * @private
+ */
+ol.format.KML.writeDocument_ = function(node, features, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_,
+      ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Object} icon Icon object.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeIcon_ = function(node, icon, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(icon, orderedKeys);
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
+      values, objectStack, orderedKeys);
+  orderedKeys =
+      ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]];
+  values = ol.xml.makeSequence(icon, orderedKeys);
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_,
+      ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Icon} style Icon style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  var properties = {};
+  var src = style.getSrc();
+  var size = style.getSize();
+  var iconImageSize = style.getImageSize();
+  var iconProperties = {
+    'href': src
+  };
+
+  if (!goog.isNull(size)) {
+    goog.object.set(iconProperties, 'w', size[0]);
+    goog.object.set(iconProperties, 'h', size[1]);
+    var anchor = style.getAnchor(); // top-left
+    var origin = style.getOrigin(); // top-left
+
+    if (!goog.isNull(origin) && !goog.isNull(iconImageSize) &&
+        origin[0] !== 0 && origin[1] !== size[1]) {
+      goog.object.set(iconProperties, 'x', origin[0]);
+      goog.object.set(iconProperties, 'y',
+          iconImageSize[1] - (origin[1] + size[1]));
+    }
+
+    if (!goog.isNull(anchor) &&
+        anchor[0] !== 0 && anchor[1] !== size[1]) {
+      var /** @type {ol.format.KMLVec2_} */ hotSpot = {
+        x: anchor[0],
+        xunits: ol.style.IconAnchorUnits.PIXELS,
+        y: size[1] - anchor[1],
+        yunits: ol.style.IconAnchorUnits.PIXELS
+      };
+      goog.object.set(properties, 'hotSpot', hotSpot);
+    }
+  }
+
+  goog.object.set(properties, 'Icon', iconProperties);
+
+  var scale = style.getScale();
+  if (scale !== 1) {
+    goog.object.set(properties, 'scale', scale);
+  }
+
+  var rotation = style.getRotation();
+  if (rotation !== 0) {
+    goog.object.set(properties, 'heading', rotation); // 0-360
+  }
+
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Text} style style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  var properties = {};
+  var fill = style.getFill();
+  if (!goog.isNull(fill)) {
+    goog.object.set(properties, 'color', fill.getColor());
+  }
+  var scale = style.getScale();
+  if (goog.isDef(scale) && scale !== 1) {
+    goog.object.set(properties, 'scale', scale);
+  }
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys =
+      ol.format.KML.LABEL_STYLE_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.LABEL_STYLE_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Stroke} style style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeLineStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  var properties = {
+    'color': style.getColor(),
+    'width': style.getWidth()
+  };
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeMultiGeometry_ =
+    function(node, geometry, objectStack) {
+  goog.asserts.assert(
+      (geometry instanceof ol.geom.MultiPoint) ||
+      (geometry instanceof ol.geom.MultiLineString) ||
+      (geometry instanceof ol.geom.MultiPolygon));
+  /** @type {ol.xml.NodeStackItem} */
+  var context = {node: node};
+  var type = geometry.getType();
+  /** @type {Array.<ol.geom.Geometry>} */
+  var geometries;
+  /** @type {function(*, Array.<*>, string=): (Node|undefined)} */
+  var factory;
+  if (type == ol.geom.GeometryType.MULTI_POINT) {
+    geometries =
+        (/** @type {ol.geom.MultiPoint} */ (geometry)).getPoints();
+    factory = ol.format.KML.POINT_NODE_FACTORY_;
+  } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) {
+    geometries =
+        (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings();
+    factory = ol.format.KML.LINE_STRING_NODE_FACTORY_;
+  } else if (type == ol.geom.GeometryType.MULTI_POLYGON) {
+    geometries =
+        (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons();
+    factory = ol.format.KML.POLYGON_NODE_FACTORY_;
+  } else {
+    goog.asserts.fail();
+  }
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory,
+      geometries, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.LinearRing} linearRing Linear ring.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.BOUNDARY_IS_SERIALIZERS_,
+      ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack);
+};
+
+
+/**
+ * FIXME currently we do serialize arbitrary/custom feature properties
+ * (ExtendedData).
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+
+  // set id
+  if (goog.isDefAndNotNull(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 (goog.isDef(styleFunction)) {
+    // FIXME the styles returned by the style function are supposed to be
+    // resolution-independent here
+    var styles = styleFunction.call(feature, 0);
+    if (!goog.isNull(styles) && styles.length > 0) {
+      goog.object.set(properties, 'Style', styles[0]);
+      var textStyle = styles[0].getText();
+      if (!goog.isNull(textStyle)) {
+        goog.object.set(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 (goog.isDefAndNotNull(geometry)) {
+    geometry =
+        ol.format.Feature.transformWithOptions(geometry, true, options);
+  }
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
+      ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) {
+  goog.asserts.assert(
+      (geometry instanceof ol.geom.Point) ||
+      (geometry instanceof ol.geom.LineString) ||
+      (geometry instanceof ol.geom.LinearRing));
+  var flatCoordinates = geometry.getFlatCoordinates();
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  goog.object.set(context, 'layout', geometry.getLayout());
+  goog.object.set(context, 'stride', geometry.getStride());
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_,
+      ol.format.KML.COORDINATES_NODE_FACTORY_,
+      [flatCoordinates], objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.geom.Polygon} polygon Polygon.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writePolygon_ = function(node, polygon, objectStack) {
+  goog.asserts.assertInstanceof(polygon, ol.geom.Polygon);
+  var linearRings = polygon.getLinearRings();
+  goog.asserts.assert(linearRings.length > 0);
+  var outerRing = linearRings.shift();
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  // inner rings
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.POLYGON_SERIALIZERS_,
+      ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_,
+      linearRings, objectStack);
+  // outer ring
+  ol.xml.pushSerializeAndPop(context,
+      ol.format.KML.POLYGON_SERIALIZERS_,
+      ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_,
+      [outerRing], objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Fill} style Style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writePolyStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_,
+      ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack);
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the scale to.
+ * @param {number|undefined} scale Scale.
+ * @private
+ */
+ol.format.KML.writeScaleTextNode_ = function(node, scale) {
+  ol.format.XSD.writeDecimalTextNode(node, scale * scale);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {ol.style.Style} style Style.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeStyle_ = function(node, style, objectStack) {
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
+  var properties = {};
+  var fillStyle = style.getFill();
+  var strokeStyle = style.getStroke();
+  var imageStyle = style.getImage();
+  var textStyle = style.getText();
+  if (!goog.isNull(imageStyle)) {
+    goog.object.set(properties, 'IconStyle', imageStyle);
+  }
+  if (!goog.isNull(textStyle)) {
+    goog.object.set(properties, 'LabelStyle', textStyle);
+  }
+  if (!goog.isNull(strokeStyle)) {
+    goog.object.set(properties, 'LineStyle', strokeStyle);
+  }
+  if (!goog.isNull(fillStyle)) {
+    goog.object.set(properties, 'PolyStyle', fillStyle);
+  }
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.STYLE_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the Vec2 to.
+ * @param {ol.format.KMLVec2_} vec2 Vec2.
+ * @private
+ */
+ol.format.KML.writeVec2_ = function(node, vec2) {
+  node.setAttribute('x', vec2.x);
+  node.setAttribute('y', vec2.y);
+  node.setAttribute('xunits', vec2.xunits);
+  node.setAttribute('yunits', vec2.yunits);
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'Document', 'Placemark'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.KML_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Document': ol.xml.makeChildAppender(ol.format.KML.writeDocument_),
+      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, string>}
+ * @private
+ */
+ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = {
+  'Point': 'Point',
+  'LineString': 'LineString',
+  'LinearRing': 'LinearRing',
+  'Polygon': 'Polygon',
+  'MultiPoint': 'MultiGeometry',
+  'MultiLineString': 'MultiGeometry',
+  'MultiPolygon': 'MultiGeometry'
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'href'
+    ],
+    ol.xml.makeStructureNS(
+        ol.format.KML.GX_NAMESPACE_URIS_, [
+          'x', 'y', 'w', 'h'
+    ]));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
+    }, ol.xml.makeStructureNS(
+        ol.format.KML.GX_NAMESPACE_URIS_, {
+          'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+          'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+          'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+          'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
+        }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'scale', 'heading', 'Icon', 'hotSpot'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_),
+      'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
+      'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_),
+      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'color', 'scale'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_),
+      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'color', 'width'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.LINE_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_),
+      'width': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LinearRing': ol.xml.makeChildAppender(
+          ol.format.KML.writePrimitiveGeometry_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'LineString': ol.xml.makeChildAppender(
+          ol.format.KML.writePrimitiveGeometry_),
+      'Point': ol.xml.makeChildAppender(
+          ol.format.KML.writePrimitiveGeometry_),
+      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'name', 'open', 'visibility', 'address', 'phoneNumber', 'description',
+      'styleUrl', 'Style'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'MultiGeometry': ol.xml.makeChildAppender(
+          ol.format.KML.writeMultiGeometry_),
+      'LineString': ol.xml.makeChildAppender(
+          ol.format.KML.writePrimitiveGeometry_),
+      'LinearRing': ol.xml.makeChildAppender(
+          ol.format.KML.writePrimitiveGeometry_),
+      'Point': ol.xml.makeChildAppender(
+          ol.format.KML.writePrimitiveGeometry_),
+      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_),
+      'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_),
+      'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'description': ol.xml.makeChildAppender(
+          ol.format.XSD.writeStringTextNode),
+      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode),
+      'phoneNumber': ol.xml.makeChildAppender(
+          ol.format.XSD.writeStringTextNode),
+      'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
+      'visibility': ol.xml.makeChildAppender(
+          ol.format.XSD.writeBooleanTextNode)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'coordinates': ol.xml.makeChildAppender(
+          ol.format.KML.writeCoordinatesTextNode_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'outerBoundaryIs': ol.xml.makeChildAppender(
+          ol.format.KML.writeBoundaryIs_),
+      'innerBoundaryIs': ol.xml.makeChildAppender(
+          ol.format.KML.writeBoundaryIs_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle'
+    ]);
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_),
+      'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_),
+      'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_),
+      'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_)
+    });
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ol.format.KML.GX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
+  return ol.xml.createElementNS(ol.format.KML.GX_NAMESPACE_URIS_[0],
+      'gx:' + opt_nodeName);
+};
+
+
+/**
+ * @const
+ * @param {*} value Value.
+ * @param {Array.<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack,
+    opt_nodeName) {
+  goog.asserts.assertInstanceof(value, ol.Feature);
+  var parentNode = objectStack[objectStack.length - 1].node;
+  goog.asserts.assert(ol.xml.isNode(parentNode));
+  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 (goog.isDefAndNotNull(value)) {
+    goog.asserts.assertInstanceof(value, ol.geom.Geometry);
+    var parentNode = objectStack[objectStack.length - 1].node;
+    goog.asserts.assert(ol.xml.isNode(parentNode));
+    return ol.xml.createElementNS(parentNode.namespaceURI,
+        ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[value.getType()]);
+  }
+};
+
+
+/**
+ * A factory for creating coordinates nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color');
+
+
+/**
+ * A factory for creating coordinates nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.COORDINATES_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('coordinates');
+
+
+/**
+ * A factory for creating innerBoundaryIs nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('innerBoundaryIs');
+
+
+/**
+ * A factory for creating Point nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.POINT_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('Point');
+
+
+/**
+ * A factory for creating LineString nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.LINE_STRING_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('LineString');
+
+
+/**
+ * A factory for creating LinearRing nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.LINEAR_RING_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('LinearRing');
+
+
+/**
+ * A factory for creating Polygon nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.POLYGON_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('Polygon');
+
+
+/**
+ * A factory for creating outerBoundaryIs nodes.
+ * @const
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ =
+    ol.xml.makeSimpleNodeFactory('outerBoundaryIs');
+
+
+/**
+ * Encode an array of features in the KML format.
+ *
+ * @function
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {string} Result.
+ * @api stable
+ */
+ol.format.KML.prototype.writeFeatures;
+
+
+/**
+ * Encode an array of features in the KML format as an XML node.
+ *
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @api
+ */
+ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) {
+  opt_options = this.adaptOptions(opt_options);
+  var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml');
+  var xmlnsUri = 'http://www.w3.org/2000/xmlns/';
+  var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance';
+  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx',
+      ol.format.KML.GX_NAMESPACE_URIS_[0]);
+  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri);
+  ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation',
+      ol.format.KML.SCHEMA_LOCATION_);
+
+  var /** @type {ol.xml.NodeStackItem} */ context = {node: kml};
+  var properties = {};
+  if (features.length > 1) {
+    goog.object.set(properties, 'Document', features);
+  } else if (features.length == 1) {
+    goog.object.set(properties, 'Placemark', features[0]);
+  }
+  var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys);
+  return kml;
+};
+
+// FIXME add typedef for stack state objects
+goog.provide('ol.format.OSMXML');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.proj');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Feature format for reading data in the
+ * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML).
+ *
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @api stable
+ */
+ol.format.OSMXML = function() {
+  goog.base(this);
+
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+};
+goog.inherits(ol.format.OSMXML, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.OSMXML.EXTENSIONS_ = ['.osm'];
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.getExtensions = function() {
+  return ol.format.OSMXML.EXTENSIONS_;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readNode_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'node');
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var id = node.getAttribute('id');
+  var coordinates = /** @type {Array.<number>} */ ([
+    parseFloat(node.getAttribute('lon')),
+    parseFloat(node.getAttribute('lat'))
+  ]);
+  goog.object.set(state.nodes, id, coordinates);
+
+  var values = ol.xml.pushParseAndPop({
+    tags: {}
+  }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack);
+  if (!goog.object.isEmpty(values.tags)) {
+    var geometry = new ol.geom.Point(coordinates);
+    ol.format.Feature.transformWithOptions(geometry, false, options);
+    var feature = new ol.Feature(geometry);
+    feature.setId(id);
+    feature.setProperties(values.tags);
+    state.features.push(feature);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readWay_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'way');
+  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
+  var id = node.getAttribute('id');
+  var values = ol.xml.pushParseAndPop({
+    ndrefs: [],
+    tags: {}
+  }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack);
+  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  var flatCoordinates = /** @type {Array.<number>} */ ([]);
+  for (var i = 0, ii = values.ndrefs.length; i < ii; i++) {
+    var point = goog.object.get(state.nodes, values.ndrefs[i]);
+    goog.array.extend(flatCoordinates, point);
+  }
+  var geometry;
+  if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) {
+    // closed way
+    geometry = new ol.geom.Polygon(null);
+    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
+        [flatCoordinates.length]);
+  } else {
+    geometry = new ol.geom.LineString(null);
+    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+  }
+  ol.format.Feature.transformWithOptions(geometry, false, options);
+  var feature = new ol.Feature(geometry);
+  feature.setId(id);
+  feature.setProperties(values.tags);
+  state.features.push(feature);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+ol.format.OSMXML.readNd_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'nd');
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  values.ndrefs.push(node.getAttribute('ref'));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+ol.format.OSMXML.readTag_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'tag');
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  goog.object.set(values.tags, node.getAttribute('k'), node.getAttribute('v'));
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.OSMXML.NAMESPACE_URIS_ = [
+  null
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'nd': ol.format.OSMXML.readNd_,
+      'tag': ol.format.OSMXML.readTag_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OSMXML.PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'node': ol.format.OSMXML.readNode_,
+      'way': ol.format.OSMXML.readWay_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'tag': ol.format.OSMXML.readTag_
+    });
+
+
+/**
+ * Read all features from an OSM source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.format.OSMXML.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  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 (goog.isDef(state.features)) {
+      return state.features;
+    }
+  }
+  return [];
+};
+
+
+/**
+ * Read the projection from an OSM source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.OSMXML.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.readProjectionFromDocument = function(doc) {
+  return this.defaultDataProjection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.readProjectionFromNode = function(node) {
+  return this.defaultDataProjection;
+};
+
+goog.provide('ol.format.XLink');
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
+
+
+/**
+ * @param {Node} node Node.
+ * @return {boolean|undefined} Boolean.
+ */
+ol.format.XLink.readHref = function(node) {
+  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
+};
+
+goog.provide('ol.format.XML');
+
+goog.require('goog.asserts');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Generic format for reading non-feature XML data
+ *
+ * @constructor
+ */
+ol.format.XML = function() {
+};
+
+
+/**
+ * @param {Document|Node|string} source Source.
+ * @return {Object}
+ */
+ol.format.XML.prototype.read = function(source) {
+  if (ol.xml.isDocument(source)) {
+    return this.readFromDocument(/** @type {Document} */ (source));
+  } else if (ol.xml.isNode(source)) {
+    return this.readFromNode(/** @type {Node} */ (source));
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readFromDocument(doc);
+  } else {
+    goog.asserts.fail();
+    return null;
+  }
+};
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {Object}
+ */
+ol.format.XML.prototype.readFromDocument = goog.abstractMethod;
+
+
+/**
+ * @param {Node} node Node.
+ * @return {Object}
+ */
+ol.format.XML.prototype.readFromNode = goog.abstractMethod;
+
+goog.provide('ol.format.OWS');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
+goog.require('ol.xml');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.format.XML}
+ */
+ol.format.OWS = function() {
+  goog.base(this);
+};
+goog.inherits(ol.format.OWS, ol.format.XML);
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {Object} OWS object.
+ */
+ol.format.OWS.prototype.readFromDocument = function(doc) {
+  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
+  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      return this.readFromNode(n);
+    }
+  }
+  return null;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {Object} OWS object.
+ */
+ol.format.OWS.prototype.readFromNode = function(node) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  var owsObject = ol.xml.pushParseAndPop({},
+      ol.format.OWS.PARSERS_, node, []);
+  return goog.isDef(owsObject) ? owsObject : null;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readAddress_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Address');
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.ADDRESS_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readAllowedValues_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'AllowedValues');
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readConstraint_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Constraint');
+  var object = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(object));
+  var name = node.getAttribute('name');
+  var value = ol.xml.pushParseAndPop({},
+      ol.format.OWS.CONSTRAINT_PARSERS_, node,
+      objectStack);
+  if (!goog.isDef(value)) {
+    return undefined;
+  }
+  if (!goog.isDef(object.constraints)) {
+    object.constraints = {};
+  }
+  object.constraints[name] = value;
+
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readContactInfo_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ContactInfo');
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readDcp_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'DCP');
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.DCP_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readGet_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Get');
+  var object = objectStack[objectStack.length - 1];
+  var url = ol.format.XLink.readHref(node);
+  goog.asserts.assert(goog.isObject(object));
+  var value = ol.xml.pushParseAndPop({'url': url},
+      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
+  if (!goog.isDef(value)) {
+    return undefined;
+  }
+  var get = goog.object.get(object, 'get');
+  if (!goog.isDef(get)) {
+    goog.object.set(object, 'get', [value]);
+  }else {
+    goog.asserts.assert(goog.isArray(get));
+    get.push(value);
+  }
+
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readHttp_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'HTTP');
+  return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_,
+      node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readOperation_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Operation');
+  var name = node.getAttribute('name');
+  var value = ol.xml.pushParseAndPop({},
+      ol.format.OWS.OPERATION_PARSERS_, node, objectStack);
+  if (!goog.isDef(value)) {
+    return undefined;
+  }
+  var object = /** @type {Object} */
+      (objectStack[objectStack.length - 1]);
+  goog.asserts.assert(goog.isObject(object));
+  goog.object.set(object, name, value);
+
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readOperationsMetadata_ = function(node,
+    objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'OperationsMetadata');
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node,
+      objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readPhone_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Phone');
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.PHONE_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readServiceIdentification_ = function(node,
+    objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ServiceIdentification');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node,
+      objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readServiceContact_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ServiceContact');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node,
+      objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ServiceProvider');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node,
+      objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined}
+ */
+ol.format.OWS.readValue_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Value');
+  var object = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(object));
+  var key = ol.format.XSD.readString(node);
+  if (!goog.isDef(key)) {
+    return undefined;
+  }
+  goog.object.set(object, key, true);
+};
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.OWS.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/ows/1.1'
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'ServiceIdentification': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readServiceIdentification_,
+          'serviceIdentification'),
+      'ServiceProvider': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readServiceProvider_,
+          'serviceProvider'),
+      'OperationsMetadata': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readOperationsMetadata_,
+          'operationsMetadata')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'DeliveryPoint': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString, 'deliveryPoint'),
+      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
+          'city'),
+      'AdministrativeArea': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString, 'administrativeArea'),
+      'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
+          'postalCode'),
+      'Country': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString, 'country'),
+      'ElectronicMailAddress': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString, 'electronicMailAddress')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Value': ol.format.OWS.readValue_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'AllowedValues': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readAllowedValues_, 'allowedValues'
+      )
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Phone': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readPhone_, 'phone'),
+      'Address': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readAddress_, 'address')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.DCP_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'HTTP': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readHttp_, 'http')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Get': ol.format.OWS.readGet_,
+      'Post': undefined // TODO
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'DCP': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readDcp_, 'dcp')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Operation': ol.format.OWS.readOperation_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
+          'voice'),
+      'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
+          'facsimile')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Constraint': ol.format.OWS.readConstraint_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.SERVICE_CONTACT_PARSERS_ =
+    ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'IndividualName': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString, 'individualName'),
+      'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
+          'positionName'),
+      'ContactInfo': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readContactInfo_, 'contactInfo')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
+    ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
+          'title'),
+      'ServiceTypeVersion': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString, 'serviceTypeVersion'),
+      'ServiceType': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString, 'serviceType')
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.OWS.SERVICE_PROVIDER_PARSERS_ =
+    ol.xml.makeParsersNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
+          'providerName'),
+      'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref,
+          'providerSite'),
+      'ServiceContact': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readServiceContact_, 'serviceContact')
+    });
+
+goog.provide('ol.geom.flat.flip');
+
+goog.require('goog.asserts');
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @param {number=} opt_destOffset Destination offset.
+ * @return {Array.<number>} Flat coordinates.
+ */
+ol.geom.flat.flip.flipXY =
+    function(flatCoordinates, offset, end, stride, opt_dest, opt_destOffset) {
+  var dest, destOffset;
+  if (goog.isDef(opt_dest)) {
+    dest = opt_dest;
+    destOffset = goog.isDef(opt_destOffset) ? opt_destOffset : 0;
+  } else {
+    goog.asserts.assert(!goog.isDef(opt_destOffset));
+    dest = [];
+    destOffset = 0;
+  }
+  var j, k;
+  for (j = offset; j < end; ) {
+    var x = flatCoordinates[j++];
+    dest[destOffset++] = flatCoordinates[j++];
+    dest[destOffset++] = x;
+    for (k = 2; k < stride; ++k) {
+      dest[destOffset++] = flatCoordinates[j++];
     }
   }
-
-  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
-  this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
-      projection, extent, z, tileLayer.getPreload());
-  this.scheduleExpireCache(frameState, tileSource);
-  this.updateLogos(frameState, tileSource);
-
-  ol.vec.Mat4.makeTransform2D(this.imageTransform_,
-      pixelRatio * frameState.size[0] / 2,
-      pixelRatio * frameState.size[1] / 2,
-      pixelRatio * tilePixelResolution / viewState.resolution,
-      pixelRatio * tilePixelResolution / viewState.resolution,
-      viewState.rotation,
-      (origin[0] - center[0]) / tilePixelResolution,
-      (center[1] - origin[1]) / tilePixelResolution);
-
-  return true;
+  dest.length = destOffset;
+  return dest;
 };
 
-goog.provide('ol.renderer.canvas.VectorLayer');
+goog.provide('ol.format.Polyline');
 
-goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('ol.ViewHint');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.EventType');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.renderer.vector');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.TextFeature');
+goog.require('ol.geom.LineString');
+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.renderer.canvas.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Vector} vectorLayer Vector layer.
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.PolylineOptions=} opt_options
+ *     Optional configuration object.
+ * @api stable
  */
-ol.renderer.canvas.VectorLayer = function(mapRenderer, vectorLayer) {
+ol.format.Polyline = function(opt_options) {
 
-  goog.base(this, mapRenderer, vectorLayer);
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.dirty_ = false;
+  goog.base(this);
 
   /**
-   * @private
-   * @type {number}
+   * @inheritDoc
    */
-  this.renderedRevision_ = -1;
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
 
   /**
    * @private
    * @type {number}
    */
-  this.renderedResolution_ = NaN;
+  this.factor_ = goog.isDef(options.factor) ? options.factor : 1e5;
+};
+goog.inherits(ol.format.Polyline, ol.format.TextFeature);
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.renderedExtent_ = ol.extent.createEmpty();
 
-  /**
-   * @private
-   * @type {function(ol.Feature, ol.Feature): number|null}
-   */
-  this.renderedRenderOrder_ = null;
+/**
+ * Encode a list of n-dimensional points and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * @param {Array.<number>} numbers A list of n-dimensional points.
+ * @param {number} stride The number of dimension of the points in the list.
+ * @param {number=} opt_factor The factor by which the numbers will be
+ *     multiplied. The remaining decimal places will get rounded away.
+ *     Default is `1e5`.
+ * @return {string} The encoded string.
+ * @api
+ */
+ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) {
+  var factor = goog.isDef(opt_factor) ? opt_factor : 1e5;
+  var d;
 
-  /**
-   * @private
-   * @type {ol.render.canvas.ReplayGroup}
-   */
-  this.replayGroup_ = null;
+  var lastNumbers = new Array(stride);
+  for (d = 0; d < stride; ++d) {
+    lastNumbers[d] = 0;
+  }
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D();
+  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);
 };
-goog.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
 
 
 /**
- * @inheritDoc
+ * Decode a list of n-dimensional points from an encoded string
+ *
+ * @param {string} encoded An encoded string.
+ * @param {number} stride The number of dimension of the points in the
+ *     encoded string.
+ * @param {number=} opt_factor The factor by which the resulting numbers will
+ *     be divided. Default is `1e5`.
+ * @return {Array.<number>} A list of n-dimensional points.
+ * @api
  */
-ol.renderer.canvas.VectorLayer.prototype.composeFrame =
-    function(frameState, layerState, context) {
+ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) {
+  var factor = goog.isDef(opt_factor) ? opt_factor : 1e5;
+  var d;
 
-  var transform = this.getTransform(frameState);
+  /** @type {Array.<number>} */
+  var lastNumbers = new Array(stride);
+  for (d = 0; d < stride; ++d) {
+    lastNumbers[d] = 0;
+  }
 
-  this.dispatchPreComposeEvent(context, frameState, transform);
+  var numbers = ol.format.Polyline.decodeFloats(encoded, factor);
 
-  var replayGroup = this.replayGroup_;
-  if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) {
-    var layer = this.getLayer();
-    var replayContext;
-    if (layer.hasListener(ol.render.EventType.RENDER)) {
-      // resize and clear
-      this.context_.canvas.width = context.canvas.width;
-      this.context_.canvas.height = context.canvas.height;
-      replayContext = this.context_;
-    } else {
-      replayContext = context;
-    }
-    // for performance reasons, context.save / context.restore is not used
-    // to save and restore the transformation matrix and the opacity.
-    // see http://jsperf.com/context-save-restore-versus-variable
-    var alpha = replayContext.globalAlpha;
-    replayContext.globalAlpha = layerState.opacity;
-    replayGroup.replay(
-        replayContext, frameState.extent, frameState.pixelRatio, transform,
-        frameState.viewState.rotation, frameState.skippedFeatureUids);
+  var i, ii;
+  for (i = 0, ii = numbers.length; i < ii;) {
+    for (d = 0; d < stride; ++d, ++i) {
+      lastNumbers[d] += numbers[i];
 
-    if (replayContext != context) {
-      this.dispatchRenderEvent(replayContext, frameState, transform);
-      context.drawImage(replayContext.canvas, 0, 0);
+      numbers[i] = lastNumbers[d];
     }
-    replayContext.globalAlpha = alpha;
   }
 
-  this.dispatchPostComposeEvent(context, frameState, transform);
-
+  return numbers;
 };
 
 
 /**
- * @inheritDoc
+ * 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.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel =
-    function(coordinate, frameState, callback, thisArg) {
-  if (goog.isNull(this.replayGroup_)) {
-    return undefined;
-  } else {
-    var extent = frameState.extent;
-    var resolution = frameState.viewState.resolution;
-    var rotation = frameState.viewState.rotation;
-    var layer = this.getLayer();
-    /** @type {Object.<string, boolean>} */
-    var features = {};
-    return this.replayGroup_.forEachGeometryAtPixel(extent, resolution,
-        rotation, coordinate, frameState.skippedFeatureUids,
-        /**
-         * @param {ol.geom.Geometry} geometry Geometry.
-         * @param {Object} data Data.
-         * @return {?} Callback result.
-         */
-        function(geometry, data) {
-          var feature = /** @type {ol.Feature} */ (data);
-          goog.asserts.assert(goog.isDef(feature));
-          var key = goog.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback.call(thisArg, feature, layer);
-          }
-        });
+ol.format.Polyline.encodeFloats = function(numbers, opt_factor) {
+  var factor = goog.isDef(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);
 };
 
 
 /**
- * Handle changes in image style state.
- * @param {goog.events.Event} event Image style change event.
- * @private
+ * 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.renderer.canvas.VectorLayer.prototype.handleImageChange_ =
-    function(event) {
-  this.renderIfReadyAndVisible();
+ol.format.Polyline.decodeFloats = function(encoded, opt_factor) {
+  var factor = goog.isDef(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;
 };
 
 
 /**
- * @inheritDoc
+ * 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.renderer.canvas.VectorLayer.prototype.prepareFrame =
-    function(frameState, layerState) {
+ol.format.Polyline.encodeSignedIntegers = function(numbers) {
+  var i, ii;
+  for (i = 0, ii = numbers.length; i < ii; ++i) {
+    var num = numbers[i];
+    numbers[i] = (num < 0) ? ~(num << 1) : (num << 1);
+  }
+  return ol.format.Polyline.encodeUnsignedIntegers(numbers);
+};
 
-  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
-  goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
-  var vectorSource = vectorLayer.getSource();
 
-  this.updateAttributions(
-      frameState.attributions, vectorSource.getAttributions());
-  this.updateLogos(frameState, vectorSource);
+/**
+ * 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;
+};
 
-  if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
-      frameState.viewHints[ol.ViewHint.INTERACTING])) {
-    return true;
+
+/**
+ * Encode a list of unsigned integers and return an encoded string
+ *
+ * @param {Array.<number>} numbers A list of unsigned integers.
+ * @return {string} The encoded string.
+ */
+ol.format.Polyline.encodeUnsignedIntegers = function(numbers) {
+  var encoded = '';
+  var i, ii;
+  for (i = 0, ii = numbers.length; i < ii; ++i) {
+    encoded += ol.format.Polyline.encodeUnsignedInteger(numbers[i]);
   }
+  return encoded;
+};
 
-  var frameStateExtent = frameState.extent;
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
-  var resolution = viewState.resolution;
-  var pixelRatio = frameState.pixelRatio;
-  var vectorLayerRevision = vectorLayer.getRevision();
-  var vectorLayerRenderOrder = vectorLayer.getRenderOrder();
-  if (!goog.isDef(vectorLayerRenderOrder)) {
-    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
+
+/**
+ * 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;
+};
 
-  if (!this.dirty_ &&
-      this.renderedResolution_ == resolution &&
-      this.renderedRevision_ == vectorLayerRevision &&
-      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
-      ol.extent.containsExtent(this.renderedExtent_, frameStateExtent)) {
-    return true;
+
+/**
+ * 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;
+};
 
-  var extent = this.renderedExtent_;
-  var xBuffer = ol.extent.getWidth(frameStateExtent) / 4;
-  var yBuffer = ol.extent.getHeight(frameStateExtent) / 4;
-  extent[0] = frameStateExtent[0] - xBuffer;
-  extent[1] = frameStateExtent[1] - yBuffer;
-  extent[2] = frameStateExtent[2] + xBuffer;
-  extent[3] = frameStateExtent[3] + yBuffer;
 
-  // FIXME dispose of old replayGroup in post render
-  goog.dispose(this.replayGroup_);
-  this.replayGroup_ = null;
+/**
+ * Read the feature from the Polyline source. The coordinates are assumed to be
+ * in two dimensions and in latitude, longitude order.
+ *
+ * @function
+ * @param {ArrayBuffer|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;
 
-  this.dirty_ = false;
 
-  var replayGroup =
-      new ol.render.canvas.ReplayGroup(
-          ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
-          resolution);
-  vectorSource.loadFeatures(extent, resolution, projection);
-  var renderFeature =
-      /**
-       * @param {ol.Feature} feature Feature.
-       * @this {ol.renderer.canvas.VectorLayer}
-       */
-      function(feature) {
-    var styles;
-    if (goog.isDef(feature.getStyleFunction())) {
-      styles = feature.getStyleFunction().call(feature, resolution);
-    } else if (goog.isDef(vectorLayer.getStyleFunction())) {
-      styles = vectorLayer.getStyleFunction()(feature, resolution);
-    }
-    if (goog.isDefAndNotNull(styles)) {
-      var dirty = this.renderFeature(
-          feature, resolution, pixelRatio, styles, replayGroup);
-      this.dirty_ = this.dirty_ || dirty;
-    }
-  };
-  if (!goog.isNull(vectorLayerRenderOrder)) {
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    vectorSource.forEachFeatureInExtentAtResolution(extent, resolution,
-        /**
-         * @param {ol.Feature} feature Feature.
-         */
-        function(feature) {
-          features.push(feature);
-        }, this);
-    goog.array.sort(features, vectorLayerRenderOrder);
-    goog.array.forEach(features, renderFeature, this);
+/**
+ * @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 {ArrayBuffer|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 {ArrayBuffer|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 flatCoordinates = ol.format.Polyline.decodeDeltas(text, 2, this.factor_);
+  ol.geom.flat.flip.flipXY(
+      flatCoordinates, 0, flatCoordinates.length, 2, flatCoordinates);
+  var coordinates = ol.geom.flat.inflate.coordinates(
+      flatCoordinates, 0, flatCoordinates.length, 2);
+
+  return /** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(
+          new ol.geom.LineString(coordinates), false,
+          this.adaptOptions(opt_options)));
+};
+
+
+/**
+ * Read the projection from a Polyline source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.Polyline.prototype.readProjection;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.readProjectionFromText = function(text) {
+  return this.defaultDataProjection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) {
+  var geometry = feature.getGeometry();
+  if (goog.isDefAndNotNull(geometry)) {
+    return this.writeGeometryText(geometry, opt_options);
   } else {
-    vectorSource.forEachFeatureInExtentAtResolution(
-        extent, resolution, renderFeature, this);
+    goog.asserts.fail();
+    return '';
   }
-  replayGroup.finish();
+};
 
-  this.renderedResolution_ = resolution;
-  this.renderedRevision_ = vectorLayerRevision;
-  this.renderedRenderOrder_ = vectorLayerRenderOrder;
-  this.replayGroup_ = replayGroup;
 
-  return true;
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.writeFeaturesText =
+    function(features, opt_options) {
+  goog.asserts.assert(features.length == 1);
+  return this.writeFeatureText(features[0], opt_options);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {Array.<ol.style.Style>} styles Array of styles
- * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
- * @return {boolean} `true` if an image is loading.
+ * 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.renderer.canvas.VectorLayer.prototype.renderFeature =
-    function(feature, resolution, pixelRatio, styles, replayGroup) {
-  if (!goog.isDefAndNotNull(styles)) {
-    return false;
-  }
-  var i, ii, loading = false;
-  for (i = 0, ii = styles.length; i < ii; ++i) {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles[i],
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        feature, this.handleImageChange_, this) || loading;
-  }
-  return loading;
+ol.format.Polyline.prototype.writeGeometryText =
+    function(geometry, opt_options) {
+  goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+  geometry = /** @type {ol.geom.LineString} */
+      (ol.format.Feature.transformWithOptions(
+          geometry, true, this.adaptOptions(opt_options)));
+  var flatCoordinates = geometry.getFlatCoordinates();
+  var stride = geometry.getStride();
+  ol.geom.flat.flip.flipXY(
+      flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates);
+  return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_);
 };
 
-// FIXME offset panning
-
-goog.provide('ol.renderer.canvas.Map');
+goog.provide('ol.format.TopoJSON');
 
 goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.style');
-goog.require('goog.vec.Mat4');
-goog.require('ol');
-goog.require('ol.RendererType');
-goog.require('ol.css');
-goog.require('ol.dom');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Layer');
-goog.require('ol.layer.Tile');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.Event');
-goog.require('ol.render.EventType');
-goog.require('ol.render.canvas.Immediate');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.canvas.ImageLayer');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.renderer.canvas.TileLayer');
-goog.require('ol.renderer.canvas.VectorLayer');
-goog.require('ol.renderer.vector');
-goog.require('ol.source.State');
-goog.require('ol.vec.Mat4');
+goog.require('goog.object');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.JSONFeature');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.proj');
 
 
 
 /**
+ * @classdesc
+ * Feature format for reading and writing data in the TopoJSON format.
+ *
  * @constructor
- * @extends {ol.renderer.Map}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
+ * @extends {ol.format.JSONFeature}
+ * @param {olx.format.TopoJSONOptions=} opt_options Options.
+ * @api stable
  */
-ol.renderer.canvas.Map = function(container, map) {
+ol.format.TopoJSON = function(opt_options) {
 
-  goog.base(this, container, map);
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D();
+  goog.base(this);
 
   /**
-   * @private
-   * @type {HTMLCanvasElement}
+   * @inheritDoc
    */
-  this.canvas_ = this.context_.canvas;
+  this.defaultDataProjection = ol.proj.get(
+      goog.isDefAndNotNull(options.defaultDataProjection) ?
+          options.defaultDataProjection : 'EPSG:4326');
 
-  this.canvas_.style.width = '100%';
-  this.canvas_.style.height = '100%';
-  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
-  goog.dom.insertChildAt(container, this.canvas_, 0);
+};
+goog.inherits(ol.format.TopoJSON, ol.format.JSONFeature);
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
 
-  /**
-   * @private
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.transform_ = goog.vec.Mat4.createNumber();
+/**
+ * @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;
 };
-goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
 
 
 /**
- * @inheritDoc
+ * 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.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) {
-  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
-    return new ol.renderer.canvas.ImageLayer(this, layer);
-  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
-    return new ol.renderer.canvas.TileLayer(this, layer);
-  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
-    return new ol.renderer.canvas.VectorLayer(this, layer);
-  } else {
-    goog.asserts.fail();
-    return null;
+ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) {
+  var coordinates = object.coordinates;
+  if (!goog.isNull(scale) && !goog.isNull(translate)) {
+    ol.format.TopoJSON.transformVertex_(coordinates, scale, translate);
   }
+  return new ol.geom.Point(coordinates);
 };
 
 
 /**
- * @param {ol.render.EventType} type Event type.
- * @param {olx.FrameState} frameState Frame state.
+ * 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.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 resolution = viewState.resolution;
-    var rotation = viewState.rotation;
-    ol.vec.Mat4.makeTransform2D(this.transform_,
-        this.canvas_.width / 2,
-        this.canvas_.height / 2,
-        pixelRatio / viewState.resolution,
-        -pixelRatio / viewState.resolution,
-        -viewState.rotation,
-        -viewState.center[0], -viewState.center[1]);
-    var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
-        extent, this.transform_, rotation);
-    var replayGroup = new ol.render.canvas.ReplayGroup(
-        ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
-        resolution);
-    var composeEvent = new ol.render.Event(type, map, vectorContext,
-        replayGroup, frameState, context, null);
-    map.dispatchEvent(composeEvent);
-    replayGroup.finish();
-    if (!replayGroup.isEmpty()) {
-      replayGroup.replay(context, extent, pixelRatio, this.transform_,
-          rotation, {});
+ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale,
+    translate) {
+  var coordinates = object.coordinates;
+  var i, ii;
+  if (!goog.isNull(scale) && !goog.isNull(translate)) {
+    for (i = 0, ii = coordinates.length; i < ii; ++i) {
+      ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate);
     }
-    vectorContext.flush();
-    this.replayGroup = replayGroup;
   }
+  return new ol.geom.MultiPoint(coordinates);
 };
 
 
 /**
- * @param {ol.layer.Layer} layer Layer.
- * @return {ol.renderer.canvas.Layer} Canvas layer renderer.
+ * 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.renderer.canvas.Map.prototype.getCanvasLayerRenderer = function(layer) {
-  var layerRenderer = this.getLayerRenderer(layer);
-  goog.asserts.assertInstanceof(layerRenderer, ol.renderer.canvas.Layer);
-  return /** @type {ol.renderer.canvas.Layer} */ (layerRenderer);
+ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
+  var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
+  return new ol.geom.LineString(coordinates);
 };
 
 
 /**
- * @inheritDoc
+ * 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.renderer.canvas.Map.prototype.getType = function() {
-  return ol.RendererType.CANVAS;
+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);
 };
 
 
 /**
- * @inheritDoc
+ * 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.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
-
-  if (goog.isNull(frameState)) {
-    if (this.renderedVisible_) {
-      goog.style.setElementShown(this.canvas_, false);
-      this.renderedVisible_ = false;
-    }
-    return;
-  }
-
-  var context = this.context_;
-  var width = frameState.size[0] * frameState.pixelRatio;
-  var height = frameState.size[1] * frameState.pixelRatio;
-  if (this.canvas_.width != width || this.canvas_.height != height) {
-    this.canvas_.width = width;
-    this.canvas_.height = height;
-  } else {
-    context.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
+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);
+};
 
-  this.calculateMatrices2D(frameState);
-
-  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
 
-  var layerStatesArray = frameState.layerStatesArray;
-  var viewResolution = frameState.viewState.resolution;
-  var i, ii, layer, layerRenderer, layerState;
-  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-    layerState = layerStatesArray[i];
-    layer = layerState.layer;
-    layerRenderer = this.getLayerRenderer(layer);
-    goog.asserts.assertInstanceof(layerRenderer, ol.renderer.canvas.Layer);
-    if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) ||
-        layerState.sourceState != ol.source.State.READY) {
-      continue;
-    }
-    if (layerRenderer.prepareFrame(frameState, layerState)) {
-      layerRenderer.composeFrame(frameState, layerState, context);
+/**
+ * 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;
   }
-
-  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
-
-  if (!this.renderedVisible_) {
-    goog.style.setElementShown(this.canvas_, true);
-    this.renderedVisible_ = true;
-  }
-
-  this.scheduleRemoveUnusedLayerRenderers(frameState);
-  this.scheduleExpireIconCache(frameState);
+  return new ol.geom.MultiPolygon(coordinates);
 };
 
-goog.provide('ol.renderer.dom.Layer');
-
-goog.require('goog.dom');
-goog.require('ol.layer.Layer');
-goog.require('ol.renderer.Layer');
-
-
 
 /**
- * @constructor
- * @extends {ol.renderer.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Layer} layer Layer.
- * @param {!Element} target Target.
+ * @inheritDoc
  */
-ol.renderer.dom.Layer = function(mapRenderer, layer, target) {
-
-  goog.base(this, mapRenderer, layer);
-
-  /**
-   * @type {!Element}
-   * @protected
-   */
-  this.target = target;
-
+ol.format.TopoJSON.prototype.getExtensions = function() {
+  return ol.format.TopoJSON.EXTENSIONS_;
 };
-goog.inherits(ol.renderer.dom.Layer, ol.renderer.Layer);
 
 
 /**
- * @return {!Element} Target.
+ * 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.renderer.dom.Layer.prototype.getTarget = function() {
-  return this.target;
+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;
 };
 
-goog.provide('ol.renderer.dom.ImageLayer');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.vec.Mat4');
-goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
-goog.require('ol.ViewHint');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.layer.Image');
-goog.require('ol.renderer.dom.Layer');
-goog.require('ol.vec.Mat4');
 
+/**
+ * Create a feature from a TopoJSON geometry object.
+ *
+ * @param {TopoJSONGeometry} object TopoJSON geometry object.
+ * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
+ * @param {Array.<number>} scale Scale for each dimension.
+ * @param {Array.<number>} translate Translation for each dimension.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @private
+ */
+ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs,
+    scale, translate, opt_options) {
+  var geometry;
+  var type = object.type;
+  var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type];
+  goog.asserts.assert(goog.isDef(geometryReader));
+  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 (goog.isDef(object.id)) {
+    feature.setId(object.id);
+  }
+  if (goog.isDef(object.properties)) {
+    feature.setProperties(object.properties);
+  }
+  return feature;
+};
 
 
 /**
- * @constructor
- * @extends {ol.renderer.dom.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Image} imageLayer Image layer.
+ * Read all features from a TopoJSON source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
  */
-ol.renderer.dom.ImageLayer = function(mapRenderer, imageLayer) {
-  var target = goog.dom.createElement(goog.dom.TagName.DIV);
-  target.style.position = 'absolute';
+ol.format.TopoJSON.prototype.readFeatures;
 
-  goog.base(this, mapRenderer, imageLayer, target);
 
-  /**
-   * The last rendered image.
-   * @private
-   * @type {?ol.ImageBase}
-   */
-  this.image_ = null;
+/**
+ * @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 (goog.isDef(topoJSONTopology.transform)) {
+      transform = /** @type {TopoJSONTransform} */
+          (topoJSONTopology.transform);
+      scale = transform.scale;
+      translate = transform.translate;
+    }
+    var arcs = topoJSONTopology.arcs;
+    if (goog.isDef(transform)) {
+      ol.format.TopoJSON.transformArcs_(arcs, scale, translate);
+    }
+    /** @type {Array.<ol.Feature>} */
+    var features = [];
+    var topoJSONFeatures = goog.object.getValues(topoJSONTopology.objects);
+    var i, ii;
+    var feature;
+    for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) {
+      if (topoJSONFeatures[i].type === 'GeometryCollection') {
+        feature = /** @type {TopoJSONGeometryCollection} */
+            (topoJSONFeatures[i]);
+        features.push.apply(features,
+            ol.format.TopoJSON.readFeaturesFromGeometryCollection_(
+                feature, arcs, scale, translate, opt_options));
+      } else {
+        feature = /** @type {TopoJSONGeometry} */
+            (topoJSONFeatures[i]);
+        features.push(ol.format.TopoJSON.readFeatureFromGeometry_(
+            feature, arcs, scale, translate, opt_options));
+      }
+    }
+    return features;
+  } else {
+    return [];
+  }
+};
 
-  /**
-   * @private
-   * @type {goog.vec.Mat4.Number}
-   */
-  this.transform_ = goog.vec.Mat4.createNumberIdentity();
 
+/**
+ * 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);
+  }
 };
-goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer);
 
 
 /**
- * @inheritDoc
+ * 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.renderer.dom.ImageLayer.prototype.forEachFeatureAtPixel =
-    function(coordinate, frameState, callback, thisArg) {
-  var layer = this.getLayer();
-  var source = layer.getSource();
-  var extent = frameState.extent;
-  var resolution = frameState.viewState.resolution;
-  var rotation = frameState.viewState.rotation;
-  var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtPixel(
-      extent, resolution, rotation, coordinate, skippedFeatureUids,
-      /**
-       * @param {ol.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        return callback.call(thisArg, feature, layer);
-      });
+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);
+  }
 };
 
 
 /**
- * @inheritDoc
+ * 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.renderer.dom.ImageLayer.prototype.prepareFrame =
-    function(frameState, layerState) {
-
-  var viewState = frameState.viewState;
-  var viewCenter = viewState.center;
-  var viewResolution = viewState.resolution;
-  var viewRotation = viewState.rotation;
-
-  var image = this.image_;
-  var imageLayer = this.getLayer();
-  goog.asserts.assertInstanceof(imageLayer, ol.layer.Image);
-  var imageSource = imageLayer.getSource();
-
-  var hints = frameState.viewHints;
-
-  var renderedExtent = frameState.extent;
-  if (goog.isDef(layerState.extent)) {
-    renderedExtent = ol.extent.getIntersection(
-        renderedExtent, layerState.extent);
-  }
-
-  if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
-      !ol.extent.isEmpty(renderedExtent)) {
-    var image_ = imageSource.getImage(renderedExtent, viewResolution,
-        frameState.pixelRatio, viewState.projection);
-    if (!goog.isNull(image_)) {
-      var imageState = image_.getState();
-      if (imageState == ol.ImageState.IDLE) {
-        goog.events.listenOnce(image_, goog.events.EventType.CHANGE,
-            this.handleImageChange, false, this);
-        image_.load();
-      } else if (imageState == ol.ImageState.LOADED) {
-        image = image_;
-      }
-    }
-  }
+ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
+  vertex[0] = vertex[0] * scale[0] + translate[0];
+  vertex[1] = vertex[1] * scale[1] + translate[1];
+};
 
-  if (!goog.isNull(image)) {
-    var imageExtent = image.getExtent();
-    var imageResolution = image.getResolution();
-    var transform = goog.vec.Mat4.createNumber();
-    ol.vec.Mat4.makeTransform2D(transform,
-        frameState.size[0] / 2, frameState.size[1] / 2,
-        imageResolution / viewResolution, imageResolution / viewResolution,
-        viewRotation,
-        (imageExtent[0] - viewCenter[0]) / imageResolution,
-        (viewCenter[1] - imageExtent[3]) / imageResolution);
-    if (image != this.image_) {
-      var imageElement = image.getImageElement(this);
-      // Bootstrap sets the style max-width: 100% for all images, which breaks
-      // prevents the image from being displayed in FireFox.  Workaround by
-      // overriding the max-width style.
-      imageElement.style.maxWidth = 'none';
-      imageElement.style.position = 'absolute';
-      goog.dom.removeChildren(this.target);
-      goog.dom.appendChild(this.target, imageElement);
-      this.image_ = image;
-    }
-    this.setTransform_(transform);
-    this.updateAttributions(frameState.attributions, image.getAttributions());
-    this.updateLogos(frameState, imageSource);
-  }
 
-  return true;
+/**
+ * Read the projection from a TopoJSON source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} object Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.TopoJSON.prototype.readProjection = function(object) {
+  return this.defaultDataProjection;
 };
 
 
 /**
- * @param {goog.vec.Mat4.Number} transform Transform.
+ * @const
  * @private
+ * @type {Object.<string, function(TopoJSONGeometry, Array, ...[Array]): ol.geom.Geometry>}
  */
-ol.renderer.dom.ImageLayer.prototype.setTransform_ = function(transform) {
-  if (!ol.vec.Mat4.equals2D(transform, this.transform_)) {
-    ol.dom.transformElement2D(this.target, transform, 6);
-    goog.vec.Mat4.setFromArray(this.transform_, transform);
-  }
+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_
 };
 
-// FIXME probably need to reset TileLayerZ if offsets get too large
-// FIXME when zooming out, preserve higher Z divs to avoid white flash
-
-goog.provide('ol.renderer.dom.TileLayer');
+goog.provide('ol.format.WFS');
 
-goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
+goog.require('goog.dom.NodeType');
 goog.require('goog.object');
-goog.require('goog.style');
-goog.require('goog.vec.Mat4');
-goog.require('ol');
-goog.require('ol.Coordinate');
-goog.require('ol.TileCoord');
-goog.require('ol.TileRange');
-goog.require('ol.TileState');
-goog.require('ol.ViewHint');
-goog.require('ol.dom');
-goog.require('ol.dom.BrowserFeature');
-goog.require('ol.extent');
-goog.require('ol.layer.Tile');
-goog.require('ol.renderer.dom.Layer');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.TileGrid');
-goog.require('ol.vec.Mat4');
+goog.require('ol.format.GML3');
+goog.require('ol.format.GMLBase');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.format.XSD');
+goog.require('ol.geom.Geometry');
+goog.require('ol.proj');
+goog.require('ol.xml');
 
 
 
 /**
+ * @classdesc
+ * Feature format for reading and writing data in the WFS format.
+ * By default, supports WFS version 1.1.0. You can pass a GML format
+ * as option if you want to read a WFS that contains GML2 (WFS 1.0.0).
+ * Also see {@link ol.format.GMLBase} which is used by this format.
+ *
  * @constructor
- * @extends {ol.renderer.dom.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Tile} tileLayer Tile layer.
+ * @param {olx.format.WFSOptions=} opt_options
+ *     Optional configuration object.
+ * @extends {ol.format.XMLFeature}
+ * @api stable
  */
-ol.renderer.dom.TileLayer = function(mapRenderer, tileLayer) {
-
-  var target = goog.dom.createElement(goog.dom.TagName.DIV);
-  target.style.position = 'absolute';
-
-  // Needed for IE7-8 to render a transformed element correctly
-  if (ol.dom.BrowserFeature.USE_MS_MATRIX_TRANSFORM) {
-    target.style.width = '100%';
-    target.style.height = '100%';
-  }
-
-  goog.base(this, mapRenderer, tileLayer, target);
+ol.format.WFS = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
   /**
    * @private
-   * @type {boolean}
+   * @type {string}
    */
-  this.renderedVisible_ = true;
+  this.featureType_ = options.featureType;
 
   /**
    * @private
-   * @type {number}
+   * @type {string}
    */
-  this.renderedOpacity_ = 1;
+  this.featureNS_ = options.featureNS;
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.format.GMLBase}
    */
-  this.renderedRevision_ = 0;
+  this.gmlFormat_ = goog.isDef(options.gmlFormat) ?
+      options.gmlFormat : new ol.format.GML3();
 
   /**
    * @private
-   * @type {Object.<number, ol.renderer.dom.TileLayerZ_>}
+   * @type {string}
    */
-  this.tileLayerZs_ = {};
+  this.schemaLocation_ = goog.isDef(options.schemaLocation) ?
+      options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION;
 
+  goog.base(this);
 };
-goog.inherits(ol.renderer.dom.TileLayer, ol.renderer.dom.Layer);
+goog.inherits(ol.format.WFS, ol.format.XMLFeature);
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {string}
  */
-ol.renderer.dom.TileLayer.prototype.prepareFrame =
-    function(frameState, layerState) {
-
-  if (!layerState.visible) {
-    if (this.renderedVisible_) {
-      goog.style.setElementShown(this.target, false);
-      this.renderedVisible_ = false;
-    }
-    return true;
-  }
+ol.format.WFS.FEATURE_PREFIX = 'feature';
 
-  var pixelRatio = frameState.pixelRatio;
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
 
-  var tileLayer = this.getLayer();
-  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
-  var tileSource = tileLayer.getSource();
-  var tileGrid = tileSource.getTileGridForProjection(projection);
-  var tileGutter = tileSource.getGutter();
-  var z = tileGrid.getZForResolution(viewState.resolution);
-  var tileResolution = tileGrid.getResolution(z);
-  var center = viewState.center;
-  var extent;
-  if (tileResolution == viewState.resolution) {
-    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
-    extent = ol.extent.getForViewAndSize(
-        center, tileResolution, viewState.rotation, frameState.size);
-  } else {
-    extent = frameState.extent;
-  }
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';
 
-  if (goog.isDef(layerState.extent)) {
-    extent = ol.extent.getIntersection(extent, layerState.extent);
-  }
 
-  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
-      extent, tileResolution);
+/**
+ * Number of features; bounds/extent.
+ * @typedef {{numberOfFeatures: number,
+ *            bounds: ol.Extent}}
+ * @api stable
+ */
+ol.format.WFS.FeatureCollectionMetadata;
 
-  /** @type {Object.<number, Object.<string, ol.Tile>>} */
-  var tilesToDrawByZ = {};
-  tilesToDrawByZ[z] = {};
 
-  var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
-    return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED;
-  }, tileSource, pixelRatio, projection);
-  var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
-      tilesToDrawByZ, getTileIfLoaded);
+/**
+ * Total deleted; total inserted; total updated; array of insert ids.
+ * @typedef {{totalDeleted: number,
+ *            totalInserted: number,
+ *            totalUpdated: number,
+ *            insertIds: Array.<string>}}
+ * @api stable
+ */
+ol.format.WFS.TransactionResponse;
 
-  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-  if (!goog.isDef(useInterimTilesOnError)) {
-    useInterimTilesOnError = true;
-  }
 
-  var tmpExtent = ol.extent.createEmpty();
-  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-  var childTileRange, fullyLoaded, tile, tileState, x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' +
+    'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd';
 
-      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-      tileState = tile.getState();
-      if (tileState == ol.TileState.LOADED) {
-        tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
-        continue;
-      } else if (tileState == ol.TileState.EMPTY ||
-                 (tileState == ol.TileState.ERROR &&
-                  !useInterimTilesOnError)) {
-        continue;
-      }
 
-      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
-          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
-      if (!fullyLoaded) {
-        childTileRange = tileGrid.getTileCoordChildTileRange(
-            tile.tileCoord, tmpTileRange, tmpExtent);
-        if (!goog.isNull(childTileRange)) {
-          findLoadedTiles(z + 1, childTileRange);
-        }
-      }
+/**
+ * Read all features from a WFS FeatureCollection.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
+ */
+ol.format.WFS.prototype.readFeatures;
 
-    }
 
+/**
+ * @inheritDoc
+ */
+ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) {
+  var context = {
+    'featureType': this.featureType_,
+    'featureNS': this.featureNS_
+  };
+  goog.object.extend(context, this.getReadOptions(node,
+      goog.isDef(opt_options) ? opt_options : {}));
+  var objectStack = [context];
+  var features = ol.xml.pushParseAndPop([],
+      this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
+      objectStack, this.gmlFormat_);
+  if (!goog.isDef(features)) {
+    features = [];
   }
+  return features;
+};
 
-  // If the tile source revision changes, we destroy the existing DOM structure
-  // so that a new one will be created.  It would be more efficient to modify
-  // the existing structure.
-  var tileLayerZ, tileLayerZKey;
-  if (this.renderedRevision_ != tileSource.getRevision()) {
-    for (tileLayerZKey in this.tileLayerZs_) {
-      tileLayerZ = this.tileLayerZs_[+tileLayerZKey];
-      goog.dom.removeNode(tileLayerZ.target);
-    }
-    this.tileLayerZs_ = {};
-    this.renderedRevision_ = tileSource.getRevision();
+
+/**
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ * @api stable
+ */
+ol.format.WFS.prototype.readTransactionResponse = function(source) {
+  if (ol.xml.isDocument(source)) {
+    return this.readTransactionResponseFromDocument(
+        /** @type {Document} */ (source));
+  } else if (ol.xml.isNode(source)) {
+    return this.readTransactionResponseFromNode(/** @type {Node} */ (source));
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readTransactionResponseFromDocument(doc);
+  } else {
+    goog.asserts.fail();
+    return undefined;
   }
+};
 
-  /** @type {Array.<number>} */
-  var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
-  goog.array.sort(zs);
 
-  /** @type {Object.<number, boolean>} */
-  var newTileLayerZKeys = {};
+/**
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
+ * @api stable
+ */
+ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) {
+  if (ol.xml.isDocument(source)) {
+    return this.readFeatureCollectionMetadataFromDocument(
+        /** @type {Document} */ (source));
+  } else if (ol.xml.isNode(source)) {
+    return this.readFeatureCollectionMetadataFromNode(
+        /** @type {Node} */ (source));
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    return this.readFeatureCollectionMetadataFromDocument(doc);
+  } else {
+    goog.asserts.fail();
+    return undefined;
+  }
+};
 
-  var iz, iziz, tileCoordKey, tileCoordOrigin, tilesToDraw;
-  for (iz = 0, iziz = zs.length; iz < iziz; ++iz) {
-    tileLayerZKey = zs[iz];
-    if (tileLayerZKey in this.tileLayerZs_) {
-      tileLayerZ = this.tileLayerZs_[tileLayerZKey];
-    } else {
-      tileCoordOrigin =
-          tileGrid.getTileCoordForCoordAndZ(center, tileLayerZKey);
-      tileLayerZ = new ol.renderer.dom.TileLayerZ_(tileGrid, tileCoordOrigin);
-      newTileLayerZKeys[tileLayerZKey] = true;
-      this.tileLayerZs_[tileLayerZKey] = tileLayerZ;
-    }
-    tilesToDraw = tilesToDrawByZ[tileLayerZKey];
-    for (tileCoordKey in tilesToDraw) {
-      tileLayerZ.addTile(tilesToDraw[tileCoordKey], tileGutter);
+
+/**
+ * @param {Document} doc Document.
+ * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
+ */
+ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument =
+    function(doc) {
+  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
+  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      return this.readFeatureCollectionMetadataFromNode(n);
     }
-    tileLayerZ.finalizeAddTiles();
   }
+  return undefined;
+};
 
-  /** @type {Array.<number>} */
-  var tileLayerZKeys =
-      goog.array.map(goog.object.getKeys(this.tileLayerZs_), Number);
-  goog.array.sort(tileLayerZKeys);
 
-  var i, ii, j, origin, resolution;
-  var transform = goog.vec.Mat4.createNumber();
-  for (i = 0, ii = tileLayerZKeys.length; i < ii; ++i) {
-    tileLayerZKey = tileLayerZKeys[i];
-    tileLayerZ = this.tileLayerZs_[tileLayerZKey];
-    if (!(tileLayerZKey in tilesToDrawByZ)) {
-      goog.dom.removeNode(tileLayerZ.target);
-      delete this.tileLayerZs_[tileLayerZKey];
-      continue;
-    }
-    resolution = tileLayerZ.getResolution();
-    origin = tileLayerZ.getOrigin();
-    ol.vec.Mat4.makeTransform2D(transform,
-        frameState.size[0] / 2, frameState.size[1] / 2,
-        resolution / viewState.resolution,
-        resolution / viewState.resolution,
-        viewState.rotation,
-        (origin[0] - center[0]) / resolution,
-        (center[1] - origin[1]) / resolution);
-    tileLayerZ.setTransform(transform);
-    if (tileLayerZKey in newTileLayerZKeys) {
-      for (j = tileLayerZKey - 1; j >= 0; --j) {
-        if (j in this.tileLayerZs_) {
-          goog.dom.insertSiblingAfter(
-              tileLayerZ.target, this.tileLayerZs_[j].target);
-          break;
-        }
-      }
-      if (j < 0) {
-        goog.dom.insertChildAt(this.target, tileLayerZ.target, 0);
-      }
-    } else {
-      if (!frameState.viewHints[ol.ViewHint.ANIMATING] &&
-          !frameState.viewHints[ol.ViewHint.INTERACTING]) {
-        tileLayerZ.removeTilesOutsideExtent(extent, tmpTileRange);
-      }
-    }
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'boundedBy': ol.xml.makeObjectPropertySetter(
+        ol.format.GMLBase.prototype.readGeometryElement, 'bounds')
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {ol.format.WFS.FeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
+ */
+ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'FeatureCollection');
+  var result = {};
+  var value = ol.format.XSD.readNonNegativeIntegerString(
+      node.getAttribute('numberOfFeatures'));
+  goog.object.set(result, 'numberOfFeatures', value);
+  return ol.xml.pushParseAndPop(
+      /** @type {ol.format.WFS.FeatureCollectionMetadata} */ (result),
+      ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_);
+};
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = {
+  'http://www.opengis.net/wfs': {
+    'totalInserted': ol.xml.makeObjectPropertySetter(
+        ol.format.XSD.readNonNegativeInteger),
+    'totalUpdated': ol.xml.makeObjectPropertySetter(
+        ol.format.XSD.readNonNegativeInteger),
+    'totalDeleted': ol.xml.makeObjectPropertySetter(
+        ol.format.XSD.readNonNegativeInteger)
   }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Transaction Summary.
+ * @private
+ */
+ol.format.WFS.readTransactionSummary_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
+};
 
-  if (layerState.opacity != this.renderedOpacity_) {
-    ol.dom.setOpacity(this.target, layerState.opacity);
-    this.renderedOpacity_ = layerState.opacity;
-  }
 
-  if (layerState.visible && !this.renderedVisible_) {
-    goog.style.setElementShown(this.target, true);
-    this.renderedVisible_ = true;
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WFS.OGC_FID_PARSERS_ = {
+  'http://www.opengis.net/ogc': {
+    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
+      return node.getAttribute('fid');
+    })
   }
+};
 
-  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 {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.WFS.fidParser_ = function(node, objectStack) {
+  ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
 };
 
 
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
+  'http://www.opengis.net/wfs': {
+    'Feature': ol.format.WFS.fidParser_
+  }
+};
+
 
 /**
- * @constructor
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Insert results.
  * @private
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @param {ol.TileCoord} tileCoordOrigin Tile coord origin.
  */
-ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) {
+ol.format.WFS.readInsertResults_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
+};
 
-  /**
-   * @type {!Element}
-   */
-  this.target = goog.dom.createElement(goog.dom.TagName.DIV);
-  this.target.style.position = 'absolute';
-  this.target.style.width = '100%';
-  this.target.style.height = '100%';
 
-  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-    /**
-     * Needed due to issues with IE7-8 clipping of transformed elements
-     * Solution is to translate separately from the scaled/rotated elements
-     * @private
-     * @type {!Element}
-     */
-    this.translateTarget_ = goog.dom.createElement(goog.dom.TagName.DIV);
-    this.translateTarget_.style.position = 'absolute';
-    this.translateTarget_.style.width = '100%';
-    this.translateTarget_.style.height = '100%';
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = {
+  'http://www.opengis.net/wfs': {
+    'TransactionSummary': ol.xml.makeObjectPropertySetter(
+        ol.format.WFS.readTransactionSummary_, 'transactionSummary'),
+    'InsertResults': ol.xml.makeObjectPropertySetter(
+        ol.format.WFS.readInsertResults_, 'insertIds')
+  }
+};
 
-    goog.dom.appendChild(this.target, this.translateTarget_);
+
+/**
+ * @param {Document} doc Document.
+ * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ */
+ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) {
+  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
+  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      return this.readTransactionResponseFromNode(n);
+    }
   }
+  return undefined;
+};
 
-  /**
-   * @private
-   * @type {ol.tilegrid.TileGrid}
-   */
-  this.tileGrid_ = tileGrid;
 
-  /**
-   * @private
-   * @type {ol.TileCoord}
-   */
-  this.tileCoordOrigin_ = tileCoordOrigin;
+/**
+ * @param {Node} node Node.
+ * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response.
+ */
+ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'TransactionResponse');
+  return ol.xml.pushParseAndPop(
+      /** @type {ol.format.WFS.TransactionResponse} */({}),
+      ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []);
+};
 
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.origin_ =
-      ol.extent.getTopLeft(tileGrid.getTileCoordExtent(tileCoordOrigin));
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.resolution_ = tileGrid.getResolution(tileCoordOrigin[0]);
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.WFS.QUERY_SERIALIZERS_ = {
+  'http://www.opengis.net/wfs': {
+    'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
+  }
+};
 
-  /**
-   * @private
-   * @type {Object.<string, ol.Tile>}
-   */
-  this.tiles_ = {};
 
-  /**
-   * @private
-   * @type {DocumentFragment}
-   */
-  this.documentFragment_ = null;
+/**
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.WFS.writeFeature_ = function(node, feature, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var featureType = goog.object.get(context, 'featureType');
+  var featureNS = goog.object.get(context, 'featureNS');
+  var child = ol.xml.createElementNS(featureNS, featureType);
+  node.appendChild(child);
+  ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack);
+};
 
-  /**
-   * @private
-   * @type {goog.vec.Mat4.Number}
-   */
-  this.transform_ = goog.vec.Mat4.createNumberIdentity();
 
+/**
+ * @param {Node} node Node.
+ * @param {number|string} fid Feature identifier.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) {
+  var filter = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
+  var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'FeatureId');
+  filter.appendChild(child);
+  child.setAttribute('fid', fid);
+  node.appendChild(filter);
 };
 
 
 /**
- * @param {ol.Tile} tile Tile.
- * @param {number} tileGutter Tile gutter.
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile, tileGutter) {
-  var tileCoord = tile.tileCoord;
-  var tileCoordZ = tileCoord[0];
-  var tileCoordX = tileCoord[1];
-  var tileCoordY = tileCoord[2];
-  goog.asserts.assert(tileCoordZ == this.tileCoordOrigin_[0]);
-  var tileCoordKey = ol.tilecoord.toString(tileCoord);
-  if (tileCoordKey in this.tiles_) {
-    return;
-  }
-  var tileSize = this.tileGrid_.getTileSize(tileCoordZ);
-  var image = tile.getImage(this);
-  var imageStyle = image.style;
-  // Bootstrap sets the style max-width: 100% for all images, which
-  // prevents the tile from being displayed in FireFox.  Workaround
-  // by overriding the max-width style.
-  imageStyle.maxWidth = 'none';
-  var tileElement;
-  var tileElementStyle;
-  if (tileGutter > 0) {
-    tileElement = goog.dom.createElement(goog.dom.TagName.DIV);
-    tileElementStyle = tileElement.style;
-    tileElementStyle.overflow = 'hidden';
-    tileElementStyle.width = tileSize + 'px';
-    tileElementStyle.height = tileSize + 'px';
-    imageStyle.position = 'absolute';
-    imageStyle.left = -tileGutter + 'px';
-    imageStyle.top = -tileGutter + 'px';
-    imageStyle.width = (tileSize + 2 * tileGutter) + 'px';
-    imageStyle.height = (tileSize + 2 * tileGutter) + 'px';
-    goog.dom.appendChild(tileElement, image);
-  } else {
-    imageStyle.width = tileSize + 'px';
-    imageStyle.height = tileSize + 'px';
-    tileElement = image;
-    tileElementStyle = imageStyle;
-  }
-  tileElementStyle.position = 'absolute';
-  tileElementStyle.left =
-      ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize) + 'px';
-  tileElementStyle.top =
-      ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize) + 'px';
-  if (goog.isNull(this.documentFragment_)) {
-    this.documentFragment_ = document.createDocumentFragment();
+ol.format.WFS.writeDelete_ = function(node, feature, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var featureType = goog.object.get(context, 'featureType');
+  var featurePrefix = goog.object.get(context, 'featurePrefix');
+  featurePrefix = goog.isDef(featurePrefix) ? featurePrefix :
+      ol.format.WFS.FEATURE_PREFIX;
+  var featureNS = goog.object.get(context, 'featureNS');
+  node.setAttribute('typeName', featurePrefix + ':' + featureType);
+  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
+      featureNS);
+  var fid = feature.getId();
+  if (goog.isDef(fid)) {
+    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
   }
-  goog.dom.appendChild(this.documentFragment_, tileElement);
-  this.tiles_[tileCoordKey] = tile;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.renderer.dom.TileLayerZ_.prototype.finalizeAddTiles = function() {
-  if (!goog.isNull(this.documentFragment_)) {
-    if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-      goog.dom.appendChild(this.translateTarget_, this.documentFragment_);
-    } else {
-      goog.dom.appendChild(this.target, this.documentFragment_);
+ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var featureType = goog.object.get(context, 'featureType');
+  var featurePrefix = goog.object.get(context, 'featurePrefix');
+  featurePrefix = goog.isDef(featurePrefix) ? featurePrefix :
+      ol.format.WFS.FEATURE_PREFIX;
+  var featureNS = goog.object.get(context, 'featureNS');
+  node.setAttribute('typeName', featurePrefix + ':' + featureType);
+  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
+      featureNS);
+  var fid = feature.getId();
+  if (goog.isDef(fid)) {
+    var keys = feature.getKeys();
+    var values = [];
+    for (var i = 0, ii = keys.length; i < ii; i++) {
+      var value = feature.get(keys[i]);
+      if (goog.isDef(value)) {
+        values.push({name: keys[i], value: value});
+      }
     }
-    this.documentFragment_ = null;
+    ol.xml.pushSerializeAndPop({node: node, srsName:
+          goog.object.get(context, 'srsName')},
+    ol.format.WFS.TRANSACTION_SERIALIZERS_,
+    ol.xml.makeSimpleNodeFactory('Property'), values,
+    objectStack);
+    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
   }
 };
 
 
 /**
- * @return {ol.Coordinate} Origin.
+ * @param {Node} node Node.
+ * @param {Object} pair Property name and value.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.renderer.dom.TileLayerZ_.prototype.getOrigin = function() {
-  return this.origin_;
+ol.format.WFS.writeProperty_ = function(node, pair, objectStack) {
+  var name = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Name');
+  node.appendChild(name);
+  ol.format.XSD.writeStringTextNode(name, pair.name);
+  if (goog.isDefAndNotNull(pair.value)) {
+    var value = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Value');
+    node.appendChild(value);
+    if (pair.value instanceof ol.geom.Geometry) {
+      ol.format.GML3.prototype.writeGeometryElement(value,
+          pair.value, objectStack);
+    } else {
+      ol.format.XSD.writeStringTextNode(value, pair.value);
+    }
+  }
 };
 
 
 /**
- * @return {number} Resolution.
+ * @param {Node} node Node.
+ * @param {{vendorId: string, safeToIgnore: boolean, value: string}}
+ *     nativeElement The native element.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.renderer.dom.TileLayerZ_.prototype.getResolution = function() {
-  return this.resolution_;
+ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) {
+  if (goog.isDef(nativeElement.vendorId)) {
+    node.setAttribute('vendorId', nativeElement.vendorId);
+  }
+  if (goog.isDef(nativeElement.safeToIgnore)) {
+    node.setAttribute('safeToIgnore', nativeElement.safeToIgnore);
+  }
+  if (goog.isDef(nativeElement.value)) {
+    ol.format.XSD.writeStringTextNode(node, nativeElement.value);
+  }
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
  */
-ol.renderer.dom.TileLayerZ_.prototype.removeTilesOutsideExtent =
-    function(extent, opt_tileRange) {
-  var tileRange = this.tileGrid_.getTileRangeForExtentAndZ(
-      extent, this.tileCoordOrigin_[0], opt_tileRange);
-  /** @type {Array.<ol.Tile>} */
-  var tilesToRemove = [];
-  var tile, tileCoordKey;
-  for (tileCoordKey in this.tiles_) {
-    tile = this.tiles_[tileCoordKey];
-    if (!tileRange.contains(tile.tileCoord)) {
-      tilesToRemove.push(tile);
-    }
-  }
-  var i, ii;
-  for (i = 0, ii = tilesToRemove.length; i < ii; ++i) {
-    tile = tilesToRemove[i];
-    tileCoordKey = ol.tilecoord.toString(tile.tileCoord);
-    goog.dom.removeNode(tile.getImage(this));
-    delete this.tiles_[tileCoordKey];
+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 {goog.vec.Mat4.Number} transform Transform.
+ * @param {Node} node Node.
+ * @param {string} featureType Feature type.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.renderer.dom.TileLayerZ_.prototype.setTransform = function(transform) {
-  if (!ol.vec.Mat4.equals2D(transform, this.transform_)) {
-    if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-      ol.dom.transformElement2D(this.target, transform, 6,
-          this.translateTarget_);
-    } else {
-      ol.dom.transformElement2D(this.target, transform, 6);
-    }
-    goog.vec.Mat4.setFromArray(this.transform_, transform);
+ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var featurePrefix = goog.object.get(context, 'featurePrefix');
+  var featureNS = goog.object.get(context, 'featureNS');
+  var propertyNames = goog.object.get(context, 'propertyNames');
+  var srsName = goog.object.get(context, 'srsName');
+  var prefix = goog.isDef(featurePrefix) ? featurePrefix + ':' : '';
+  node.setAttribute('typeName', prefix + featureType);
+  if (goog.isDef(srsName)) {
+    node.setAttribute('srsName', srsName);
+  }
+  if (goog.isDef(featureNS)) {
+    ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
+        featureNS);
+  }
+  var item = goog.object.clone(context);
+  item.node = node;
+  ol.xml.pushSerializeAndPop(item,
+      ol.format.WFS.QUERY_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames,
+      objectStack);
+  var bbox = goog.object.get(context, 'bbox');
+  if (goog.isDef(bbox)) {
+    var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
+    ol.format.WFS.writeOgcBBOX_(child, bbox, objectStack);
+    node.appendChild(child);
   }
 };
 
-goog.provide('ol.renderer.dom.Map');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.functions');
-goog.require('goog.style');
-goog.require('ol');
-goog.require('ol.RendererType');
-goog.require('ol.css');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Tile');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.dom.ImageLayer');
-goog.require('ol.renderer.dom.Layer');
-goog.require('ol.renderer.dom.TileLayer');
-goog.require('ol.source.State');
 
+/**
+ * @param {Node} node Node.
+ * @param {string} value PropertyName value.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) {
+  var property = ol.xml.createElementNS('http://www.opengis.net/ogc',
+      'PropertyName');
+  ol.format.XSD.writeStringTextNode(property, value);
+  node.appendChild(property);
+};
 
 
 /**
- * @constructor
- * @extends {ol.renderer.Map}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
+ * @param {Node} node Node.
+ * @param {ol.Extent} bbox Bounding box.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.renderer.dom.Map = function(container, map) {
-
-  goog.base(this, container, map);
+ol.format.WFS.writeOgcBBOX_ = function(node, bbox, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var geometryName = goog.object.get(context, 'geometryName');
+  var bboxNode = ol.xml.createElementNS('http://www.opengis.net/ogc', 'BBOX');
+  node.appendChild(bboxNode);
+  ol.format.WFS.writeOgcPropertyName_(bboxNode, geometryName, objectStack);
+  ol.format.GML3.prototype.writeGeometryElement(bboxNode, bbox, objectStack);
+};
 
-  /**
-   * @type {!Element}
-   * @private
-   */
-  this.layersPane_ = goog.dom.createElement(goog.dom.TagName.DIV);
-  this.layersPane_.className = ol.css.CLASS_UNSELECTABLE;
-  var style = this.layersPane_.style;
-  style.position = 'absolute';
-  style.width = '100%';
-  style.height = '100%';
 
-  // in IE < 9, we need to return false from ondragstart to cancel the default
-  // behavior of dragging images, which is interfering with the custom handler
-  // in the Drag interaction subclasses
-  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-    this.layersPane_.ondragstart = goog.functions.FALSE;
-    this.layersPane_.onselectstart = goog.functions.FALSE;
+/**
+ * @type {Object.<string, Object.<string, ol.xml.Serializer>>}
+ * @private
+ */
+ol.format.WFS.GETFEATURE_SERIALIZERS_ = {
+  'http://www.opengis.net/wfs': {
+    'Query': ol.xml.makeChildAppender(
+        ol.format.WFS.writeQuery_)
   }
+};
 
-  goog.dom.insertChildAt(container, this.layersPane_, 0);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
 
+/**
+ * @param {Node} node Node.
+ * @param {Array.<{string}>} featureTypes Feature types.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  goog.asserts.assert(goog.isObject(context));
+  var item = goog.object.clone(context);
+  item.node = node;
+  ol.xml.pushSerializeAndPop(item,
+      ol.format.WFS.GETFEATURE_SERIALIZERS_,
+      ol.xml.makeSimpleNodeFactory('Query'), featureTypes,
+      objectStack);
 };
-goog.inherits(ol.renderer.dom.Map, ol.renderer.Map);
 
 
 /**
- * @inheritDoc
+ * @param {olx.format.WFSWriteGetFeatureOptions} options Options.
+ * @return {Node} Result.
+ * @api stable
  */
-ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) {
-  var layerRenderer;
-  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
-    layerRenderer = new ol.renderer.dom.ImageLayer(this, layer);
-  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
-    layerRenderer = new ol.renderer.dom.TileLayer(this, layer);
-  } else {
-    goog.asserts.fail();
-    return null;
+ol.format.WFS.prototype.writeGetFeature = function(options) {
+  var node = ol.xml.createElementNS('http://www.opengis.net/wfs',
+      'GetFeature');
+  node.setAttribute('service', 'WFS');
+  node.setAttribute('version', '1.1.0');
+  if (goog.isDef(options)) {
+    if (goog.isDef(options.handle)) {
+      node.setAttribute('handle', options.handle);
+    }
+    if (goog.isDef(options.outputFormat)) {
+      node.setAttribute('outputFormat', options.outputFormat);
+    }
+    if (goog.isDef(options.maxFeatures)) {
+      node.setAttribute('maxFeatures', options.maxFeatures);
+    }
+    if (goog.isDef(options.resultType)) {
+      node.setAttribute('resultType', options.resultType);
+    }
+    if (goog.isDef(options.startIndex)) {
+      node.setAttribute('startIndex', options.startIndex);
+    }
+    if (goog.isDef(options.count)) {
+      node.setAttribute('count', options.count);
+    }
   }
-  return layerRenderer;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.dom.Map.prototype.getType = function() {
-  return ol.RendererType.DOM;
+  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
+      'xsi:schemaLocation', this.schemaLocation_);
+  var context = {
+    node: node,
+    srsName: options.srsName,
+    featureNS: goog.isDef(options.featureNS) ?
+        options.featureNS : this.featureNS_,
+    featurePrefix: options.featurePrefix,
+    geometryName: options.geometryName,
+    bbox: options.bbox,
+    propertyNames: goog.isDef(options.propertyNames) ?
+        options.propertyNames : []
+  };
+  goog.asserts.assert(goog.isArray(options.featureTypes));
+  ol.format.WFS.writeGetFeature_(node, options.featureTypes, [context]);
+  return node;
 };
 
 
 /**
- * @inheritDoc
+ * @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.renderer.dom.Map.prototype.renderFrame = function(frameState) {
-
-  if (goog.isNull(frameState)) {
-    if (this.renderedVisible_) {
-      goog.style.setElementShown(this.layersPane_, false);
-      this.renderedVisible_ = false;
+ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes,
+    options) {
+  var objectStack = [];
+  var node = ol.xml.createElementNS('http://www.opengis.net/wfs',
+      'Transaction');
+  node.setAttribute('service', 'WFS');
+  node.setAttribute('version', '1.1.0');
+  var baseObj, obj;
+  if (goog.isDef(options)) {
+    baseObj = goog.isDef(options.gmlOptions) ? options.gmlOptions : {};
+    if (goog.isDef(options.handle)) {
+      node.setAttribute('handle', options.handle);
     }
-    return;
   }
-
-  /**
-   * @this {ol.renderer.dom.Map}
-   * @param {Element} elem
-   * @param {number} i
-   */
-  var addChild;
-
-  // appendChild is actually more performant than insertBefore
-  // in IE 7 and 8. http://jsperf.com/reattaching-dom-nodes
-  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
-    addChild =
-        /**
-         * @this {ol.renderer.dom.Map}
-         * @param {Element} elem
-         */ (
-        function(elem) {
-          goog.dom.appendChild(this.layersPane_, elem);
-        });
-  } else {
-    addChild =
-        /**
-         * @this {ol.renderer.dom.Map}
-         * @param {Element} elem
-         * @param {number} i
-         */ (
-        function(elem, i) {
-          goog.dom.insertChildAt(this.layersPane_, elem, i);
-        });
+  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
+      'xsi:schemaLocation', this.schemaLocation_);
+  if (goog.isDefAndNotNull(inserts)) {
+    obj = {node: node, featureNS: options.featureNS,
+      featureType: options.featureType, featurePrefix: options.featurePrefix};
+    goog.object.extend(obj, baseObj);
+    ol.xml.pushSerializeAndPop(obj,
+        ol.format.WFS.TRANSACTION_SERIALIZERS_,
+        ol.xml.makeSimpleNodeFactory('Insert'), inserts,
+        objectStack);
   }
-
-  var layerStatesArray = frameState.layerStatesArray;
-  var i, ii, layer, layerRenderer, layerState;
-  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-    layerState = layerStatesArray[i];
-    layer = layerState.layer;
-    layerRenderer = /** @type {ol.renderer.dom.Layer} */ (
-        this.getLayerRenderer(layer));
-    goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer);
-    addChild.call(this, layerRenderer.getTarget(), i);
-    if (layerState.sourceState == ol.source.State.READY) {
-      layerRenderer.prepareFrame(frameState, layerState);
-    }
+  if (goog.isDefAndNotNull(updates)) {
+    obj = {node: node, featureNS: options.featureNS,
+      featureType: options.featureType, featurePrefix: options.featurePrefix};
+    goog.object.extend(obj, baseObj);
+    ol.xml.pushSerializeAndPop(obj,
+        ol.format.WFS.TRANSACTION_SERIALIZERS_,
+        ol.xml.makeSimpleNodeFactory('Update'), updates,
+        objectStack);
   }
-
-  var layerStates = frameState.layerStates;
-  var layerKey;
-  for (layerKey in this.getLayerRenderers()) {
-    if (!(layerKey in layerStates)) {
-      layerRenderer = this.getLayerRendererByKey(layerKey);
-      goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer);
-      goog.dom.removeNode(layerRenderer.getTarget());
-    }
+  if (goog.isDefAndNotNull(deletes)) {
+    ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS,
+      featureType: options.featureType, featurePrefix: options.featurePrefix},
+    ol.format.WFS.TRANSACTION_SERIALIZERS_,
+    ol.xml.makeSimpleNodeFactory('Delete'), deletes,
+    objectStack);
   }
-
-  if (!this.renderedVisible_) {
-    goog.style.setElementShown(this.layersPane_, true);
-    this.renderedVisible_ = true;
+  if (goog.isDef(options.nativeElements)) {
+    ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS,
+      featureType: options.featureType, featurePrefix: options.featurePrefix},
+    ol.format.WFS.TRANSACTION_SERIALIZERS_,
+    ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements,
+    objectStack);
   }
-
-  this.calculateMatrices2D(frameState);
-  this.scheduleRemoveUnusedLayerRenderers(frameState);
-  this.scheduleExpireIconCache(frameState);
-
+  return node;
 };
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
 
 /**
- * @fileoverview Constants used by the WebGL rendering, including all of the
- * constants used from the WebGL context.  For example, instead of using
- * context.ARRAY_BUFFER, your code can use
- * goog.webgl.ARRAY_BUFFER. The benefits for doing this include allowing
- * the compiler to optimize your code so that the compiled code does not have to
- * contain large strings to reference these properties, and reducing runtime
- * property access.
+ * Read the projection from a WFS source.
  *
- * Values are taken from the WebGL Spec:
- * https://www.khronos.org/registry/webgl/specs/1.0/#WEBGLRENDERINGCONTEXT
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {?ol.proj.Projection} Projection.
+ * @api stable
  */
-
-goog.provide('goog.webgl');
+ol.format.WFS.prototype.readProjection;
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.DEPTH_BUFFER_BIT = 0x00000100;
+ol.format.WFS.prototype.readProjectionFromDocument = function(doc) {
+  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
+  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      return this.readProjectionFromNode(n);
+    }
+  }
+  return null;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.STENCIL_BUFFER_BIT = 0x00000400;
-
+ol.format.WFS.prototype.readProjectionFromNode = function(node) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'FeatureCollection');
+  node = node.firstElementChild.firstElementChild;
+  if (goog.isDefAndNotNull(node)) {
+    for (var n = node.firstElementChild; !goog.isNull(n);
+        n = n.nextElementSibling) {
+      if (!(n.childNodes.length === 0 ||
+          (n.childNodes.length === 1 &&
+          n.firstChild.nodeType === 3))) {
+        var objectStack = [{}];
+        this.gmlFormat_.readGeometryElement(n, objectStack);
+        return ol.proj.get(objectStack.pop().srsName);
+      }
+    }
+  }
+  return null;
+};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.COLOR_BUFFER_BIT = 0x00004000;
+goog.provide('ol.format.WKT');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('ol.Feature');
+goog.require('ol.format.Feature');
+goog.require('ol.format.TextFeature');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryCollection');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.POINTS = 0x0000;
 
 
 /**
- * @const
- * @type {number}
+ * @constructor
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.WKTOptions=} opt_options Options.
+ * @api stable
  */
-goog.webgl.LINES = 0x0001;
+ol.format.WKT = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.LINE_LOOP = 0x0002;
+  goog.base(this);
 
+  /**
+   * Split GeometryCollection into multiple features.
+   * @type {boolean}
+   * @private
+   */
+  this.splitCollection_ = goog.isDef(options.splitCollection) ?
+      options.splitCollection : false;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.LINE_STRIP = 0x0003;
+};
+goog.inherits(ol.format.WKT, ol.format.TextFeature);
 
 
 /**
  * @const
- * @type {number}
+ * @type {string}
  */
-goog.webgl.TRIANGLES = 0x0004;
+ol.format.WKT.EMPTY = 'EMPTY';
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.geom.Point} geom Point geometry.
+ * @return {string} Coordinates part of Point as WKT.
+ * @private
  */
-goog.webgl.TRIANGLE_STRIP = 0x0005;
+ol.format.WKT.encodePointGeometry_ = function(geom) {
+  var coordinates = geom.getCoordinates();
+  if (goog.array.isEmpty(coordinates)) {
+    return '';
+  }
+  return coordinates[0] + ' ' + coordinates[1];
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
+ * @return {string} Coordinates part of MultiPoint as WKT.
+ * @private
  */
-goog.webgl.TRIANGLE_FAN = 0x0006;
+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(',');
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
+ * @return {string} Coordinates part of GeometryCollection as WKT.
+ * @private
  */
-goog.webgl.ZERO = 0;
+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(',');
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
+ * @return {string} Coordinates part of LineString as WKT.
+ * @private
  */
-goog.webgl.ONE = 1;
+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(',');
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
+ * @return {string} Coordinates part of MultiLineString as WKT.
+ * @private
  */
-goog.webgl.SRC_COLOR = 0x0300;
+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(',');
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.geom.Polygon} geom Polygon geometry.
+ * @return {string} Coordinates part of Polygon as WKT.
+ * @private
  */
-goog.webgl.ONE_MINUS_SRC_COLOR = 0x0301;
+ol.format.WKT.encodePolygonGeometry_ = function(geom) {
+  var array = [];
+  var rings = geom.getLinearRings();
+  for (var i = 0, ii = rings.length; i < ii; ++i) {
+    array.push('(' + ol.format.WKT.encodeLineStringGeometry_(
+        rings[i]) + ')');
+  }
+  return array.join(',');
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
+ * @return {string} Coordinates part of MultiPolygon as WKT.
+ * @private
  */
-goog.webgl.SRC_ALPHA = 0x0302;
+ol.format.WKT.encodeMultiPolygonGeometry_ = function(geom) {
+  var array = [];
+  var components = geom.getPolygons();
+  for (var i = 0, ii = components.length; i < ii; ++i) {
+    array.push('(' + ol.format.WKT.encodePolygonGeometry_(
+        components[i]) + ')');
+  }
+  return array.join(',');
+};
 
 
 /**
- * @const
- * @type {number}
+ * Encode a geometry as WKT.
+ * @param {ol.geom.Geometry} geom The geometry to encode.
+ * @return {string} WKT string for the geometry.
+ * @private
  */
-goog.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;
+ol.format.WKT.encode_ = function(geom) {
+  var type = geom.getType();
+  var geometryEncoder = ol.format.WKT.GeometryEncoder_[type];
+  goog.asserts.assert(goog.isDef(geometryEncoder));
+  var enc = geometryEncoder(geom);
+  type = type.toUpperCase();
+  if (enc.length === 0) {
+    return type + ' ' + ol.format.WKT.EMPTY;
+  }
+  return type + '(' + enc + ')';
+};
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, function(ol.geom.Geometry): string>}
+ * @private
  */
-goog.webgl.DST_ALPHA = 0x0304;
+ol.format.WKT.GeometryEncoder_ = {
+  'Point': ol.format.WKT.encodePointGeometry_,
+  'LineString': ol.format.WKT.encodeLineStringGeometry_,
+  'Polygon': ol.format.WKT.encodePolygonGeometry_,
+  'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_,
+  'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_,
+  'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_,
+  'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_
+};
 
 
 /**
- * @const
- * @type {number}
+ * Parse a WKT string.
+ * @param {string} wkt WKT string.
+ * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined}
+ *     The geometry created.
+ * @private
  */
-goog.webgl.ONE_MINUS_DST_ALPHA = 0x0305;
+ol.format.WKT.prototype.parse_ = function(wkt) {
+  var lexer = new ol.format.WKT.Lexer(wkt);
+  var parser = new ol.format.WKT.Parser(lexer);
+  return parser.parse();
+};
 
 
 /**
- * @const
- * @type {number}
+ * Read a feature from a WKT source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
+ * @api stable
  */
-goog.webgl.DST_COLOR = 0x0306;
+ol.format.WKT.prototype.readFeature;
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.ONE_MINUS_DST_COLOR = 0x0307;
+ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) {
+  var geom = this.readGeometryFromText(text, opt_options);
+  if (goog.isDef(geom)) {
+    var feature = new ol.Feature();
+    feature.setGeometry(geom);
+    return feature;
+  }
+  return null;
+};
 
 
 /**
- * @const
- * @type {number}
+ * Read all features from a WKT source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
  */
-goog.webgl.SRC_ALPHA_SATURATE = 0x0308;
+ol.format.WKT.prototype.readFeatures;
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.FUNC_ADD = 0x8006;
+ol.format.WKT.prototype.readFeaturesFromText = function(text, opt_options) {
+  var geometries = [];
+  var geometry = this.readGeometryFromText(text, opt_options);
+  if (this.splitCollection_ &&
+      geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
+    geometries = (/** @type {ol.geom.GeometryCollection} */ (geometry))
+        .getGeometriesArray();
+  } else {
+    geometries = [geometry];
+  }
+  var feature, features = [];
+  for (var i = 0, ii = geometries.length; i < ii; ++i) {
+    feature = new ol.Feature();
+    feature.setGeometry(geometries[i]);
+    features.push(feature);
+  }
+  return features;
+};
 
 
 /**
- * @const
- * @type {number}
+ * Read a single geometry from a WKT source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.geom.Geometry} Geometry.
+ * @api stable
  */
-goog.webgl.BLEND_EQUATION = 0x8009;
+ol.format.WKT.prototype.readGeometry;
 
 
 /**
- * Same as BLEND_EQUATION
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.BLEND_EQUATION_RGB = 0x8009;
+ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) {
+  var geometry = this.parse_(text);
+  if (goog.isDef(geometry)) {
+    return /** @type {ol.geom.Geometry} */ (
+        ol.format.Feature.transformWithOptions(geometry, false, opt_options));
+  } else {
+    return null;
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.BLEND_EQUATION_ALPHA = 0x883D;
+ol.format.WKT.prototype.readProjectionFromText = function(text) {
+  return null;
+};
 
 
 /**
- * @const
- * @type {number}
+ * 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
  */
-goog.webgl.FUNC_SUBTRACT = 0x800A;
+ol.format.WKT.prototype.writeFeature;
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.FUNC_REVERSE_SUBTRACT = 0x800B;
+ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
+  var geometry = feature.getGeometry();
+  if (goog.isDef(geometry)) {
+    return this.writeGeometryText(geometry, opt_options);
+  }
+  return '';
+};
 
 
 /**
- * @const
- * @type {number}
+ * 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
  */
-goog.webgl.BLEND_DST_RGB = 0x80C8;
+ol.format.WKT.prototype.writeFeatures;
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.BLEND_SRC_RGB = 0x80C9;
+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);
+};
 
 
 /**
- * @const
- * @type {number}
+ * Write a single geometry as a WKT string.
+ *
+ * @function
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @return {string} WKT string.
+ * @api stable
  */
-goog.webgl.BLEND_DST_ALPHA = 0x80CA;
+ol.format.WKT.prototype.writeGeometry;
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.BLEND_SRC_ALPHA = 0x80CB;
+ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) {
+  return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ (
+      ol.format.Feature.transformWithOptions(geometry, true, opt_options)));
+};
 
 
 /**
- * @const
- * @type {number}
+ * @typedef {{type: number, value: (number|string|undefined), position: number}}
  */
-goog.webgl.CONSTANT_COLOR = 0x8001;
+ol.format.WKT.Token;
 
 
 /**
  * @const
- * @type {number}
+ * @enum {number}
  */
-goog.webgl.ONE_MINUS_CONSTANT_COLOR = 0x8002;
+ol.format.WKT.TokenType = {
+  TEXT: 1,
+  LEFT_PAREN: 2,
+  RIGHT_PAREN: 3,
+  NUMBER: 4,
+  COMMA: 5,
+  EOF: 6
+};
+
 
 
 /**
- * @const
- * @type {number}
+ * Class to tokenize a WKT string.
+ * @param {string} wkt WKT string.
+ * @constructor
+ * @protected
  */
-goog.webgl.CONSTANT_ALPHA = 0x8003;
+ol.format.WKT.Lexer = function(wkt) {
 
+  /**
+   * @type {string}
+   */
+  this.wkt = wkt;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.ONE_MINUS_CONSTANT_ALPHA = 0x8004;
+  /**
+   * @type {number}
+   * @private
+   */
+  this.index_ = -1;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is alphabetic.
+ * @private
  */
-goog.webgl.BLEND_COLOR = 0x8005;
+ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) {
+  return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {string} c Character.
+ * @param {boolean=} opt_decimal Whether the string number
+ *     contains a dot, i.e. is a decimal number.
+ * @return {boolean} Whether the character is numeric.
+ * @private
  */
-goog.webgl.ARRAY_BUFFER = 0x8892;
+ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) {
+  var decimal = goog.isDef(opt_decimal) ? opt_decimal : false;
+  return c >= '0' && c <= '9' || c == '.' && !decimal;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is whitespace.
+ * @private
  */
-goog.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;
+ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
+  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {string} Next string character.
+ * @private
  */
-goog.webgl.ARRAY_BUFFER_BINDING = 0x8894;
+ol.format.WKT.Lexer.prototype.nextChar_ = function() {
+  return this.wkt.charAt(++this.index_);
+};
 
 
 /**
- * @const
- * @type {number}
+ * Fetch and return the next token.
+ * @return {!ol.format.WKT.Token} Next string token.
  */
-goog.webgl.ELEMENT_ARRAY_BUFFER_BINDING = 0x8895;
+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);
+  }
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.STREAM_DRAW = 0x88E0;
+  return token;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {number} Numeric token value.
+ * @private
  */
-goog.webgl.STATIC_DRAW = 0x88E4;
+ol.format.WKT.Lexer.prototype.readNumber_ = function() {
+  var c, index = this.index_;
+  var decimal = false;
+  do {
+    if (c == '.') {
+      decimal = true;
+    }
+    c = this.nextChar_();
+  } while (this.isNumeric_(c, decimal));
+  return parseFloat(this.wkt.substring(index, this.index_--));
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {string} String token value.
+ * @private
  */
-goog.webgl.DYNAMIC_DRAW = 0x88E8;
+ol.format.WKT.Lexer.prototype.readText_ = function() {
+  var c, index = this.index_;
+  do {
+    c = this.nextChar_();
+  } while (this.isAlpha_(c));
+  return this.wkt.substring(index, this.index_--).toUpperCase();
+};
+
 
 
 /**
- * @const
- * @type {number}
+ * Class to parse the tokens from the WKT string.
+ * @param {ol.format.WKT.Lexer} lexer
+ * @constructor
+ * @protected
  */
-goog.webgl.BUFFER_SIZE = 0x8764;
+ol.format.WKT.Parser = function(lexer) {
 
+  /**
+   * @type {ol.format.WKT.Lexer}
+   * @private
+   */
+  this.lexer_ = lexer;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.BUFFER_USAGE = 0x8765;
+  /**
+   * @type {ol.format.WKT.Token}
+   * @private
+   */
+  this.token_;
+
+  /**
+   * @type {number}
+   * @private
+   */
+  this.dimension_ = 2;
+};
 
 
 /**
- * @const
- * @type {number}
+ * Fetch the next token form the lexer and replace the active token.
+ * @private
  */
-goog.webgl.CURRENT_VERTEX_ATTRIB = 0x8626;
+ol.format.WKT.Parser.prototype.consume_ = function() {
+  this.token_ = this.lexer_.nextToken();
+};
 
 
 /**
- * @const
- * @type {number}
+ * If the given type matches the current token, consume it.
+ * @param {ol.format.WKT.TokenType.<number>} type Token type.
+ * @return {boolean} Whether the token matches the given type.
  */
-goog.webgl.FRONT = 0x0404;
+ol.format.WKT.Parser.prototype.match = function(type) {
+  var isMatch = this.token_.type == type;
+  if (isMatch) {
+    this.consume_();
+  }
+  return isMatch;
+};
 
 
 /**
- * @const
- * @type {number}
+ * Try to parse the tokens provided by the lexer.
+ * @return {ol.geom.Geometry|ol.geom.GeometryCollection} The geometry.
  */
-goog.webgl.BACK = 0x0405;
+ol.format.WKT.Parser.prototype.parse = function() {
+  this.consume_();
+  var geometry = this.parseGeometry_();
+  goog.asserts.assert(this.token_.type == ol.format.WKT.TokenType.EOF);
+  return geometry;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!(ol.geom.Geometry|ol.geom.GeometryCollection)} The geometry.
+ * @private
  */
-goog.webgl.FRONT_AND_BACK = 0x0408;
+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 (!goog.isDef(parser) || !goog.isDef(ctor)) {
+        throw new Error('Invalid geometry type: ' + geomType);
+      }
+      var coordinates = parser.call(this);
+      return new ctor(coordinates);
+    }
+  }
+  throw new Error(this.formatErrorMessage_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<ol.geom.Geometry>} A collection of geometries.
+ * @private
  */
-goog.webgl.CULL_FACE = 0x0B44;
+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_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {Array.<number>} All values in a point.
+ * @private
  */
-goog.webgl.BLEND = 0x0BE2;
+ol.format.WKT.Parser.prototype.parsePointText_ = function() {
+  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
+    var coordinates = this.parsePoint_();
+    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
+      return coordinates;
+    }
+  } else if (this.isEmptyGeometry_()) {
+    return null;
+  }
+  throw new Error(this.formatErrorMessage_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} All points in a linestring.
+ * @private
  */
-goog.webgl.DITHER = 0x0BD0;
+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_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} All points in a polygon.
+ * @private
  */
-goog.webgl.STENCIL_TEST = 0x0B90;
+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_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} All points in a multipoint.
+ * @private
  */
-goog.webgl.DEPTH_TEST = 0x0B71;
+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_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} All linestring points
+ *                                        in a multilinestring.
+ * @private
  */
-goog.webgl.SCISSOR_TEST = 0x0C11;
+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_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon.
+ * @private
  */
-goog.webgl.POLYGON_OFFSET_FILL = 0x8037;
+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_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<number>} A point.
+ * @private
  */
-goog.webgl.SAMPLE_ALPHA_TO_COVERAGE = 0x809E;
+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_());
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @private
  */
-goog.webgl.SAMPLE_COVERAGE = 0x80A0;
+ol.format.WKT.Parser.prototype.parsePointList_ = function() {
+  var coordinates = [this.parsePoint_()];
+  while (this.match(ol.format.WKT.TokenType.COMMA)) {
+    coordinates.push(this.parsePoint_());
+  }
+  return coordinates;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @private
  */
-goog.webgl.NO_ERROR = 0;
+ol.format.WKT.Parser.prototype.parsePointTextList_ = function() {
+  var coordinates = [this.parsePointText_()];
+  while (this.match(ol.format.WKT.TokenType.COMMA)) {
+    coordinates.push(this.parsePointText_());
+  }
+  return coordinates;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @private
  */
-goog.webgl.INVALID_ENUM = 0x0500;
+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;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {!Array.<!Array.<number>>} An array of points.
+ * @private
  */
-goog.webgl.INVALID_VALUE = 0x0501;
+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;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {boolean} Whether the token implies an empty geometry.
+ * @private
  */
-goog.webgl.INVALID_OPERATION = 0x0502;
+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;
+};
 
 
 /**
- * @const
- * @type {number}
+ * Create an error message for an unexpected token error.
+ * @return {string} Error message.
+ * @private
  */
-goog.webgl.OUT_OF_MEMORY = 0x0505;
+ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() {
+  return 'Unexpected `' + this.token_.value + '` at position ' +
+      this.token_.position + ' in `' + this.lexer_.wkt + '`';
+};
 
 
 /**
- * @const
- * @type {number}
+ * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout.<string>=)}
+ * @private
  */
-goog.webgl.CW = 0x0900;
+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
+};
 
 
 /**
- * @const
- * @type {number}
+ * @enum {(function(): Array)}
+ * @private
  */
-goog.webgl.CCW = 0x0901;
+ol.format.WKT.Parser.GeometryParser_ = {
+  'POINT': ol.format.WKT.Parser.prototype.parsePointText_,
+  'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_,
+  'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_,
+  'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_,
+  'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_,
+  'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_
+};
+
+goog.provide('ol.format.WMSCapabilities');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
+goog.require('ol.xml');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.LINE_WIDTH = 0x0B21;
 
 
 /**
- * @const
- * @type {number}
+ * @classdesc
+ * Format for reading WMS capabilities data
+ *
+ * @constructor
+ * @extends {ol.format.XML}
+ * @api
  */
-goog.webgl.ALIASED_POINT_SIZE_RANGE = 0x846D;
+ol.format.WMSCapabilities = function() {
 
+  goog.base(this);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.ALIASED_LINE_WIDTH_RANGE = 0x846E;
+  /**
+   * @type {string|undefined}
+   */
+  this.version = undefined;
+};
+goog.inherits(ol.format.WMSCapabilities, ol.format.XML);
 
 
 /**
- * @const
- * @type {number}
+ * Read a WMS capabilities document.
+ *
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMS capabilities.
+ * @api
  */
-goog.webgl.CULL_FACE_MODE = 0x0B45;
+ol.format.WMSCapabilities.prototype.read;
 
 
 /**
- * @const
- * @type {number}
+ * @param {Document} doc Document.
+ * @return {Object} WMS Capability object.
  */
-goog.webgl.FRONT_FACE = 0x0B46;
+ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) {
+  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
+  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      return this.readFromNode(n);
+    }
+  }
+  return null;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @return {Object} WMS Capability object.
  */
-goog.webgl.DEPTH_RANGE = 0x0B70;
+ol.format.WMSCapabilities.prototype.readFromNode = function(node) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'WMS_Capabilities' ||
+      node.localName == 'WMT_MS_Capabilities');
+  this.version = goog.string.trim(node.getAttribute('version'));
+  goog.asserts.assertString(this.version);
+  var wmsCapabilityObject = ol.xml.pushParseAndPop({
+    'version': this.version
+  }, ol.format.WMSCapabilities.PARSERS_, node, []);
+  return goog.isDef(wmsCapabilityObject) ? wmsCapabilityObject : null;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
  */
-goog.webgl.DEPTH_WRITEMASK = 0x0B72;
+ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Attribution');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object} Bounding box object.
  */
-goog.webgl.DEPTH_CLEAR_VALUE = 0x0B73;
+ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == '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'))
+  ];
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.DEPTH_FUNC = 0x0B74;
+  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
+  };
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.Extent|undefined} Bounding box object.
  */
-goog.webgl.STENCIL_CLEAR_VALUE = 0x0B91;
+ol.format.WMSCapabilities.readEXGeographicBoundingBox_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'EX_GeographicBoundingBox');
+  var geographicBoundingBox = ol.xml.pushParseAndPop(
+      {},
+      ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_,
+      node, objectStack);
+  if (!goog.isDef(geographicBoundingBox)) {
+    return undefined;
+  }
+  var westBoundLongitude = /** @type {number|undefined} */ (goog.object.get(
+      geographicBoundingBox, 'westBoundLongitude'));
+  var southBoundLatitude = /** @type {number|undefined} */ (goog.object.get(
+      geographicBoundingBox, 'southBoundLatitude'));
+  var eastBoundLongitude = /** @type {number|undefined} */ (goog.object.get(
+      geographicBoundingBox, 'eastBoundLongitude'));
+  var northBoundLatitude = /** @type {number|undefined} */ (goog.object.get(
+      geographicBoundingBox, 'northBoundLatitude'));
+  if (!goog.isDef(westBoundLongitude) || !goog.isDef(southBoundLatitude) ||
+      !goog.isDef(eastBoundLongitude) || !goog.isDef(northBoundLatitude)) {
+    return undefined;
+  }
+  return [
+    westBoundLongitude, southBoundLatitude,
+    eastBoundLongitude, northBoundLatitude
+  ];
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Capability object.
  */
-goog.webgl.STENCIL_FUNC = 0x0B92;
+ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Capability');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Service object.
  */
-goog.webgl.STENCIL_FAIL = 0x0B94;
+ol.format.WMSCapabilities.readService_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Service');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact information object.
  */
-goog.webgl.STENCIL_PASS_DEPTH_FAIL = 0x0B95;
+ol.format.WMSCapabilities.readContactInformation_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ContactInformation');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_,
+      node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact person object.
  */
-goog.webgl.STENCIL_PASS_DEPTH_PASS = 0x0B96;
+ol.format.WMSCapabilities.readContactPersonPrimary_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ContactPersonPrimary');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_,
+      node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Contact address object.
  */
-goog.webgl.STENCIL_REF = 0x0B97;
+ol.format.WMSCapabilities.readContactAddress_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'ContactAddress');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_,
+      node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Array.<string>|undefined} Format array.
  */
-goog.webgl.STENCIL_VALUE_MASK = 0x0B93;
+ol.format.WMSCapabilities.readException_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Exception');
+  return ol.xml.pushParseAndPop(
+      [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} Layer object.
  */
-goog.webgl.STENCIL_WRITEMASK = 0x0B98;
+ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Layer');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layer object.
  */
-goog.webgl.STENCIL_BACK_FUNC = 0x8800;
+ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Layer');
+  var parentLayerObject = /**  @type {Object.<string,*>} */
+      (objectStack[objectStack.length - 1]);
 
+  var layerObject = /**  @type {Object.<string,*>} */ (ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack));
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.STENCIL_BACK_FAIL = 0x8801;
+  if (!goog.isDef(layerObject)) {
+    return undefined;
+  }
+  var queryable =
+      ol.format.XSD.readBooleanString(node.getAttribute('queryable'));
+  if (!goog.isDef(queryable)) {
+    queryable = goog.object.get(parentLayerObject, 'queryable');
+  }
+  goog.object.set(
+      layerObject, 'queryable', goog.isDef(queryable) ? queryable : false);
 
+  var cascaded = ol.format.XSD.readNonNegativeIntegerString(
+      node.getAttribute('cascaded'));
+  if (!goog.isDef(cascaded)) {
+    cascaded = goog.object.get(parentLayerObject, 'cascaded');
+  }
+  goog.object.set(layerObject, 'cascaded', cascaded);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802;
+  var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
+  if (!goog.isDef(opaque)) {
+    opaque = goog.object.get(parentLayerObject, 'opaque');
+  }
+  goog.object.set(layerObject, 'opaque', goog.isDef(opaque) ? opaque : false);
 
+  var noSubsets =
+      ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
+  if (!goog.isDef(noSubsets)) {
+    noSubsets = goog.object.get(parentLayerObject, 'noSubsets');
+  }
+  goog.object.set(
+      layerObject, 'noSubsets', goog.isDef(noSubsets) ? noSubsets : false);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.STENCIL_BACK_PASS_DEPTH_PASS = 0x8803;
+  var fixedWidth =
+      ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
+  if (!goog.isDef(fixedWidth)) {
+    fixedWidth = goog.object.get(parentLayerObject, 'fixedWidth');
+  }
+  goog.object.set(layerObject, 'fixedWidth', fixedWidth);
 
+  var fixedHeight =
+      ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
+  if (!goog.isDef(fixedHeight)) {
+    fixedHeight = goog.object.get(parentLayerObject, 'fixedHeight');
+  }
+  goog.object.set(layerObject, 'fixedHeight', fixedHeight);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.STENCIL_BACK_REF = 0x8CA3;
+  // See 7.2.4.8
+  var addKeys = ['Style', 'CRS', 'AuthorityURL'];
+  goog.array.forEach(addKeys, function(key) {
+    var parentValue = goog.object.get(parentLayerObject, key);
+    if (goog.isDef(parentValue)) {
+      var childValue = goog.object.setIfUndefined(layerObject, key, []);
+      childValue = childValue.concat(parentValue);
+      goog.object.set(layerObject, key, childValue);
+    }
+  });
 
+  var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
+    'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'];
+  goog.array.forEach(replaceKeys, function(key) {
+    var childValue = goog.object.get(layerObject, key);
+    if (!goog.isDef(childValue)) {
+      var parentValue = goog.object.get(parentLayerObject, key);
+      goog.object.set(layerObject, key, parentValue);
+    }
+  });
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.STENCIL_BACK_VALUE_MASK = 0x8CA4;
+  return layerObject;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object} Dimension object.
  */
-goog.webgl.STENCIL_BACK_WRITEMASK = 0x8CA5;
+ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == '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;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Online resource object.
  */
-goog.webgl.VIEWPORT = 0x0BA2;
+ol.format.WMSCapabilities.readFormatOnlineresource_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_,
+      node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Request object.
  */
-goog.webgl.SCISSOR_BOX = 0x0C10;
+ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Request');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} DCP type object.
  */
-goog.webgl.COLOR_CLEAR_VALUE = 0x0C22;
+ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'DCPType');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} HTTP object.
  */
-goog.webgl.COLOR_WRITEMASK = 0x0C23;
+ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'HTTP');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Operation type object.
  */
-goog.webgl.UNPACK_ALIGNMENT = 0x0CF5;
+ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Online resource object.
  */
-goog.webgl.PACK_ALIGNMENT = 0x0D05;
+ol.format.WMSCapabilities.readSizedFormatOnlineresource_ =
+    function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  var formatOnlineresource =
+      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
+  if (goog.isDef(formatOnlineresource)) {
+    var size = [
+      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')),
+      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height'))
+    ];
+    goog.object.set(formatOnlineresource, 'size', size);
+    return formatOnlineresource;
+  }
+  return undefined;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Authority URL object.
  */
-goog.webgl.MAX_TEXTURE_SIZE = 0x0D33;
+ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'AuthorityURL');
+  var authorityObject =
+      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
+  if (goog.isDef(authorityObject)) {
+    goog.object.set(authorityObject, 'name', node.getAttribute('name'));
+    return authorityObject;
+  }
+  return undefined;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Metadata URL object.
  */
-goog.webgl.MAX_VIEWPORT_DIMS = 0x0D3A;
+ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'MetadataURL');
+  var metadataObject =
+      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
+  if (goog.isDef(metadataObject)) {
+    goog.object.set(metadataObject, 'type', node.getAttribute('type'));
+    return metadataObject;
+  }
+  return undefined;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Style object.
  */
-goog.webgl.SUBPIXEL_BITS = 0x0D50;
+ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Style');
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack);
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Keyword list.
  */
-goog.webgl.RED_BITS = 0x0D52;
+ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'KeywordList');
+  return ol.xml.pushParseAndPop(
+      [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack);
+};
 
 
 /**
  * @const
- * @type {number}
+ * @private
+ * @type {Array.<string>}
  */
-goog.webgl.GREEN_BITS = 0x0D53;
+ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/wms'
+];
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.BLUE_BITS = 0x0D54;
+ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Service': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readService_),
+      'Capability': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readCapability_)
+    });
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.ALPHA_BITS = 0x0D55;
+ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.DEPTH_BITS = 0x0D56;
+ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.STENCIL_BITS = 0x0D57;
+ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.POLYGON_OFFSET_UNITS = 0x2A00;
+ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'ContactPerson': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'ContactOrganization': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    });
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.POLYGON_OFFSET_FACTOR = 0x8038;
+ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.TEXTURE_BINDING_2D = 0x8069;
+ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
+    });
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.SAMPLE_BUFFERS = 0x80A8;
+ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.SAMPLES = 0x80A9;
+ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.SAMPLE_COVERAGE_VALUE = 0x80AA;
+ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ =
+    ol.xml.makeParsersNS(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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.SAMPLE_COVERAGE_INVERT = 0x80AB;
+ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.COMPRESSED_TEXTURE_FORMATS = 0x86A3;
+ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
+      'DCPType': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMSCapabilities.readDCPType_)
+    });
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.DONT_CARE = 0x1100;
+ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'HTTP': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readHTTP_)
+    });
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.FASTEST = 0x1101;
+ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Get': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readFormatOnlineresource_),
+      'Post': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readFormatOnlineresource_)
+    });
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.NICEST = 0x1102;
+ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeParsersNS(
+    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 {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.GENERATE_MIPMAP_HINT = 0x8192;
+ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'OnlineResource': ol.xml.makeObjectPropertySetter(
+          ol.format.XLink.readHref)
+    });
 
 
 /**
  * @const
- * @type {number}
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
  */
-goog.webgl.BYTE = 0x1400;
-
+ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString)
+    });
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.UNSIGNED_BYTE = 0x1401;
+goog.provide('ol.format.WMSGetFeatureInfo');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('ol.format.GML');
+goog.require('ol.format.GML2');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.xml');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.SHORT = 0x1402;
 
 
 /**
- * @const
- * @type {number}
+ * @classdesc
+ * Format for reading WMSGetFeatureInfo format. It uses
+ * {@link ol.format.GML2} to read features.
+ *
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @api
  */
-goog.webgl.UNSIGNED_SHORT = 0x1403;
+ol.format.WMSGetFeatureInfo = function() {
 
+  /**
+   * @private
+   * @type {string}
+   */
+  this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver';
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.INT = 0x1404;
 
+  /**
+   * @private
+   * @type {ol.format.GML2}
+   */
+  this.gmlFormat_ = new ol.format.GML2();
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.UNSIGNED_INT = 0x1405;
+  goog.base(this);
+};
+goog.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature);
 
 
 /**
  * @const
- * @type {number}
+ * @type {string}
+ * @private
  */
-goog.webgl.FLOAT = 0x1406;
+ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';
 
 
 /**
  * @const
- * @type {number}
+ * @type {string}
+ * @private
  */
-goog.webgl.DEPTH_COMPONENT = 0x1902;
+ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';
 
 
 /**
- * @const
- * @type {number}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<ol.Feature>} Features.
+ * @private
  */
-goog.webgl.ALPHA = 0x1906;
+ol.format.WMSGetFeatureInfo.prototype.readFeatures_ =
+    function(node, objectStack) {
+
+  node.namespaceURI = this.featureNS_;
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  var localName = ol.xml.getLocalName(node);
+  /** @type {Array.<ol.Feature>} */
+  var features = [];
+  if (node.childNodes.length === 0) {
+    return features;
+  }
+  if (localName == 'msGMLOutput') {
+    goog.array.forEach(node.childNodes, function(layer) {
+      if (layer.nodeType !== goog.dom.NodeType.ELEMENT) {
+        return;
+      }
+      var context = objectStack[0];
+      goog.asserts.assert(goog.isObject(context));
+
+      goog.asserts.assert(layer.localName.indexOf(
+          ol.format.WMSGetFeatureInfo.layerIdentifier_) >= 0);
+
+      var featureType = goog.string.remove(layer.localName,
+          ol.format.WMSGetFeatureInfo.layerIdentifier_) +
+          ol.format.WMSGetFeatureInfo.featureIdentifier_;
+
+      goog.object.set(context, 'featureType', featureType);
+      goog.object.set(context, 'featureNS', this.featureNS_);
+
+      var parsers = {};
+      parsers[featureType] = ol.xml.makeArrayPusher(
+          this.gmlFormat_.readFeatureElement, this.gmlFormat_);
+      var parsersNS = ol.xml.makeParsersNS(
+          [goog.object.get(context, 'featureNS'), null], parsers);
+      layer.namespaceURI = this.featureNS_;
+      var layerFeatures = ol.xml.pushParseAndPop(
+          [], parsersNS, layer, objectStack, this.gmlFormat_);
+      if (goog.isDef(layerFeatures)) {
+        goog.array.extend(features, layerFeatures);
+      }
+    }, this);
+  }
+  if (localName == 'FeatureCollection') {
+    var gmlFeatures = ol.xml.pushParseAndPop([],
+        this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
+        [{}], this.gmlFormat_);
+    if (goog.isDef(gmlFeatures)) {
+      features = gmlFeatures;
+    }
+  }
+  return features;
+};
 
 
 /**
- * @const
- * @type {number}
+ * Read all features from a WMSGetFeatureInfo response.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
  */
-goog.webgl.RGB = 0x1907;
+ol.format.WMSGetFeatureInfo.prototype.readFeatures;
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.RGBA = 0x1908;
+ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode =
+    function(node, opt_options) {
+  var options = {
+    'featureType': this.featureType,
+    'featureNS': this.featureNS
+  };
+  if (goog.isDef(opt_options)) {
+    goog.object.extend(options, this.getReadOptions(node, opt_options));
+  }
+  return this.readFeatures_(node, [options]);
+};
 
+goog.provide('ol.sphere.WGS84');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.LUMINANCE = 0x1909;
+goog.require('ol.Sphere');
 
 
 /**
+ * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid.
  * @const
- * @type {number}
+ * @type {ol.Sphere}
  */
-goog.webgl.LUMINANCE_ALPHA = 0x190A;
+ol.sphere.WGS84 = new ol.Sphere(6378137);
 
+// FIXME handle geolocation not supported
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.UNSIGNED_SHORT_4_4_4_4 = 0x8033;
+goog.provide('ol.Geolocation');
+goog.provide('ol.GeolocationProperty');
+
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.math');
+goog.require('ol.Coordinate');
+goog.require('ol.Object');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.Polygon');
+goog.require('ol.has');
+goog.require('ol.proj');
+goog.require('ol.sphere.WGS84');
 
 
 /**
- * @const
- * @type {number}
+ * @enum {string}
  */
-goog.webgl.UNSIGNED_SHORT_5_5_5_1 = 0x8034;
+ol.GeolocationProperty = {
+  ACCURACY: 'accuracy',
+  ACCURACY_GEOMETRY: 'accuracyGeometry',
+  ALTITUDE: 'altitude',
+  ALTITUDE_ACCURACY: 'altitudeAccuracy',
+  HEADING: 'heading',
+  POSITION: 'position',
+  PROJECTION: 'projection',
+  SPEED: 'speed',
+  TRACKING: 'tracking',
+  TRACKING_OPTIONS: 'trackingOptions'
+};
+
 
 
 /**
- * @const
- * @type {number}
+ * @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.
+ *
+ * Example:
+ *
+ *     var geolocation = new ol.Geolocation({
+ *       // take the projection to use from the map's view
+ *       projection: view.getProjection()
+ *     });
+ *     // listen to changes in position
+ *     geolocation.on('change', function(evt) {
+ *       window.console.log(geolocation.getPosition());
+ *     });
+ *
+ * @constructor
+ * @extends {ol.Object}
+ * @fires change Triggered when the position changes.
+ * @param {olx.GeolocationOptions=} opt_options Options.
+ * @api stable
  */
-goog.webgl.UNSIGNED_SHORT_5_6_5 = 0x8363;
+ol.Geolocation = function(opt_options) {
 
+  goog.base(this);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAGMENT_SHADER = 0x8B30;
+  var options = goog.isDef(opt_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;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.VERTEX_SHADER = 0x8B31;
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.watchId_ = undefined;
 
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.GeolocationProperty.PROJECTION),
+      this.handleProjectionChanged_, false, this);
+  goog.events.listen(
+      this, ol.Object.getChangeEventType(ol.GeolocationProperty.TRACKING),
+      this.handleTrackingChanged_, false, this);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.MAX_VERTEX_ATTRIBS = 0x8869;
+  if (goog.isDef(options.projection)) {
+    this.setProjection(ol.proj.get(options.projection));
+  }
+  if (goog.isDef(options.trackingOptions)) {
+    this.setTrackingOptions(options.trackingOptions);
+  }
 
+  this.setTracking(goog.isDef(options.tracking) ? options.tracking : false);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB;
+};
+goog.inherits(ol.Geolocation, ol.Object);
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.MAX_VARYING_VECTORS = 0x8DFC;
+ol.Geolocation.prototype.disposeInternal = function() {
+  this.setTracking(false);
+  goog.base(this, 'disposeInternal');
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
  */
-goog.webgl.MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D;
+ol.Geolocation.prototype.handleProjectionChanged_ = function() {
+  var projection = this.getProjection();
+  if (goog.isDefAndNotNull(projection)) {
+    this.transform_ = ol.proj.getTransformFromProjections(
+        ol.proj.get('EPSG:4326'), projection);
+    if (!goog.isNull(this.position_)) {
+      this.set(
+          ol.GeolocationProperty.POSITION, this.transform_(this.position_));
+    }
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
  */
-goog.webgl.MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C;
+ol.Geolocation.prototype.handleTrackingChanged_ = function() {
+  if (ol.has.GEOLOCATION) {
+    var tracking = this.getTracking();
+    if (tracking && !goog.isDef(this.watchId_)) {
+      this.watchId_ = goog.global.navigator.geolocation.watchPosition(
+          goog.bind(this.positionChange_, this),
+          goog.bind(this.positionError_, this),
+          this.getTrackingOptions());
+    } else if (!tracking && goog.isDef(this.watchId_)) {
+      goog.global.navigator.geolocation.clearWatch(this.watchId_);
+      this.watchId_ = undefined;
+    }
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {GeolocationPosition} position position event.
  */
-goog.webgl.MAX_TEXTURE_IMAGE_UNITS = 0x8872;
+ol.Geolocation.prototype.positionChange_ = function(position) {
+  var coords = position.coords;
+  this.set(ol.GeolocationProperty.ACCURACY, coords.accuracy);
+  this.set(ol.GeolocationProperty.ALTITUDE,
+      goog.isNull(coords.altitude) ? undefined : coords.altitude);
+  this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY,
+      goog.isNull(coords.altitudeAccuracy) ?
+      undefined : coords.altitudeAccuracy);
+  this.set(ol.GeolocationProperty.HEADING, goog.isNull(coords.heading) ?
+      undefined : goog.math.toRadians(coords.heading));
+  if (goog.isNull(this.position_)) {
+    this.position_ = [coords.longitude, coords.latitude];
+  } else {
+    this.position_[0] = coords.longitude;
+    this.position_[1] = coords.latitude;
+  }
+  var projectedPosition = this.transform_(this.position_);
+  this.set(ol.GeolocationProperty.POSITION, projectedPosition);
+  this.set(ol.GeolocationProperty.SPEED,
+      goog.isNull(coords.speed) ? undefined : coords.speed);
+  var geometry = ol.geom.Polygon.circular(
+      ol.sphere.WGS84, this.position_, coords.accuracy);
+  geometry.applyTransform(this.transform_);
+  this.set(ol.GeolocationProperty.ACCURACY_GEOMETRY, geometry);
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {number}
+ * @private
+ * @param {GeolocationPositionError} error error object.
  */
-goog.webgl.MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD;
+ol.Geolocation.prototype.positionError_ = function(error) {
+  error.type = goog.events.EventType.ERROR;
+  this.setTracking(false);
+  this.dispatchEvent(error);
+};
 
 
 /**
- * @const
- * @type {number}
+ * Get the accuracy of the position in meters.
+ * @return {number|undefined} The accuracy of the position measurement in
+ *     meters.
+ * @observable
+ * @api stable
  */
-goog.webgl.SHADER_TYPE = 0x8B4F;
+ol.Geolocation.prototype.getAccuracy = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.ACCURACY));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getAccuracy',
+    ol.Geolocation.prototype.getAccuracy);
 
 
 /**
- * @const
- * @type {number}
+ * Get a geometry of the position accuracy.
+ * @return {?ol.geom.Geometry} A geometry of the position accuracy.
+ * @observable
+ * @api stable
  */
-goog.webgl.DELETE_STATUS = 0x8B80;
+ol.Geolocation.prototype.getAccuracyGeometry = function() {
+  return /** @type {?ol.geom.Geometry} */ (
+      this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null);
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getAccuracyGeometry',
+    ol.Geolocation.prototype.getAccuracyGeometry);
 
 
 /**
- * @const
- * @type {number}
+ * Get the altitude associated with the position.
+ * @return {number|undefined} The altitude of the position in meters above mean
+ *     sea level.
+ * @observable
+ * @api stable
  */
-goog.webgl.LINK_STATUS = 0x8B82;
+ol.Geolocation.prototype.getAltitude = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.ALTITUDE));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getAltitude',
+    ol.Geolocation.prototype.getAltitude);
 
 
 /**
- * @const
- * @type {number}
+ * Get the altitude accuracy of the position.
+ * @return {number|undefined} The accuracy of the altitude measurement in
+ *     meters.
+ * @observable
+ * @api stable
  */
-goog.webgl.VALIDATE_STATUS = 0x8B83;
+ol.Geolocation.prototype.getAltitudeAccuracy = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getAltitudeAccuracy',
+    ol.Geolocation.prototype.getAltitudeAccuracy);
 
 
 /**
- * @const
- * @type {number}
+ * Get the heading as radians clockwise from North.
+ * @return {number|undefined} The heading of the device in radians from north.
+ * @observable
+ * @api stable
  */
-goog.webgl.ATTACHED_SHADERS = 0x8B85;
+ol.Geolocation.prototype.getHeading = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.HEADING));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getHeading',
+    ol.Geolocation.prototype.getHeading);
 
 
 /**
- * @const
- * @type {number}
+ * Get the position of the device.
+ * @return {ol.Coordinate|undefined} The current position of the device reported
+ *     in the current projection.
+ * @observable
+ * @api stable
  */
-goog.webgl.ACTIVE_UNIFORMS = 0x8B86;
+ol.Geolocation.prototype.getPosition = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+      this.get(ol.GeolocationProperty.POSITION));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getPosition',
+    ol.Geolocation.prototype.getPosition);
 
 
 /**
- * @const
- * @type {number}
+ * Get the projection associated with the position.
+ * @return {ol.proj.Projection|undefined} The projection the position is
+ *     reported in.
+ * @observable
+ * @api stable
  */
-goog.webgl.ACTIVE_ATTRIBUTES = 0x8B89;
+ol.Geolocation.prototype.getProjection = function() {
+  return /** @type {ol.proj.Projection|undefined} */ (
+      this.get(ol.GeolocationProperty.PROJECTION));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getProjection',
+    ol.Geolocation.prototype.getProjection);
 
 
 /**
- * @const
- * @type {number}
+ * Get the speed in meters per second.
+ * @return {number|undefined} The instantaneous speed of the device in meters
+ *     per second.
+ * @observable
+ * @api stable
  */
-goog.webgl.SHADING_LANGUAGE_VERSION = 0x8B8C;
+ol.Geolocation.prototype.getSpeed = function() {
+  return /** @type {number|undefined} */ (
+      this.get(ol.GeolocationProperty.SPEED));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getSpeed',
+    ol.Geolocation.prototype.getSpeed);
 
 
 /**
- * @const
- * @type {number}
+ * Are we tracking the user's position?
+ * @return {boolean} Whether to track the device's position.
+ * @observable
+ * @api stable
  */
-goog.webgl.CURRENT_PROGRAM = 0x8B8D;
+ol.Geolocation.prototype.getTracking = function() {
+  return /** @type {boolean} */ (
+      this.get(ol.GeolocationProperty.TRACKING));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getTracking',
+    ol.Geolocation.prototype.getTracking);
 
 
 /**
- * @const
- * @type {number}
+ * 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 at
+ *     {@link http://www.w3.org/TR/geolocation-API/#position_options_interface}
+ * @observable
+ * @api stable
  */
-goog.webgl.NEVER = 0x0200;
+ol.Geolocation.prototype.getTrackingOptions = function() {
+  return /** @type {GeolocationPositionOptions|undefined} */ (
+      this.get(ol.GeolocationProperty.TRACKING_OPTIONS));
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'getTrackingOptions',
+    ol.Geolocation.prototype.getTrackingOptions);
 
 
 /**
- * @const
- * @type {number}
+ * Set the projection to use for transforming the coordinates.
+ * @param {ol.proj.Projection} projection The projection the position is
+ *     reported in.
+ * @observable
+ * @api stable
  */
-goog.webgl.LESS = 0x0201;
+ol.Geolocation.prototype.setProjection = function(projection) {
+  this.set(ol.GeolocationProperty.PROJECTION, projection);
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'setProjection',
+    ol.Geolocation.prototype.setProjection);
 
 
 /**
- * @const
- * @type {number}
+ * Enable/disable tracking.
+ * @param {boolean} tracking Whether to track the device's position.
+ * @observable
+ * @api stable
  */
-goog.webgl.EQUAL = 0x0202;
+ol.Geolocation.prototype.setTracking = function(tracking) {
+  this.set(ol.GeolocationProperty.TRACKING, tracking);
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'setTracking',
+    ol.Geolocation.prototype.setTracking);
 
 
 /**
- * @const
- * @type {number}
+ * 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 at
+ *     {@link http://www.w3.org/TR/geolocation-API/#position_options_interface}
+ * @observable
+ * @api stable
  */
-goog.webgl.LEQUAL = 0x0203;
+ol.Geolocation.prototype.setTrackingOptions = function(options) {
+  this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options);
+};
+goog.exportProperty(
+    ol.Geolocation.prototype,
+    'setTrackingOptions',
+    ol.Geolocation.prototype.setTrackingOptions);
 
+goog.provide('ol.geom.flat.geodesic');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.GREATER = 0x0204;
+goog.require('goog.asserts');
+goog.require('goog.math');
+goog.require('goog.object');
+goog.require('ol.TransformFunction');
+goog.require('ol.math');
+goog.require('ol.proj');
 
 
 /**
- * @const
- * @type {number}
+ * @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.
  */
-goog.webgl.NOTEQUAL = 0x0205;
+ol.geom.flat.geodesic.line_ =
+    function(interpolate, transform, squaredTolerance) {
+  // FIXME reduce garbage generation
+  // FIXME optimize stack operations
 
+  /** @type {Array.<number>} */
+  var flatCoordinates = [];
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.GEQUAL = 0x0206;
+  var geoA = interpolate(0);
+  var geoB = interpolate(1);
 
+  var a = transform(geoA);
+  var b = transform(geoB);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.ALWAYS = 0x0207;
+  /** @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.<number, boolean>} */
+  var fractions = {};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.KEEP = 0x1E00;
+  var maxIterations = 1e5;
+  var geoM, m, fracA, fracB, fracM, key;
 
+  while (--maxIterations > 0 && fractionStack.length > 0) {
+    // Pop the a coordinate off the stack
+    fracA = fractionStack.pop();
+    geoA = geoStack.pop();
+    a = stack.pop();
+    // Add the a coordinate if it has not been added yet
+    key = fracA.toString();
+    if (!goog.object.containsKey(fractions, key)) {
+      flatCoordinates.push(a[0], a[1]);
+      goog.object.set(fractions, key, true);
+    }
+    // Pop the b coordinate off the stack
+    fracB = fractionStack.pop();
+    geoB = geoStack.pop();
+    b = stack.pop();
+    // Find the m point between the a and b coordinates
+    fracM = (fracA + fracB) / 2;
+    geoM = interpolate(fracM);
+    m = transform(geoM);
+    if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1],
+        b[0], b[1]) < squaredTolerance) {
+      // If the m point is sufficiently close to the straight line, then we
+      // discard it.  Just use the b coordinate and move on to the next line
+      // segment.
+      flatCoordinates.push(b[0], b[1]);
+      key = fracB.toString();
+      goog.asserts.assert(!goog.object.containsKey(fractions, key));
+      goog.object.set(fractions, key, true);
+    } else {
+      // Otherwise, we need to subdivide the current line segment.  Split it
+      // into two and push the two line segments onto the stack.
+      fractionStack.push(fracB, fracM, fracM, fracA);
+      stack.push(b, m, m, a);
+      geoStack.push(geoB, geoM, geoM, geoA);
+    }
+  }
+  goog.asserts.assert(maxIterations > 0);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.REPLACE = 0x1E01;
+  return flatCoordinates;
+};
 
 
 /**
- * @const
- * @type {number}
- */
-goog.webgl.INCR = 0x1E02;
-
+* 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) {
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.DECR = 0x1E03;
+  var geoProjection = ol.proj.get('EPSG:4326');
 
+  var cosLat1 = Math.cos(goog.math.toRadians(lat1));
+  var sinLat1 = Math.sin(goog.math.toRadians(lat1));
+  var cosLat2 = Math.cos(goog.math.toRadians(lat2));
+  var sinLat2 = Math.sin(goog.math.toRadians(lat2));
+  var cosDeltaLon = Math.cos(goog.math.toRadians(lon2 - lon1));
+  var sinDeltaLon = Math.sin(goog.math.toRadians(lon2 - lon1));
+  var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.INVERT = 0x150A;
+  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 = goog.math.toRadians(lon1) +
+            Math.atan2(Math.sin(theta) * sinD * cosLat1,
+                       cosD - sinLat1 * Math.sin(lat));
+        return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)];
+      }, ol.proj.getTransform(geoProjection, projection), squaredTolerance);
+};
 
 
 /**
- * @const
- * @type {number}
+ * 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.
  */
-goog.webgl.INCR_WRAP = 0x8507;
+ol.geom.flat.geodesic.meridian =
+    function(lon, lat1, lat2, projection, squaredTolerance) {
+  var epsg4326Projection = ol.proj.get('EPSG:4326');
+  return ol.geom.flat.geodesic.line_(
+      /**
+       * @param {number} frac Fraction.
+       * @return {ol.Coordinate} Coordinate.
+       */
+      function(frac) {
+        return [lon, lat1 + ((lat2 - lat1) * frac)];
+      },
+      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
+};
 
 
 /**
- * @const
- * @type {number}
+ * 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.
  */
-goog.webgl.DECR_WRAP = 0x8508;
+ol.geom.flat.geodesic.parallel =
+    function(lat, lon1, lon2, projection, squaredTolerance) {
+  var epsg4326Projection = ol.proj.get('EPSG:4326');
+  return ol.geom.flat.geodesic.line_(
+      /**
+       * @param {number} frac Fraction.
+       * @return {ol.Coordinate} Coordinate.
+       */
+      function(frac) {
+        return [lon1 + ((lon2 - lon1) * frac), lat];
+      },
+      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
+};
 
+goog.provide('ol.Graticule');
+
+goog.require('goog.asserts');
+goog.require('goog.math');
+goog.require('ol.extent');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.flat.geodesic');
+goog.require('ol.proj');
+goog.require('ol.render.EventType');
+goog.require('ol.style.Stroke');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.VENDOR = 0x1F00;
 
 
 /**
- * @const
- * @type {number}
+ * @constructor
+ * @param {olx.GraticuleOptions=} opt_options Options.
+ * @api
  */
-goog.webgl.RENDERER = 0x1F01;
+ol.Graticule = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.VERSION = 0x1F02;
+  /**
+   * @type {ol.Map}
+   * @private
+   */
+  this.map_ = null;
 
+  /**
+   * @type {ol.proj.Projection}
+   * @private
+   */
+  this.projection_ = null;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.NEAREST = 0x2600;
+  /**
+   * @type {number}
+   * @private
+   */
+  this.maxLat_ = Infinity;
 
+  /**
+   * @type {number}
+   * @private
+   */
+  this.maxLon_ = Infinity;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.LINEAR = 0x2601;
+  /**
+   * @type {number}
+   * @private
+   */
+  this.minLat_ = -Infinity;
 
+  /**
+   * @type {number}
+   * @private
+   */
+  this.minLon_ = -Infinity;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.NEAREST_MIPMAP_NEAREST = 0x2700;
+  /**
+   * @type {number}
+   * @private
+   */
+  this.targetSize_ = goog.isDef(options.targetSize) ?
+      options.targetSize : 100;
 
+  /**
+   * @type {number}
+   * @private
+   */
+  this.maxLines_ = goog.isDef(options.maxLines) ? options.maxLines : 100;
+  goog.asserts.assert(this.maxLines_ > 0);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.LINEAR_MIPMAP_NEAREST = 0x2701;
+  /**
+   * @type {Array.<ol.geom.LineString>}
+   * @private
+   */
+  this.meridians_ = [];
 
+  /**
+   * @type {Array.<ol.geom.LineString>}
+   * @private
+   */
+  this.parallels_ = [];
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.NEAREST_MIPMAP_LINEAR = 0x2702;
+  /**
+   * @type {ol.style.Stroke}
+   * @private
+   */
+  this.strokeStyle_ = goog.isDef(options.strokeStyle) ?
+      options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_;
 
+  /**
+   * @type {ol.TransformFunction|undefined}
+   * @private
+   */
+  this.fromLonLatTransform_ = undefined;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.LINEAR_MIPMAP_LINEAR = 0x2703;
+  /**
+   * @type {ol.TransformFunction|undefined}
+   * @private
+   */
+  this.toLonLatTransform_ = undefined;
 
+  /**
+   * @type {ol.Coordinate}
+   * @private
+   */
+  this.projectionCenterLonLat_ = null;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_MAG_FILTER = 0x2800;
+  this.setMap(goog.isDef(options.map) ? options.map : null);
+};
 
 
 /**
+ * @type {ol.style.Stroke}
+ * @private
  * @const
- * @type {number}
  */
-goog.webgl.TEXTURE_MIN_FILTER = 0x2801;
+ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
+  color: 'rgba(0,0,0,0.2)'
+});
 
 
 /**
- * @const
- * @type {number}
+ * TODO can be configurable
+ * @type {Array.<number>}
+ * @private
  */
-goog.webgl.TEXTURE_WRAP_S = 0x2802;
+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];
 
 
 /**
- * @const
- * @type {number}
+ * @param {number} lon Longitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {number} Index.
+ * @private
  */
-goog.webgl.TEXTURE_WRAP_T = 0x2803;
+ol.Graticule.prototype.addMeridian_ =
+    function(lon, squaredTolerance, extent, index) {
+  var lineString = this.getMeridian_(lon, squaredTolerance, index);
+  if (ol.extent.intersects(lineString.getExtent(), extent)) {
+    this.meridians_[index++] = lineString;
+  }
+  return index;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {number} lat Latitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {number} Index.
+ * @private
  */
-goog.webgl.TEXTURE_2D = 0x0DE1;
+ol.Graticule.prototype.addParallel_ =
+    function(lat, squaredTolerance, extent, index) {
+  var lineString = this.getParallel_(lat, squaredTolerance, index);
+  if (ol.extent.intersects(lineString.getExtent(), extent)) {
+    this.parallels_[index++] = lineString;
+  }
+  return index;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @private
  */
-goog.webgl.TEXTURE = 0x1702;
+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;
+  }
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_CUBE_MAP = 0x8513;
+  var centerLonLat = this.toLonLatTransform_(center);
+  var centerLon = centerLonLat[0];
+  var centerLat = centerLonLat[1];
+  var maxLines = this.maxLines_;
+  var cnt, idx, lat, lon;
 
+  // Create meridians
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_BINDING_CUBE_MAP = 0x8514;
+  centerLon = Math.floor(centerLon / interval) * interval;
+  lon = goog.math.clamp(centerLon, this.minLon_, this.maxLon_);
 
+  idx = this.addMeridian_(lon, squaredTolerance, extent, 0);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
+  cnt = 0;
+  while (lon != this.minLon_ && cnt++ < maxLines) {
+    lon = Math.max(lon - interval, this.minLon_);
+    idx = this.addMeridian_(lon, squaredTolerance, extent, idx);
+  }
 
+  lon = goog.math.clamp(centerLon, this.minLon_, this.maxLon_);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
+  cnt = 0;
+  while (lon != this.maxLon_ && cnt++ < maxLines) {
+    lon = Math.min(lon + interval, this.maxLon_);
+    idx = this.addMeridian_(lon, squaredTolerance, extent, idx);
+  }
 
+  this.meridians_.length = idx;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
+  // Create parallels
 
+  centerLat = Math.floor(centerLat / interval) * interval;
+  lat = goog.math.clamp(centerLat, this.minLat_, this.maxLat_);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
+  idx = this.addParallel_(lat, squaredTolerance, extent, 0);
+
+  cnt = 0;
+  while (lat != this.minLat_ && cnt++ < maxLines) {
+    lat = Math.max(lat - interval, this.minLat_);
+    idx = this.addParallel_(lat, squaredTolerance, extent, idx);
+  }
 
+  lat = goog.math.clamp(centerLat, this.minLat_, this.maxLat_);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
+  cnt = 0;
+  while (lat != this.maxLat_ && cnt++ < maxLines) {
+    lat = Math.min(lat + interval, this.maxLat_);
+    idx = this.addParallel_(lat, squaredTolerance, extent, idx);
+  }
 
+  this.parallels_.length = idx;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {number} resolution Resolution.
+ * @return {number} The interval in degrees.
+ * @private
  */
-goog.webgl.MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C;
+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;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {ol.Map} The map.
+ * @api
  */
-goog.webgl.TEXTURE0 = 0x84C0;
+ol.Graticule.prototype.getMap = function() {
+  return this.map_;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {number} lon Longitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.LineString} The meridian line string.
+ * @param {number} index Index.
+ * @private
  */
-goog.webgl.TEXTURE1 = 0x84C1;
+ol.Graticule.prototype.getMeridian_ = function(lon, squaredTolerance, index) {
+  goog.asserts.assert(lon >= this.minLon_);
+  goog.asserts.assert(lon <= this.maxLon_);
+  var flatCoordinates = ol.geom.flat.geodesic.meridian(lon,
+      this.minLat_, this.maxLat_, this.projection_, squaredTolerance);
+  goog.asserts.assert(flatCoordinates.length > 0);
+  var lineString = goog.isDef(this.meridians_[index]) ?
+      this.meridians_[index] : new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+  return lineString;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {Array.<ol.geom.LineString>} The meridians.
+ * @api
  */
-goog.webgl.TEXTURE2 = 0x84C2;
+ol.Graticule.prototype.getMeridians = function() {
+  return this.meridians_;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {number} lat Latitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.LineString} The parallel line string.
+ * @param {number} index Index.
+ * @private
  */
-goog.webgl.TEXTURE3 = 0x84C3;
+ol.Graticule.prototype.getParallel_ = function(lat, squaredTolerance, index) {
+  goog.asserts.assert(lat >= this.minLat_);
+  goog.asserts.assert(lat <= this.maxLat_);
+  var flatCoordinates = ol.geom.flat.geodesic.parallel(lat,
+      this.minLon_, this.maxLon_, this.projection_, squaredTolerance);
+  goog.asserts.assert(flatCoordinates.length > 0);
+  var lineString = goog.isDef(this.parallels_[index]) ?
+      this.parallels_[index] : new ol.geom.LineString(null);
+  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+  return lineString;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @return {Array.<ol.geom.LineString>} The parallels.
+ * @api
  */
-goog.webgl.TEXTURE4 = 0x84C4;
+ol.Graticule.prototype.getParallels = function() {
+  return this.parallels_;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.render.Event} e Event.
+ * @private
  */
-goog.webgl.TEXTURE5 = 0x84C5;
+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 = goog.isNull(this.projection_) ||
+      !ol.proj.equivalent(this.projection_, projection);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE6 = 0x84C6;
+  if (updateProjectionInfo) {
+    this.updateProjectionInfo_(projection);
+  }
 
+  this.createGraticule_(extent, center, resolution, squaredTolerance);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE7 = 0x84C7;
+  // Draw the lines
+  vectorContext.setFillStrokeStyle(null, this.strokeStyle_);
+  var i, l, line;
+  for (i = 0, l = this.meridians_.length; i < l; ++i) {
+    line = this.meridians_[i];
+    vectorContext.drawLineStringGeometry(line, null);
+  }
+  for (i = 0, l = this.parallels_.length; i < l; ++i) {
+    line = this.parallels_[i];
+    vectorContext.drawLineStringGeometry(line, null);
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.proj.Projection} projection Projection.
+ * @private
  */
-goog.webgl.TEXTURE8 = 0x84C8;
+ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
+  goog.asserts.assert(!goog.isNull(projection));
 
+  var extent = projection.getExtent();
+  var worldExtent = projection.getWorldExtent();
+  var maxLat = worldExtent[3];
+  var maxLon = worldExtent[2];
+  var minLat = worldExtent[1];
+  var minLon = worldExtent[0];
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE9 = 0x84C9;
+  goog.asserts.assert(!goog.isNull(extent));
+  goog.asserts.assert(goog.isDef(maxLat));
+  goog.asserts.assert(goog.isDef(maxLon));
+  goog.asserts.assert(goog.isDef(minLat));
+  goog.asserts.assert(goog.isDef(minLon));
 
+  this.maxLat_ = maxLat;
+  this.maxLon_ = maxLon;
+  this.minLat_ = minLat;
+  this.minLon_ = minLon;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE10 = 0x84CA;
+  var epsg4326Projection = ol.proj.get('EPSG:4326');
 
+  this.fromLonLatTransform_ = ol.proj.getTransform(
+      epsg4326Projection, projection);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE11 = 0x84CB;
+  this.toLonLatTransform_ = ol.proj.getTransform(
+      projection, epsg4326Projection);
 
+  this.projectionCenterLonLat_ = this.toLonLatTransform_(
+      ol.extent.getCenter(extent));
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE12 = 0x84CC;
+  this.projection_ = projection;
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.Map} map Map.
+ * @api
  */
-goog.webgl.TEXTURE13 = 0x84CD;
+ol.Graticule.prototype.setMap = function(map) {
+  if (!goog.isNull(this.map_)) {
+    this.map_.un(ol.render.EventType.POSTCOMPOSE,
+        this.handlePostCompose_, this);
+    this.map_.render();
+  }
+  if (!goog.isNull(map)) {
+    map.on(ol.render.EventType.POSTCOMPOSE,
+        this.handlePostCompose_, this);
+    map.render();
+  }
+  this.map_ = map;
+};
 
+goog.provide('ol.Image');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
+goog.require('ol.extent');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE14 = 0x84CE;
 
 
 /**
- * @const
- * @type {number}
+ * @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.
  */
-goog.webgl.TEXTURE15 = 0x84CF;
+ol.Image = function(extent, resolution, pixelRatio, attributions, src,
+    crossOrigin, imageLoadFunction) {
 
+  goog.base(this, extent, resolution, pixelRatio, ol.ImageState.IDLE,
+      attributions);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE16 = 0x84D0;
+  /**
+   * @private
+   * @type {string}
+   */
+  this.src_ = src;
 
+  /**
+   * @private
+   * @type {Image}
+   */
+  this.image_ = new Image();
+  if (!goog.isNull(crossOrigin)) {
+    this.image_.crossOrigin = crossOrigin;
+  }
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE17 = 0x84D1;
+  /**
+   * @private
+   * @type {Object.<number, Image>}
+   */
+  this.imageByContext_ = {};
 
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.imageListenerKeys_ = null;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE18 = 0x84D2;
+  /**
+   * @protected
+   * @type {ol.ImageState}
+   */
+  this.state = ol.ImageState.IDLE;
 
+  /**
+   * @private
+   * @type {ol.ImageLoadFunctionType}
+   */
+  this.imageLoadFunction_ = imageLoadFunction;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE19 = 0x84D3;
+};
+goog.inherits(ol.Image, ol.ImageBase);
 
 
 /**
- * @const
- * @type {number}
+ * @param {Object=} opt_context Object.
+ * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
+ * @api
  */
-goog.webgl.TEXTURE20 = 0x84D4;
+ol.Image.prototype.getImage = function(opt_context) {
+  if (goog.isDef(opt_context)) {
+    var image;
+    var key = goog.getUid(opt_context);
+    if (key in this.imageByContext_) {
+      return this.imageByContext_[key];
+    } else if (goog.object.isEmpty(this.imageByContext_)) {
+      image = this.image_;
+    } else {
+      image = /** @type {Image} */ (this.image_.cloneNode(false));
+    }
+    this.imageByContext_[key] = image;
+    return image;
+  } else {
+    return this.image_;
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * Tracks loading or read errors.
+ *
+ * @private
  */
-goog.webgl.TEXTURE21 = 0x84D5;
+ol.Image.prototype.handleImageError_ = function() {
+  this.state = ol.ImageState.ERROR;
+  this.unlistenImage_();
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {number}
+ * Tracks successful image load.
+ *
+ * @private
  */
-goog.webgl.TEXTURE22 = 0x84D6;
+ol.Image.prototype.handleImageLoad_ = function() {
+  if (!goog.isDef(this.resolution)) {
+    this.resolution = ol.extent.getHeight(this.extent) / this.image_.height;
+  }
+  this.state = ol.ImageState.LOADED;
+  this.unlistenImage_();
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {number}
+ * Load not yet loaded URI.
  */
-goog.webgl.TEXTURE23 = 0x84D7;
+ol.Image.prototype.load = function() {
+  if (this.state == ol.ImageState.IDLE) {
+    this.state = ol.ImageState.LOADING;
+    goog.asserts.assert(goog.isNull(this.imageListenerKeys_));
+    this.imageListenerKeys_ = [
+      goog.events.listenOnce(this.image_, goog.events.EventType.ERROR,
+          this.handleImageError_, false, this),
+      goog.events.listenOnce(this.image_, goog.events.EventType.LOAD,
+          this.handleImageLoad_, false, this)
+    ];
+    this.imageLoadFunction_(this, this.src_);
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
  */
-goog.webgl.TEXTURE24 = 0x84D8;
-
+ol.Image.prototype.unlistenImage_ = function() {
+  goog.asserts.assert(!goog.isNull(this.imageListenerKeys_));
+  goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
+};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE25 = 0x84D9;
+goog.provide('ol.ImageCanvas');
 
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE26 = 0x84DA;
 
 
 /**
- * @const
- * @type {number}
+ * @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.
  */
-goog.webgl.TEXTURE27 = 0x84DB;
-
+ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions,
+    canvas) {
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE28 = 0x84DC;
+  goog.base(this, extent, resolution, pixelRatio, ol.ImageState.LOADED,
+      attributions);
 
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = canvas;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE29 = 0x84DD;
+};
+goog.inherits(ol.ImageCanvas, ol.ImageBase);
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.TEXTURE30 = 0x84DE;
-
+ol.ImageCanvas.prototype.getImage = function(opt_context) {
+  return this.canvas_;
+};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.TEXTURE31 = 0x84DF;
+goog.provide('ol.ImageLoadFunctionType');
 
 
 /**
- * @const
- * @type {number}
+ * A function that takes an {@link ol.Image} for the image and a `{string}` for
+ * the src as arguments. It is supposed to make it so the underlying image
+ * {@link ol.Image#getImage} is assigned the content specified by the src. If
+ * not specified, the default is
+ *
+ *     function(image, src) {
+ *       image.getImage().src = src;
+ *     }
+ *
+ * Providing a custom `imageLoadFunction` can be useful to load images with
+ * post requests or - in general - through XHR requests, where the src of the
+ * image element would be set to a data URI when the content is loaded.
+ *
+ * @typedef {function(ol.Image, string)}
+ * @api
  */
-goog.webgl.ACTIVE_TEXTURE = 0x84E0;
-
+ol.ImageLoadFunctionType;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.REPEAT = 0x2901;
+goog.provide('ol.TileLoadFunctionType');
 
 
 /**
- * @const
- * @type {number}
+ * A function that takes an {@link ol.ImageTile} for the image tile and a
+ * `{string}` for the src as arguments.
+ *
+ * @typedef {function(ol.ImageTile, string)}
+ * @api
  */
-goog.webgl.CLAMP_TO_EDGE = 0x812F;
-
+ol.TileLoadFunctionType;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.MIRRORED_REPEAT = 0x8370;
+goog.provide('ol.ImageTile');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol.Tile');
+goog.require('ol.TileCoord');
+goog.require('ol.TileLoadFunctionType');
+goog.require('ol.TileState');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FLOAT_VEC2 = 0x8B50;
 
 
 /**
- * @const
- * @type {number}
+ * @constructor
+ * @extends {ol.Tile}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
+ * @param {string} src Image source URI.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
  */
-goog.webgl.FLOAT_VEC3 = 0x8B51;
-
+ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) {
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FLOAT_VEC4 = 0x8B52;
+  goog.base(this, tileCoord, state);
 
+  /**
+   * Image URI
+   *
+   * @private
+   * @type {string}
+   */
+  this.src_ = src;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.INT_VEC2 = 0x8B53;
+  /**
+   * @private
+   * @type {Image}
+   */
+  this.image_ = new Image();
+  if (!goog.isNull(crossOrigin)) {
+    this.image_.crossOrigin = crossOrigin;
+  }
 
+  /**
+   * @private
+   * @type {Object.<number, Image>}
+   */
+  this.imageByContext_ = {};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.INT_VEC3 = 0x8B54;
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.imageListenerKeys_ = null;
 
+  /**
+   * @private
+   * @type {ol.TileLoadFunctionType}
+   */
+  this.tileLoadFunction_ = tileLoadFunction;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.INT_VEC4 = 0x8B55;
+};
+goog.inherits(ol.ImageTile, ol.Tile);
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
+ * @api
  */
-goog.webgl.BOOL = 0x8B56;
+ol.ImageTile.prototype.getImage = function(opt_context) {
+  if (goog.isDef(opt_context)) {
+    var image;
+    var key = goog.getUid(opt_context);
+    if (key in this.imageByContext_) {
+      return this.imageByContext_[key];
+    } else if (goog.object.isEmpty(this.imageByContext_)) {
+      image = this.image_;
+    } else {
+      image = /** @type {Image} */ (this.image_.cloneNode(false));
+    }
+    this.imageByContext_[key] = image;
+    return image;
+  } else {
+    return this.image_;
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @inheritDoc
  */
-goog.webgl.BOOL_VEC2 = 0x8B57;
+ol.ImageTile.prototype.getKey = function() {
+  return this.src_;
+};
 
 
 /**
- * @const
- * @type {number}
+ * Tracks loading or read errors.
+ *
+ * @private
  */
-goog.webgl.BOOL_VEC3 = 0x8B58;
+ol.ImageTile.prototype.handleImageError_ = function() {
+  this.state = ol.TileState.ERROR;
+  this.unlistenImage_();
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {number}
+ * Tracks successful image load.
+ *
+ * @private
  */
-goog.webgl.BOOL_VEC4 = 0x8B59;
+ol.ImageTile.prototype.handleImageLoad_ = function() {
+  if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
+    if (!goog.isDef(this.image_.naturalWidth)) {
+      this.image_.naturalWidth = this.image_.width;
+      this.image_.naturalHeight = this.image_.height;
+    }
+  }
+
+  if (this.image_.naturalWidth && this.image_.naturalHeight) {
+    this.state = ol.TileState.LOADED;
+  } else {
+    this.state = ol.TileState.EMPTY;
+  }
+  this.unlistenImage_();
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {number}
+ * Load not yet loaded URI.
  */
-goog.webgl.FLOAT_MAT2 = 0x8B5A;
+ol.ImageTile.prototype.load = function() {
+  if (this.state == ol.TileState.IDLE) {
+    this.state = ol.TileState.LOADING;
+    goog.asserts.assert(goog.isNull(this.imageListenerKeys_));
+    this.imageListenerKeys_ = [
+      goog.events.listenOnce(this.image_, goog.events.EventType.ERROR,
+          this.handleImageError_, false, this),
+      goog.events.listenOnce(this.image_, goog.events.EventType.LOAD,
+          this.handleImageLoad_, false, this)
+    ];
+    this.tileLoadFunction_(this, this.src_);
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
  */
-goog.webgl.FLOAT_MAT3 = 0x8B5B;
+ol.ImageTile.prototype.unlistenImage_ = function() {
+  goog.asserts.assert(!goog.isNull(this.imageListenerKeys_));
+  goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
+};
 
+goog.provide('ol.ImageUrlFunction');
+goog.provide('ol.ImageUrlFunctionType');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FLOAT_MAT4 = 0x8B5C;
+goog.require('ol.Size');
 
 
 /**
- * @const
- * @type {number}
+ * @typedef {function(this:ol.source.Image, ol.Extent, ol.Size,
+ *     ol.proj.Projection): (string|undefined)}
  */
-goog.webgl.SAMPLER_2D = 0x8B5E;
+ol.ImageUrlFunctionType;
 
 
 /**
- * @const
- * @type {number}
+ * @param {string} baseUrl Base URL (may have query data).
+ * @param {Object.<string,*>} params to encode in the URL.
+ * @param {function(string, Object.<string,*>, ol.Extent, ol.Size,
+ *     ol.proj.Projection): (string|undefined)} paramsFunction params function.
+ * @return {ol.ImageUrlFunctionType} Image URL function.
  */
-goog.webgl.SAMPLER_CUBE = 0x8B60;
+ol.ImageUrlFunction.createFromParamsFunction =
+    function(baseUrl, params, paramsFunction) {
+  return (
+      /**
+       * @this {ol.source.Image}
+       * @param {ol.Extent} extent Extent.
+       * @param {ol.Size} size Size.
+       * @param {ol.proj.Projection} projection Projection.
+       * @return {string|undefined} URL.
+       */
+      function(extent, size, projection) {
+        return paramsFunction(baseUrl, params, extent, size, projection);
+      });
+};
 
 
 /**
- * @const
- * @type {number}
+ * @this {ol.source.Image}
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Size} size Size.
+ * @return {string|undefined} Image URL.
  */
-goog.webgl.VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622;
+ol.ImageUrlFunction.nullImageUrlFunction =
+    function(extent, size) {
+  return undefined;
+};
 
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @const
- * @type {number}
+ * @fileoverview Provides a files drag and drop event detector. It works on
+ * HTML5 browsers.
+ *
+ * @see ../demos/filedrophandler.html
  */
-goog.webgl.VERTEX_ATTRIB_ARRAY_SIZE = 0x8623;
 
+goog.provide('goog.events.FileDropHandler');
+goog.provide('goog.events.FileDropHandler.EventType');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.log');
+goog.require('goog.log.Level');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624;
 
 
 /**
- * @const
- * @type {number}
+ * A files drag and drop event detector. Gets an {@code element} as parameter
+ * and fires {@code goog.events.FileDropHandler.EventType.DROP} event when files
+ * are dropped in the {@code element}.
+ *
+ * @param {Element|Document} element The element or document to listen on.
+ * @param {boolean=} opt_preventDropOutside Whether to prevent a drop on the
+ *     area outside the {@code element}. Default false.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @final
  */
-goog.webgl.VERTEX_ATTRIB_ARRAY_TYPE = 0x8625;
+goog.events.FileDropHandler = function(element, opt_preventDropOutside) {
+  goog.events.EventTarget.call(this);
 
+  /**
+   * Handler for drag/drop events.
+   * @type {!goog.events.EventHandler<!goog.events.FileDropHandler>}
+   * @private
+   */
+  this.eventHandler_ = new goog.events.EventHandler(this);
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A;
+  var doc = element;
+  if (opt_preventDropOutside) {
+    doc = goog.dom.getOwnerDocument(element);
+  }
+
+  // Add dragenter listener to the owner document of the element.
+  this.eventHandler_.listen(doc,
+                            goog.events.EventType.DRAGENTER,
+                            this.onDocDragEnter_);
 
+  // Add dragover listener to the owner document of the element only if the
+  // document is not the element itself.
+  if (doc != element) {
+    this.eventHandler_.listen(doc,
+                              goog.events.EventType.DRAGOVER,
+                              this.onDocDragOver_);
+  }
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.VERTEX_ATTRIB_ARRAY_POINTER = 0x8645;
+  // Add dragover and drop listeners to the element.
+  this.eventHandler_.listen(element,
+                            goog.events.EventType.DRAGOVER,
+                            this.onElemDragOver_);
+  this.eventHandler_.listen(element,
+                            goog.events.EventType.DROP,
+                            this.onElemDrop_);
+};
+goog.inherits(goog.events.FileDropHandler, goog.events.EventTarget);
 
 
 /**
- * @const
- * @type {number}
+ * Whether the drag event contains files. It is initialized only in the
+ * dragenter event. It is used in all the drag events to prevent default actions
+ * only if the drag contains files. Preventing default actions is necessary to
+ * go from dragenter to dragover and from dragover to drop. However we do not
+ * always want to prevent default actions, e.g. when the user drags text or
+ * links on a text area we should not prevent the browser default action that
+ * inserts the text in the text area. It is also necessary to stop propagation
+ * when handling drag events on the element to prevent them from propagating
+ * to the document.
+ * @private
+ * @type {boolean}
  */
-goog.webgl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F;
+goog.events.FileDropHandler.prototype.dndContainsFiles_ = false;
 
 
 /**
- * @const
- * @type {number}
+ * A logger, used to help us debug the algorithm.
+ * @type {goog.log.Logger}
+ * @private
  */
-goog.webgl.COMPILE_STATUS = 0x8B81;
+goog.events.FileDropHandler.prototype.logger_ =
+    goog.log.getLogger('goog.events.FileDropHandler');
 
 
 /**
- * @const
- * @type {number}
+ * The types of events fired by this class.
+ * @enum {string}
  */
-goog.webgl.LOW_FLOAT = 0x8DF0;
+goog.events.FileDropHandler.EventType = {
+  DROP: goog.events.EventType.DROP
+};
 
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.MEDIUM_FLOAT = 0x8DF1;
+/** @override */
+goog.events.FileDropHandler.prototype.disposeInternal = function() {
+  goog.events.FileDropHandler.superClass_.disposeInternal.call(this);
+  this.eventHandler_.dispose();
+};
 
 
 /**
- * @const
- * @type {number}
+ * Dispatches the DROP event.
+ * @param {goog.events.BrowserEvent} e The underlying browser event.
+ * @private
  */
-goog.webgl.HIGH_FLOAT = 0x8DF2;
+goog.events.FileDropHandler.prototype.dispatch_ = function(e) {
+  goog.log.fine(this.logger_, 'Firing DROP event...');
+  var event = new goog.events.BrowserEvent(e.getBrowserEvent());
+  event.type = goog.events.FileDropHandler.EventType.DROP;
+  this.dispatchEvent(event);
+};
 
 
 /**
- * @const
- * @type {number}
+ * Handles dragenter on the document.
+ * @param {goog.events.BrowserEvent} e The dragenter event.
+ * @private
  */
-goog.webgl.LOW_INT = 0x8DF3;
+goog.events.FileDropHandler.prototype.onDocDragEnter_ = function(e) {
+  goog.log.log(this.logger_, goog.log.Level.FINER,
+      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
+  var dt = e.getBrowserEvent().dataTransfer;
+  // Check whether the drag event contains files.
+  this.dndContainsFiles_ = !!(dt &&
+      ((dt.types &&
+          (goog.array.contains(dt.types, 'Files') ||
+          goog.array.contains(dt.types, 'public.file-url'))) ||
+      (dt.files && dt.files.length > 0)));
+  // If it does
+  if (this.dndContainsFiles_) {
+    // Prevent default actions.
+    e.preventDefault();
+  }
+  goog.log.log(this.logger_, goog.log.Level.FINER,
+      'dndContainsFiles_: ' + this.dndContainsFiles_);
+};
 
 
 /**
- * @const
- * @type {number}
+ * Handles dragging something over the document.
+ * @param {goog.events.BrowserEvent} e The dragover event.
+ * @private
  */
-goog.webgl.MEDIUM_INT = 0x8DF4;
+goog.events.FileDropHandler.prototype.onDocDragOver_ = function(e) {
+  goog.log.log(this.logger_, goog.log.Level.FINEST,
+      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
+  if (this.dndContainsFiles_) {
+    // Prevent default actions.
+    e.preventDefault();
+    // Disable the drop on the document outside the drop zone.
+    var dt = e.getBrowserEvent().dataTransfer;
+    dt.dropEffect = 'none';
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * Handles dragging something over the element (drop zone).
+ * @param {goog.events.BrowserEvent} e The dragover event.
+ * @private
  */
-goog.webgl.HIGH_INT = 0x8DF5;
+goog.events.FileDropHandler.prototype.onElemDragOver_ = function(e) {
+  goog.log.log(this.logger_, goog.log.Level.FINEST,
+      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
+  if (this.dndContainsFiles_) {
+    // Prevent default actions and stop the event from propagating further to
+    // the document. Both lines are needed! (See comment above).
+    e.preventDefault();
+    e.stopPropagation();
+    // Allow the drop on the drop zone.
+    var dt = e.getBrowserEvent().dataTransfer;
+    dt.effectAllowed = 'all';
+    dt.dropEffect = 'copy';
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * Handles dropping something onto the element (drop zone).
+ * @param {goog.events.BrowserEvent} e The drop event.
+ * @private
  */
-goog.webgl.FRAMEBUFFER = 0x8D40;
+goog.events.FileDropHandler.prototype.onElemDrop_ = function(e) {
+  goog.log.log(this.logger_, goog.log.Level.FINER,
+      '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
+  // If the drag and drop event contains files.
+  if (this.dndContainsFiles_) {
+    // Prevent default actions and stop the event from propagating further to
+    // the document. Both lines are needed! (See comment above).
+    e.preventDefault();
+    e.stopPropagation();
+    // Dispatch DROP event.
+    this.dispatch_(e);
+  }
+};
+
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
+goog.provide('goog.Thenable');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.RENDERBUFFER = 0x8D41;
 
 
 /**
- * @const
- * @type {number}
+ * Provides a more strict interface for Thenables in terms of
+ * http://promisesaplus.com for interop with {@see goog.Promise}.
+ *
+ * @interface
+ * @extends {IThenable<TYPE>}
+ * @template TYPE
  */
-goog.webgl.RGBA4 = 0x8056;
+goog.Thenable = function() {};
 
 
 /**
- * @const
- * @type {number}
+ * Adds callbacks that will operate on the result of the Thenable, returning a
+ * new child Promise.
+ *
+ * If the Thenable is fulfilled, the {@code onFulfilled} callback will be
+ * invoked with the fulfillment value as argument, and the child Promise will
+ * be fulfilled with the return value of the callback. If the callback throws
+ * an exception, the child Promise will be rejected with the thrown value
+ * instead.
+ *
+ * If the Thenable is rejected, the {@code onRejected} callback will be invoked
+ * with the rejection reason as argument, and the child Promise will be rejected
+ * with the return value of the callback or thrown value.
+ *
+ * @param {?(function(this:THIS, TYPE):
+ *             (RESULT|IThenable<RESULT>|Thenable))=} opt_onFulfilled A
+ *     function that will be invoked with the fulfillment value if the Promise
+ *     is fullfilled.
+ * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will
+ *     be invoked with the rejection reason if the Promise is rejected.
+ * @param {THIS=} opt_context An optional context object that will be the
+ *     execution context for the callbacks. By default, functions are executed
+ *     with the default this.
+ * @return {!goog.Promise<RESULT>} A new Promise that will receive the result
+ *     of the fulfillment or rejection callback.
+ * @template RESULT,THIS
  */
-goog.webgl.RGB5_A1 = 0x8057;
+goog.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected,
+    opt_context) {};
 
 
 /**
+ * An expando property to indicate that an object implements
+ * {@code goog.Thenable}.
+ *
+ * {@see addImplementation}.
+ *
  * @const
- * @type {number}
  */
-goog.webgl.RGB565 = 0x8D62;
+goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable';
 
 
 /**
- * @const
- * @type {number}
+ * Marks a given class (constructor) as an implementation of Thenable, so
+ * that we can query that fact at runtime. The class must have already
+ * implemented the interface.
+ * Exports a 'then' method on the constructor prototype, so that the objects
+ * also implement the extern {@see goog.Thenable} interface for interop with
+ * other Promise implementations.
+ * @param {function(new:goog.Thenable,...[?])} ctor The class constructor. The
+ *     corresponding class must have already implemented the interface.
  */
-goog.webgl.DEPTH_COMPONENT16 = 0x81A5;
+goog.Thenable.addImplementation = function(ctor) {
+  goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then);
+  if (COMPILED) {
+    ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true;
+  } else {
+    // Avoids dictionary access in uncompiled mode.
+    ctor.prototype.$goog_Thenable = true;
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {*} object
+ * @return {boolean} Whether a given instance implements {@code goog.Thenable}.
+ *     The class/superclass of the instance must call {@code addImplementation}.
  */
-goog.webgl.STENCIL_INDEX = 0x1901;
+goog.Thenable.isImplementedBy = function(object) {
+  if (!object) {
+    return false;
+  }
+  try {
+    if (COMPILED) {
+      return !!object[goog.Thenable.IMPLEMENTED_BY_PROP];
+    }
+    return !!object.$goog_Thenable;
+  } catch (e) {
+    // Property access seems to be forbidden.
+    return false;
+  }
+};
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @const
- * @type {number}
+ * @fileoverview Simple notifiers for the Closure testing framework.
+ *
+ * @author johnlenz@google.com (John Lenz)
  */
-goog.webgl.STENCIL_INDEX8 = 0x8D48;
 
+goog.provide('goog.testing.watchers');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.DEPTH_STENCIL = 0x84F9;
+
+/** @private {!Array<function()>} */
+goog.testing.watchers.resetWatchers_ = [];
 
 
 /**
- * @const
- * @type {number}
+ * Fires clock reset watching functions.
  */
-goog.webgl.RENDERBUFFER_WIDTH = 0x8D42;
+goog.testing.watchers.signalClockReset = function() {
+  var watchers = goog.testing.watchers.resetWatchers_;
+  for (var i = 0; i < watchers.length; i++) {
+    goog.testing.watchers.resetWatchers_[i]();
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * Enqueues a function to be called when the clock used for setTimeout is reset.
+ * @param {function()} fn
  */
-goog.webgl.RENDERBUFFER_HEIGHT = 0x8D43;
+goog.testing.watchers.watchClockReset = function(fn) {
+  goog.testing.watchers.resetWatchers_.push(fn);
+};
 
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.RENDERBUFFER_INTERNAL_FORMAT = 0x8D44;
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
+goog.provide('goog.async.run');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.RENDERBUFFER_RED_SIZE = 0x8D50;
+goog.require('goog.async.nextTick');
+goog.require('goog.async.throwException');
+goog.require('goog.testing.watchers');
 
 
 /**
- * @const
- * @type {number}
+ * Fires the provided callback just before the current callstack unwinds, or as
+ * soon as possible after the current JS execution context.
+ * @param {function(this:THIS)} callback
+ * @param {THIS=} opt_context Object to use as the "this value" when calling
+ *     the provided function.
+ * @template THIS
  */
-goog.webgl.RENDERBUFFER_GREEN_SIZE = 0x8D51;
+goog.async.run = function(callback, opt_context) {
+  if (!goog.async.run.schedule_) {
+    goog.async.run.initializeRunner_();
+  }
+  if (!goog.async.run.workQueueScheduled_) {
+    // Nothing is currently scheduled, schedule it now.
+    goog.async.run.schedule_();
+    goog.async.run.workQueueScheduled_ = true;
+  }
+
+  goog.async.run.workQueue_.push(
+      new goog.async.run.WorkItem_(callback, opt_context));
+};
 
 
 /**
- * @const
- * @type {number}
+ * Initializes the function to use to process the work queue.
+ * @private
  */
-goog.webgl.RENDERBUFFER_BLUE_SIZE = 0x8D52;
+goog.async.run.initializeRunner_ = function() {
+  // If native Promises are available in the browser, just schedule the callback
+  // on a fulfilled promise, which is specified to be async, but as fast as
+  // possible.
+  if (goog.global.Promise && goog.global.Promise.resolve) {
+    var promise = goog.global.Promise.resolve();
+    goog.async.run.schedule_ = function() {
+      promise.then(goog.async.run.processWorkQueue);
+    };
+  } else {
+    goog.async.run.schedule_ = function() {
+      goog.async.nextTick(goog.async.run.processWorkQueue);
+    };
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * Forces goog.async.run to use nextTick instead of Promise.
+ *
+ * This should only be done in unit tests. It's useful because MockClock
+ * replaces nextTick, but not the browser Promise implementation, so it allows
+ * Promise-based code to be tested with MockClock.
  */
-goog.webgl.RENDERBUFFER_ALPHA_SIZE = 0x8D53;
+goog.async.run.forceNextTick = function() {
+  goog.async.run.schedule_ = function() {
+    goog.async.nextTick(goog.async.run.processWorkQueue);
+  };
+};
 
 
 /**
- * @const
- * @type {number}
+ * The function used to schedule work asynchronousely.
+ * @private {function()}
  */
-goog.webgl.RENDERBUFFER_DEPTH_SIZE = 0x8D54;
+goog.async.run.schedule_;
 
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.RENDERBUFFER_STENCIL_SIZE = 0x8D55;
+/** @private {boolean} */
+goog.async.run.workQueueScheduled_ = false;
 
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0;
+/** @private {!Array<!goog.async.run.WorkItem_>} */
+goog.async.run.workQueue_ = [];
 
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1;
+if (goog.DEBUG) {
+  /**
+   * Reset the event queue.
+   * @private
+   */
+  goog.async.run.resetQueue_ = function() {
+    goog.async.run.workQueueScheduled_ = false;
+    goog.async.run.workQueue_ = [];
+  };
+
+  // If there is a clock implemenation in use for testing
+  // and it is reset, reset the queue.
+  goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_);
+}
 
 
 /**
- * @const
- * @type {number}
+ * Run any pending goog.async.run work items. This function is not intended
+ * for general use, but for use by entry point handlers to run items ahead of
+ * goog.async.nextTick.
  */
-goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2;
+goog.async.run.processWorkQueue = function() {
+  // NOTE: additional work queue items may be pushed while processing.
+  while (goog.async.run.workQueue_.length) {
+    // Don't let the work queue grow indefinitely.
+    var workItems = goog.async.run.workQueue_;
+    goog.async.run.workQueue_ = [];
+    for (var i = 0; i < workItems.length; i++) {
+      var workItem = workItems[i];
+      try {
+        workItem.fn.call(workItem.scope);
+      } catch (e) {
+        goog.async.throwException(e);
+      }
+    }
+  }
 
+  // There are no more work items, reset the work queue.
+  goog.async.run.workQueueScheduled_ = false;
+};
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3;
 
 
 /**
- * @const
- * @type {number}
+ * @constructor
+ * @final
+ * @struct
+ * @private
+ *
+ * @param {function()} fn
+ * @param {Object|null|undefined} scope
  */
-goog.webgl.COLOR_ATTACHMENT0 = 0x8CE0;
+goog.async.run.WorkItem_ = function(fn, scope) {
+  /** @const */ this.fn = fn;
+  /** @const */ this.scope = scope;
+};
+
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
+goog.provide('goog.promise.Resolver');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.DEPTH_ATTACHMENT = 0x8D00;
 
 
 /**
- * @const
- * @type {number}
+ * Resolver interface for promises. The resolver is a convenience interface that
+ * bundles the promise and its associated resolve and reject functions together,
+ * for cases where the resolver needs to be persisted internally.
+ *
+ * @interface
+ * @template TYPE
  */
-goog.webgl.STENCIL_ATTACHMENT = 0x8D20;
+goog.promise.Resolver = function() {};
 
 
 /**
- * @const
- * @type {number}
+ * The promise that created this resolver.
+ * @type {!goog.Promise<TYPE>}
  */
-goog.webgl.DEPTH_STENCIL_ATTACHMENT = 0x821A;
+goog.promise.Resolver.prototype.promise;
 
 
 /**
- * @const
- * @type {number}
+ * Resolves this resolver with the specified value.
+ * @type {function((TYPE|goog.Promise<TYPE>|Thenable)=)}
  */
-goog.webgl.NONE = 0;
+goog.promise.Resolver.prototype.resolve;
 
 
 /**
- * @const
- * @type {number}
+ * Rejects this resolver with the specified reason.
+ * @type {function(*): void}
  */
-goog.webgl.FRAMEBUFFER_COMPLETE = 0x8CD5;
+goog.promise.Resolver.prototype.reject;
 
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6;
+goog.provide('goog.Promise');
 
+goog.require('goog.Thenable');
+goog.require('goog.asserts');
+goog.require('goog.async.run');
+goog.require('goog.async.throwException');
+goog.require('goog.debug.Error');
+goog.require('goog.promise.Resolver');
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7;
 
 
 /**
- * @const
- * @type {number}
+ * Promises provide a result that may be resolved asynchronously. A Promise may
+ * be resolved by being fulfilled or rejected with a value, which will be known
+ * as the fulfillment value or the rejection reason. Whether fulfilled or
+ * rejected, the Promise result is immutable once it is set.
+ *
+ * Promises may represent results of any type, including undefined. Rejection
+ * reasons are typically Errors, but may also be of any type. Closure Promises
+ * allow for optional type annotations that enforce that fulfillment values are
+ * of the appropriate types at compile time.
+ *
+ * The result of a Promise is accessible by calling {@code then} and registering
+ * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise
+ * resolves, the relevant callbacks are invoked with the fulfillment value or
+ * rejection reason as argument. Callbacks are always invoked in the order they
+ * were registered, even when additional {@code then} calls are made from inside
+ * another callback. A callback is always run asynchronously sometime after the
+ * scope containing the registering {@code then} invocation has returned.
+ *
+ * If a Promise is resolved with another Promise, the first Promise will block
+ * until the second is resolved, and then assumes the same result as the second
+ * Promise. This allows Promises to depend on the results of other Promises,
+ * linking together multiple asynchronous operations.
+ *
+ * This implementation is compatible with the Promises/A+ specification and
+ * passes that specification's conformance test suite. A Closure Promise may be
+ * resolved with a Promise instance (or sufficiently compatible Promise-like
+ * object) created by other Promise implementations. From the specification,
+ * Promise-like objects are known as "Thenables".
+ *
+ * @see http://promisesaplus.com/
+ *
+ * @param {function(
+ *             this:RESOLVER_CONTEXT,
+ *             function((TYPE|IThenable<TYPE>|Thenable)=),
+ *             function(*)): void} resolver
+ *     Initialization function that is invoked immediately with {@code resolve}
+ *     and {@code reject} functions as arguments. The Promise is resolved or
+ *     rejected with the first argument passed to either function.
+ * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the
+ *     resolver function. If unspecified, the resolver function will be executed
+ *     in the default scope.
+ * @constructor
+ * @struct
+ * @final
+ * @implements {goog.Thenable<TYPE>}
+ * @template TYPE,RESOLVER_CONTEXT
  */
-goog.webgl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9;
+goog.Promise = function(resolver, opt_context) {
+  /**
+   * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or
+   * BLOCKED.
+   * @private {goog.Promise.State_}
+   */
+  this.state_ = goog.Promise.State_.PENDING;
 
+  /**
+   * The resolved result of the Promise. Immutable once set with either a
+   * fulfillment value or rejection reason.
+   * @private {*}
+   */
+  this.result_ = undefined;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAMEBUFFER_UNSUPPORTED = 0x8CDD;
+  /**
+   * For Promises created by calling {@code then()}, the originating parent.
+   * @private {goog.Promise}
+   */
+  this.parent_ = null;
 
+  /**
+   * The list of {@code onFulfilled} and {@code onRejected} callbacks added to
+   * this Promise by calls to {@code then()}.
+   * @private {Array<goog.Promise.CallbackEntry_>}
+   */
+  this.callbackEntries_ = null;
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.FRAMEBUFFER_BINDING = 0x8CA6;
+  /**
+   * Whether the Promise is in the queue of Promises to execute.
+   * @private {boolean}
+   */
+  this.executing_ = false;
 
+  if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
+    /**
+     * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater
+     * than 0 milliseconds. The ID is set when the Promise is rejected, and
+     * cleared only if an {@code onRejected} callback is invoked for the
+     * Promise (or one of its descendants) before the delay is exceeded.
+     *
+     * If the rejection is not handled before the timeout completes, the
+     * rejection reason is passed to the unhandled rejection handler.
+     * @private {number}
+     */
+    this.unhandledRejectionId_ = 0;
+  } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
+    /**
+     * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a
+     * boolean that is set if the Promise is rejected, and reset to false if an
+     * {@code onRejected} callback is invoked for the Promise (or one of its
+     * descendants). If the rejection is not handled before the next timestep,
+     * the rejection reason is passed to the unhandled rejection handler.
+     * @private {boolean}
+     */
+    this.hadUnhandledRejection_ = false;
+  }
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.RENDERBUFFER_BINDING = 0x8CA7;
+  if (goog.Promise.LONG_STACK_TRACES) {
+    /**
+     * A list of stack trace frames pointing to the locations where this Promise
+     * was created or had callbacks added to it. Saved to add additional context
+     * to stack traces when an exception is thrown.
+     * @private {!Array<string>}
+     */
+    this.stack_ = [];
+    this.addStackTrace_(new Error('created'));
 
+    /**
+     * Index of the most recently executed stack frame entry.
+     * @private {number}
+     */
+    this.currentStep_ = 0;
+  }
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.MAX_RENDERBUFFER_SIZE = 0x84E8;
+  try {
+    var self = this;
+    resolver.call(
+        opt_context,
+        function(value) {
+          self.resolve_(goog.Promise.State_.FULFILLED, value);
+        },
+        function(reason) {
+          if (goog.DEBUG &&
+              !(reason instanceof goog.Promise.CancellationError)) {
+            try {
+              // Promise was rejected. Step up one call frame to see why.
+              if (reason instanceof Error) {
+                throw reason;
+              } else {
+                throw new Error('Promise rejected.');
+              }
+            } catch (e) {
+              // Only thrown so browser dev tools can catch rejections of
+              // promises when the option to break on caught exceptions is
+              // activated.
+            }
+          }
+          self.resolve_(goog.Promise.State_.REJECTED, reason);
+        });
+  } catch (e) {
+    this.resolve_(goog.Promise.State_.REJECTED, e);
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @define {boolean} Whether traces of {@code then} calls should be included in
+ * exceptions thrown
  */
-goog.webgl.INVALID_FRAMEBUFFER_OPERATION = 0x0506;
+goog.define('goog.Promise.LONG_STACK_TRACES', false);
 
 
 /**
- * @const
- * @type {number}
+ * @define {number} The delay in milliseconds before a rejected Promise's reason
+ * is passed to the rejection handler. By default, the rejection handler
+ * rethrows the rejection reason so that it appears in the developer console or
+ * {@code window.onerror} handler.
+ *
+ * Rejections are rethrown as quickly as possible by default. A negative value
+ * disables rejection handling entirely.
  */
-goog.webgl.UNPACK_FLIP_Y_WEBGL = 0x9240;
+goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0);
 
 
 /**
- * @const
- * @type {number}
+ * The possible internal states for a Promise. These states are not directly
+ * observable to external callers.
+ * @enum {number}
+ * @private
  */
-goog.webgl.UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241;
-
+goog.Promise.State_ = {
+  /** The Promise is waiting for resolution. */
+  PENDING: 0,
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.CONTEXT_LOST_WEBGL = 0x9242;
+  /** The Promise is blocked waiting for the result of another Thenable. */
+  BLOCKED: 1,
 
+  /** The Promise has been resolved with a fulfillment value. */
+  FULFILLED: 2,
 
-/**
- * @const
- * @type {number}
- */
-goog.webgl.UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243;
+  /** The Promise has been resolved with a rejection reason. */
+  REJECTED: 3
+};
 
 
 /**
- * @const
- * @type {number}
+ * Typedef for entries in the callback chain. Each call to {@code then},
+ * {@code thenCatch}, or {@code thenAlways} creates an entry containing the
+ * functions that may be invoked once the Promise is resolved.
+ *
+ * @typedef {{
+ *   child: goog.Promise,
+ *   onFulfilled: function(*),
+ *   onRejected: function(*)
+ * }}
+ * @private
  */
-goog.webgl.BROWSER_DEFAULT_WEBGL = 0x9244;
+goog.Promise.CallbackEntry_;
 
 
 /**
- * From the OES_texture_half_float extension.
- * http://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/
- * @const
- * @type {number}
+ * @param {(TYPE|goog.Thenable<TYPE>|Thenable)=} opt_value
+ * @return {!goog.Promise<TYPE>} A new Promise that is immediately resolved
+ *     with the given value.
+ * @template TYPE
  */
-goog.webgl.HALF_FLOAT_OES = 0x8D61;
+goog.Promise.resolve = function(opt_value) {
+  return new goog.Promise(function(resolve, reject) {
+    resolve(opt_value);
+  });
+};
 
 
 /**
- * From the OES_standard_derivatives extension.
- * http://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/
- * @const
- * @type {number}
+ * @param {*=} opt_reason
+ * @return {!goog.Promise} A new Promise that is immediately rejected with the
+ *     given reason.
  */
-goog.webgl.FRAGMENT_SHADER_DERIVATIVE_HINT_OES = 0x8B8B;
+goog.Promise.reject = function(opt_reason) {
+  return new goog.Promise(function(resolve, reject) {
+    reject(opt_reason);
+  });
+};
 
 
 /**
- * From the OES_vertex_array_object extension.
- * http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/
- * @const
- * @type {number}
+ * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
+ * @return {!goog.Promise<TYPE>} A Promise that receives the result of the
+ *     first Promise (or Promise-like) input to complete.
+ * @template TYPE
  */
-goog.webgl.VERTEX_ARRAY_BINDING_OES = 0x85B5;
+goog.Promise.race = function(promises) {
+  return new goog.Promise(function(resolve, reject) {
+    if (!promises.length) {
+      resolve(undefined);
+    }
+    for (var i = 0, promise; promise = promises[i]; i++) {
+      promise.then(resolve, reject);
+    }
+  });
+};
 
 
 /**
- * From the WEBGL_debug_renderer_info extension.
- * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/
- * @const
- * @type {number}
+ * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
+ * @return {!goog.Promise<!Array<TYPE>>} A Promise that receives a list of
+ *     every fulfilled value once every input Promise (or Promise-like) is
+ *     successfully fulfilled, or is rejected by the first rejection result.
+ * @template TYPE
  */
-goog.webgl.UNMASKED_VENDOR_WEBGL = 0x9245;
+goog.Promise.all = function(promises) {
+  return new goog.Promise(function(resolve, reject) {
+    var toFulfill = promises.length;
+    var values = [];
 
+    if (!toFulfill) {
+      resolve(values);
+      return;
+    }
 
-/**
- * From the WEBGL_debug_renderer_info extension.
- * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/
- * @const
- * @type {number}
- */
-goog.webgl.UNMASKED_RENDERER_WEBGL = 0x9246;
+    var onFulfill = function(index, value) {
+      toFulfill--;
+      values[index] = value;
+      if (toFulfill == 0) {
+        resolve(values);
+      }
+    };
 
+    var onReject = function(reason) {
+      reject(reason);
+    };
 
-/**
- * From the WEBGL_compressed_texture_s3tc extension.
- * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
- * @const
- * @type {number}
- */
-goog.webgl.COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
+    for (var i = 0, promise; promise = promises[i]; i++) {
+      promise.then(goog.partial(onFulfill, i), onReject);
+    }
+  });
+};
 
 
 /**
- * From the WEBGL_compressed_texture_s3tc extension.
- * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
- * @const
- * @type {number}
+ * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
+ * @return {!goog.Promise<TYPE>} A Promise that receives the value of the first
+ *     input to be fulfilled, or is rejected with a list of every rejection
+ *     reason if all inputs are rejected.
+ * @template TYPE
  */
-goog.webgl.COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
+goog.Promise.firstFulfilled = function(promises) {
+  return new goog.Promise(function(resolve, reject) {
+    var toReject = promises.length;
+    var reasons = [];
 
+    if (!toReject) {
+      resolve(undefined);
+      return;
+    }
 
-/**
- * From the WEBGL_compressed_texture_s3tc extension.
- * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
- * @const
- * @type {number}
- */
-goog.webgl.COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
+    var onFulfill = function(value) {
+      resolve(value);
+    };
 
+    var onReject = function(index, reason) {
+      toReject--;
+      reasons[index] = reason;
+      if (toReject == 0) {
+        reject(reasons);
+      }
+    };
 
-/**
- * From the WEBGL_compressed_texture_s3tc extension.
- * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
- * @const
- * @type {number}
- */
-goog.webgl.COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
+    for (var i = 0, promise; promise = promises[i]; i++) {
+      promise.then(onFulfill, goog.partial(onReject, i));
+    }
+  });
+};
 
 
 /**
- * From the EXT_texture_filter_anisotropic extension.
- * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/
- * @const
- * @type {number}
+ * @return {!goog.promise.Resolver<TYPE>} Resolver wrapping the promise and its
+ *     resolve / reject functions. Resolving or rejecting the resolver
+ *     resolves or rejects the promise.
+ * @template TYPE
  */
-goog.webgl.TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE;
+goog.Promise.withResolver = function() {
+  var resolve, reject;
+  var promise = new goog.Promise(function(rs, rj) {
+    resolve = rs;
+    reject = rj;
+  });
+  return new goog.Promise.Resolver_(promise, resolve, reject);
+};
 
 
 /**
- * From the EXT_texture_filter_anisotropic extension.
- * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/
- * @const
- * @type {number}
+ * Adds callbacks that will operate on the result of the Promise, returning a
+ * new child Promise.
+ *
+ * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
+ * with the fulfillment value as argument, and the child Promise will be
+ * fulfilled with the return value of the callback. If the callback throws an
+ * exception, the child Promise will be rejected with the thrown value instead.
+ *
+ * If the Promise is rejected, the {@code onRejected} callback will be invoked
+ * with the rejection reason as argument, and the child Promise will be resolved
+ * with the return value or rejected with the thrown value of the callback.
+ *
+ * @override
  */
-goog.webgl.MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF;
-
-goog.provide('ol.render.webgl.Immediate');
-
-
+goog.Promise.prototype.then = function(
+    opt_onFulfilled, opt_onRejected, opt_context) {
 
-/**
- * @constructor
- * @implements {ol.render.IVectorContext}
- * @param {ol.webgl.Context} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @struct
- */
-ol.render.webgl.Immediate = function(context, pixelRatio) {
-};
+  if (opt_onFulfilled != null) {
+    goog.asserts.assertFunction(opt_onFulfilled,
+        'opt_onFulfilled should be a function.');
+  }
+  if (opt_onRejected != null) {
+    goog.asserts.assertFunction(opt_onRejected,
+        'opt_onRejected should be a function. Did you pass opt_context ' +
+        'as the second argument instead of the third?');
+  }
 
+  if (goog.Promise.LONG_STACK_TRACES) {
+    this.addStackTrace_(new Error('then'));
+  }
 
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) {
+  return this.addChildPromise_(
+      goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null,
+      goog.isFunction(opt_onRejected) ? opt_onRejected : null,
+      opt_context);
 };
+goog.Thenable.addImplementation(goog.Promise);
 
 
 /**
- * @inheritDoc
+ * Adds a callback that will be invoked whether the Promise is fulfilled or
+ * rejected. The callback receives no argument, and no new child Promise is
+ * created. This is useful for ensuring that cleanup takes place after certain
+ * asynchronous operations. Callbacks added with {@code thenAlways} will be
+ * executed in the same order with other calls to {@code then},
+ * {@code thenAlways}, or {@code thenCatch}.
+ *
+ * Since it does not produce a new child Promise, cancellation propagation is
+ * not prevented by adding callbacks with {@code thenAlways}. A Promise that has
+ * a cleanup handler added with {@code thenAlways} will be canceled if all of
+ * its children created by {@code then} (or {@code thenCatch}) are canceled.
+ * Additionally, since any rejections are not passed to the callback, it does
+ * not stop the unhandled rejection handler from running.
+ *
+ * @param {function(this:THIS): void} onResolved A function that will be invoked
+ *     when the Promise is resolved.
+ * @param {THIS=} opt_context An optional context object that will be the
+ *     execution context for the callbacks. By default, functions are executed
+ *     in the global scope.
+ * @return {!goog.Promise<TYPE>} This Promise, for chaining additional calls.
+ * @template THIS
  */
-ol.render.webgl.Immediate.prototype.drawCircleGeometry =
-    function(circleGeometry, data) {
-};
+goog.Promise.prototype.thenAlways = function(onResolved, opt_context) {
+  if (goog.Promise.LONG_STACK_TRACES) {
+    this.addStackTrace_(new Error('thenAlways'));
+  }
 
+  var callback = function() {
+    try {
+      // Ensure that no arguments are passed to onResolved.
+      onResolved.call(opt_context);
+    } catch (err) {
+      goog.Promise.handleRejection_.call(null, err);
+    }
+  };
 
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) {
+  this.addCallbackEntry_({
+    child: null,
+    onRejected: callback,
+    onFulfilled: callback
+  });
+  return this;
 };
 
 
 /**
- * @inheritDoc
+ * Adds a callback that will be invoked only if the Promise is rejected. This
+ * is equivalent to {@code then(null, onRejected)}.
+ *
+ * @param {!function(this:THIS, *): *} onRejected A function that will be
+ *     invoked with the rejection reason if the Promise is rejected.
+ * @param {THIS=} opt_context An optional context object that will be the
+ *     execution context for the callbacks. By default, functions are executed
+ *     in the global scope.
+ * @return {!goog.Promise} A new Promise that will receive the result of the
+ *     callback.
+ * @template THIS
  */
-ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry =
-    function(geometryCollectionGeometry, data) {
+goog.Promise.prototype.thenCatch = function(onRejected, opt_context) {
+  if (goog.Promise.LONG_STACK_TRACES) {
+    this.addStackTrace_(new Error('thenCatch'));
+  }
+  return this.addChildPromise_(null, onRejected, opt_context);
 };
 
 
 /**
- * @inheritDoc
+ * Cancels the Promise if it is still pending by rejecting it with a cancel
+ * Error. No action is performed if the Promise is already resolved.
+ *
+ * All child Promises of the canceled Promise will be rejected with the same
+ * cancel error, as with normal Promise rejection. If the Promise to be canceled
+ * is the only child of a pending Promise, the parent Promise will also be
+ * canceled. Cancellation may propagate upward through multiple generations.
+ *
+ * @param {string=} opt_message An optional debugging message for describing the
+ *     cancellation reason.
  */
-ol.render.webgl.Immediate.prototype.drawPointGeometry =
-    function(pointGeometry, data) {
+goog.Promise.prototype.cancel = function(opt_message) {
+  if (this.state_ == goog.Promise.State_.PENDING) {
+    goog.async.run(function() {
+      var err = new goog.Promise.CancellationError(opt_message);
+      this.cancelInternal_(err);
+    }, this);
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Cancels this Promise with the given error.
+ *
+ * @param {!Error} err The cancellation error.
+ * @private
  */
-ol.render.webgl.Immediate.prototype.drawLineStringGeometry =
-    function(lineStringGeometry, data) {
+goog.Promise.prototype.cancelInternal_ = function(err) {
+  if (this.state_ == goog.Promise.State_.PENDING) {
+    if (this.parent_) {
+      // Cancel the Promise and remove it from the parent's child list.
+      this.parent_.cancelChild_(this, err);
+    } else {
+      this.resolve_(goog.Promise.State_.REJECTED, err);
+    }
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Cancels a child Promise from the list of callback entries. If the Promise has
+ * not already been resolved, reject it with a cancel error. If there are no
+ * other children in the list of callback entries, propagate the cancellation
+ * by canceling this Promise as well.
+ *
+ * @param {!goog.Promise} childPromise The Promise to cancel.
+ * @param {!Error} err The cancel error to use for rejecting the Promise.
+ * @private
  */
-ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry =
-    function(multiLineStringGeometry, data) {
-};
-
+goog.Promise.prototype.cancelChild_ = function(childPromise, err) {
+  if (!this.callbackEntries_) {
+    return;
+  }
+  var childCount = 0;
+  var childIndex = -1;
 
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.drawMultiPointGeometry =
-    function(multiPointGeometry, data) {
+  // Find the callback entry for the childPromise, and count whether there are
+  // additional child Promises.
+  for (var i = 0, entry; entry = this.callbackEntries_[i]; i++) {
+    var child = entry.child;
+    if (child) {
+      childCount++;
+      if (child == childPromise) {
+        childIndex = i;
+      }
+      if (childIndex >= 0 && childCount > 1) {
+        break;
+      }
+    }
+  }
+
+  // If the child Promise was the only child, cancel this Promise as well.
+  // Otherwise, reject only the child Promise with the cancel error.
+  if (childIndex >= 0) {
+    if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) {
+      this.cancelInternal_(err);
+    } else {
+      var callbackEntry = this.callbackEntries_.splice(childIndex, 1)[0];
+      this.executeCallback_(
+          callbackEntry, goog.Promise.State_.REJECTED, err);
+    }
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Adds a callback entry to the current Promise, and schedules callback
+ * execution if the Promise has already been resolved.
+ *
+ * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing
+ *     {@code onFulfilled} and {@code onRejected} callbacks to execute after
+ *     the Promise is resolved.
+ * @private
  */
-ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry =
-    function(multiPolygonGeometry, data) {
+goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) {
+  if ((!this.callbackEntries_ || !this.callbackEntries_.length) &&
+      (this.state_ == goog.Promise.State_.FULFILLED ||
+       this.state_ == goog.Promise.State_.REJECTED)) {
+    this.scheduleCallbacks_();
+  }
+  if (!this.callbackEntries_) {
+    this.callbackEntries_ = [];
+  }
+  this.callbackEntries_.push(callbackEntry);
 };
 
 
 /**
- * @inheritDoc
+ * Creates a child Promise and adds it to the callback entry list. The result of
+ * the child Promise is determined by the state of the parent Promise and the
+ * result of the {@code onFulfilled} or {@code onRejected} callbacks as
+ * specified in the Promise resolution procedure.
+ *
+ * @see http://promisesaplus.com/#the__method
+ *
+ * @param {?function(this:THIS, TYPE):
+ *          (RESULT|goog.Promise<RESULT>|Thenable)} onFulfilled A callback that
+ *     will be invoked if the Promise is fullfilled, or null.
+ * @param {?function(this:THIS, *): *} onRejected A callback that will be
+ *     invoked if the Promise is rejected, or null.
+ * @param {THIS=} opt_context An optional execution context for the callbacks.
+ *     in the default calling context.
+ * @return {!goog.Promise} The child Promise.
+ * @template RESULT,THIS
+ * @private
  */
-ol.render.webgl.Immediate.prototype.drawPolygonGeometry =
-    function(polygonGeometry, data) {
-};
+goog.Promise.prototype.addChildPromise_ = function(
+    onFulfilled, onRejected, opt_context) {
 
+  var callbackEntry = {
+    child: null,
+    onFulfilled: null,
+    onRejected: null
+  };
 
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.drawText =
-    function(flatCoordinates, offset, end, stride, geometry, data) {
-};
+  callbackEntry.child = new goog.Promise(function(resolve, reject) {
+    // Invoke onFulfilled, or resolve with the parent's value if absent.
+    callbackEntry.onFulfilled = onFulfilled ? function(value) {
+      try {
+        var result = onFulfilled.call(opt_context, value);
+        resolve(result);
+      } catch (err) {
+        reject(err);
+      }
+    } : resolve;
 
+    // Invoke onRejected, or reject with the parent's reason if absent.
+    callbackEntry.onRejected = onRejected ? function(reason) {
+      try {
+        var result = onRejected.call(opt_context, reason);
+        if (!goog.isDef(result) &&
+            reason instanceof goog.Promise.CancellationError) {
+          // Propagate cancellation to children if no other result is returned.
+          reject(reason);
+        } else {
+          resolve(result);
+        }
+      } catch (err) {
+        reject(err);
+      }
+    } : reject;
+  });
 
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.setFillStrokeStyle =
-    function(fillStyle, strokeStyle) {
+  callbackEntry.child.parent_ = this;
+  this.addCallbackEntry_(
+      /** @type {goog.Promise.CallbackEntry_} */ (callbackEntry));
+  return callbackEntry.child;
 };
 
 
 /**
- * @inheritDoc
+ * Unblocks the Promise and fulfills it with the given value.
+ *
+ * @param {TYPE} value
+ * @private
  */
-ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
+goog.Promise.prototype.unblockAndFulfill_ = function(value) {
+  goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
+  this.state_ = goog.Promise.State_.PENDING;
+  this.resolve_(goog.Promise.State_.FULFILLED, value);
 };
 
 
 /**
- * @inheritDoc
+ * Unblocks the Promise and rejects it with the given rejection reason.
+ *
+ * @param {*} reason
+ * @private
  */
-ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) {
+goog.Promise.prototype.unblockAndReject_ = function(reason) {
+  goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
+  this.state_ = goog.Promise.State_.PENDING;
+  this.resolve_(goog.Promise.State_.REJECTED, reason);
 };
 
-goog.provide('ol.webgl.shader');
-
-goog.require('goog.functions');
-goog.require('goog.webgl');
-goog.require('ol.webgl');
-
-
 
 /**
- * @constructor
- * @param {string} source Source.
- * @struct
+ * Attempts to resolve a Promise with a given resolution state and value. This
+ * is a no-op if the given Promise has already been resolved.
+ *
+ * If the given result is a Thenable (such as another Promise), the Promise will
+ * be resolved with the same state and result as the Thenable once it is itself
+ * resolved.
+ *
+ * If the given result is not a Thenable, the Promise will be fulfilled or
+ * rejected with that result based on the given state.
+ *
+ * @see http://promisesaplus.com/#the_promise_resolution_procedure
+ *
+ * @param {goog.Promise.State_} state
+ * @param {*} x The result to apply to the Promise.
+ * @private
  */
-ol.webgl.Shader = function(source) {
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.source_ = source;
+goog.Promise.prototype.resolve_ = function(state, x) {
+  if (this.state_ != goog.Promise.State_.PENDING) {
+    return;
+  }
 
-};
+  if (this == x) {
+    state = goog.Promise.State_.REJECTED;
+    x = new TypeError('Promise cannot resolve to itself');
 
+  } else if (goog.Thenable.isImplementedBy(x)) {
+    x = /** @type {!goog.Thenable} */ (x);
+    this.state_ = goog.Promise.State_.BLOCKED;
+    x.then(this.unblockAndFulfill_, this.unblockAndReject_, this);
+    return;
 
-/**
- * @return {number} Type.
- */
-ol.webgl.Shader.prototype.getType = goog.abstractMethod;
+  } else if (goog.isObject(x)) {
+    try {
+      var then = x['then'];
+      if (goog.isFunction(then)) {
+        this.tryThen_(x, then);
+        return;
+      }
+    } catch (e) {
+      state = goog.Promise.State_.REJECTED;
+      x = e;
+    }
+  }
 
+  this.result_ = x;
+  this.state_ = state;
+  this.scheduleCallbacks_();
 
-/**
- * @return {string} Source.
- */
-ol.webgl.Shader.prototype.getSource = function() {
-  return this.source_;
+  if (state == goog.Promise.State_.REJECTED &&
+      !(x instanceof goog.Promise.CancellationError)) {
+    goog.Promise.addUnhandledRejection_(this, x);
+  }
 };
 
 
 /**
- * @return {boolean} Is animated?
+ * Attempts to call the {@code then} method on an object in the hopes that it is
+ * a Promise-compatible instance. This allows interoperation between different
+ * Promise implementations, however a non-compliant object may cause a Promise
+ * to hang indefinitely. If the {@code then} method throws an exception, the
+ * dependent Promise will be rejected with the thrown value.
+ *
+ * @see http://promisesaplus.com/#point-70
+ *
+ * @param {Thenable} thenable An object with a {@code then} method that may be
+ *     compatible with the Promise/A+ specification.
+ * @param {!Function} then The {@code then} method of the Thenable object.
+ * @private
  */
-ol.webgl.Shader.prototype.isAnimated = goog.functions.FALSE;
+goog.Promise.prototype.tryThen_ = function(thenable, then) {
+  this.state_ = goog.Promise.State_.BLOCKED;
+  var promise = this;
+  var called = false;
 
+  var resolve = function(value) {
+    if (!called) {
+      called = true;
+      promise.unblockAndFulfill_(value);
+    }
+  };
 
+  var reject = function(reason) {
+    if (!called) {
+      called = true;
+      promise.unblockAndReject_(reason);
+    }
+  };
 
-/**
- * @constructor
- * @extends {ol.webgl.Shader}
- * @param {string} source Source.
- * @struct
- */
-ol.webgl.shader.Fragment = function(source) {
-  goog.base(this, source);
+  try {
+    then.call(thenable, resolve, reject);
+  } catch (e) {
+    reject(e);
+  }
 };
-goog.inherits(ol.webgl.shader.Fragment, ol.webgl.Shader);
 
 
 /**
- * @inheritDoc
+ * Executes the pending callbacks of a resolved Promise after a timeout.
+ *
+ * Section 2.2.4 of the Promises/A+ specification requires that Promise
+ * callbacks must only be invoked from a call stack that only contains Promise
+ * implementation code, which we accomplish by invoking callback execution after
+ * a timeout. If {@code startExecution_} is called multiple times for the same
+ * Promise, the callback chain will be evaluated only once. Additional callbacks
+ * may be added during the evaluation phase, and will be executed in the same
+ * event loop.
+ *
+ * All Promises added to the waiting list during the same browser event loop
+ * will be executed in one batch to avoid using a separate timeout per Promise.
+ *
+ * @private
  */
-ol.webgl.shader.Fragment.prototype.getType = function() {
-  return goog.webgl.FRAGMENT_SHADER;
+goog.Promise.prototype.scheduleCallbacks_ = function() {
+  if (!this.executing_) {
+    this.executing_ = true;
+    goog.async.run(this.executeCallbacks_, this);
+  }
 };
 
 
-
 /**
- * @constructor
- * @extends {ol.webgl.Shader}
- * @param {string} source Source.
- * @struct
+ * Executes all pending callbacks for this Promise.
+ *
+ * @private
  */
-ol.webgl.shader.Vertex = function(source) {
-  goog.base(this, source);
-};
-goog.inherits(ol.webgl.shader.Vertex, ol.webgl.Shader);
-
+goog.Promise.prototype.executeCallbacks_ = function() {
+  while (this.callbackEntries_ && this.callbackEntries_.length) {
+    var entries = this.callbackEntries_;
+    this.callbackEntries_ = [];
 
-/**
- * @inheritDoc
- */
-ol.webgl.shader.Vertex.prototype.getType = function() {
-  return goog.webgl.VERTEX_SHADER;
+    for (var i = 0; i < entries.length; i++) {
+      if (goog.Promise.LONG_STACK_TRACES) {
+        this.currentStep_++;
+      }
+      this.executeCallback_(entries[i], this.state_, this.result_);
+    }
+  }
+  this.executing_ = false;
 };
 
-// This file is automatically generated, do not edit
-goog.provide('ol.renderer.webgl.map.shader.Color');
-
-goog.require('ol.webgl.shader');
-
-
 
 /**
- * @constructor
- * @extends {ol.webgl.shader.Fragment}
- * @struct
+ * Executes a pending callback for this Promise. Invokes an {@code onFulfilled}
+ * or {@code onRejected} callback based on the resolved state of the Promise.
+ *
+ * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the
+ *     onFulfilled and/or onRejected callbacks for this step.
+ * @param {goog.Promise.State_} state The resolution status of the Promise,
+ *     either FULFILLED or REJECTED.
+ * @param {*} result The resolved result of the Promise.
+ * @private
  */
-ol.renderer.webgl.map.shader.ColorFragment = function() {
-  goog.base(this, ol.renderer.webgl.map.shader.ColorFragment.SOURCE);
+goog.Promise.prototype.executeCallback_ = function(
+    callbackEntry, state, result) {
+  if (state == goog.Promise.State_.FULFILLED) {
+    callbackEntry.onFulfilled(result);
+  } else {
+    if (callbackEntry.child) {
+      this.removeUnhandledRejection_();
+    }
+    callbackEntry.onRejected(result);
+  }
 };
-goog.inherits(ol.renderer.webgl.map.shader.ColorFragment, ol.webgl.shader.Fragment);
-goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorFragment);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_texture, v_texCoord);\n  gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n  gl_FragColor.a = texColor.a * u_opacity;\n}\n';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}';
 
 
 /**
- * @const
- * @type {string}
+ * Records a stack trace entry for functions that call {@code then} or the
+ * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}.
+ *
+ * @param {!Error} err An Error object created by the calling function for
+ *     providing a stack trace.
+ * @private
  */
-ol.renderer.webgl.map.shader.ColorFragment.SOURCE = goog.DEBUG ?
-    ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE :
-    ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE;
-
-
+goog.Promise.prototype.addStackTrace_ = function(err) {
+  if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) {
+    // Extract the third line of the stack trace, which is the entry for the
+    // user function that called into Promise code.
+    var trace = err.stack.split('\n', 4)[3];
+    var message = err.message;
 
-/**
- * @constructor
- * @extends {ol.webgl.shader.Vertex}
- * @struct
- */
-ol.renderer.webgl.map.shader.ColorVertex = function() {
-  goog.base(this, ol.renderer.webgl.map.shader.ColorVertex.SOURCE);
+    // Pad the message to align the traces.
+    message += Array(11 - message.length).join(' ');
+    this.stack_.push(message + trace);
+  }
 };
-goog.inherits(ol.renderer.webgl.map.shader.ColorVertex, ol.webgl.shader.Vertex);
-goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorVertex);
 
 
 /**
- * @const
- * @type {string}
+ * Adds extra stack trace information to an exception for the list of
+ * asynchronous {@code then} calls that have been run for this Promise. Stack
+ * trace information is recorded in {@see #addStackTrace_}, and appended to
+ * rethrown errors when {@code LONG_STACK_TRACES} is enabled.
+ *
+ * @param {*} err An unhandled exception captured during callback execution.
+ * @private
  */
-ol.renderer.webgl.map.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n  v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n';
-
+goog.Promise.prototype.appendLongStack_ = function(err) {
+  if (goog.Promise.LONG_STACK_TRACES &&
+      err && goog.isString(err.stack) && this.stack_.length) {
+    var longTrace = ['Promise trace:'];
 
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.map.shader.ColorVertex.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;}';
+    for (var promise = this; promise; promise = promise.parent_) {
+      for (var i = this.currentStep_; i >= 0; i--) {
+        longTrace.push(promise.stack_[i]);
+      }
+      longTrace.push('Value: ' +
+          '[' + (promise.state_ == goog.Promise.State_.REJECTED ?
+              'REJECTED' : 'FULFILLED') + '] ' +
+          '<' + String(promise.result_) + '>');
+    }
+    err.stack += '\n\n' + longTrace.join('\n');
+  }
+};
 
 
 /**
- * @const
- * @type {string}
+ * Marks this rejected Promise as having being handled. Also marks any parent
+ * Promises in the rejected state as handled. The rejection handler will no
+ * longer be invoked for this Promise (if it has not been called already).
+ *
+ * @private
  */
-ol.renderer.webgl.map.shader.ColorVertex.SOURCE = goog.DEBUG ?
-    ol.renderer.webgl.map.shader.ColorVertex.DEBUG_SOURCE :
-    ol.renderer.webgl.map.shader.ColorVertex.OPTIMIZED_SOURCE;
-
+goog.Promise.prototype.removeUnhandledRejection_ = function() {
+  if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
+    for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) {
+      goog.global.clearTimeout(p.unhandledRejectionId_);
+      p.unhandledRejectionId_ = 0;
+    }
+  } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
+    for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) {
+      p.hadUnhandledRejection_ = false;
+    }
+  }
+};
 
 
 /**
- * @constructor
- * @param {WebGLRenderingContext} gl GL.
- * @param {WebGLProgram} program Program.
- * @struct
+ * Marks this rejected Promise as unhandled. If no {@code onRejected} callback
+ * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY}
+ * expires, the reason will be passed to the unhandled rejection handler. The
+ * handler typically rethrows the rejection reason so that it becomes visible in
+ * the developer console.
+ *
+ * @param {!goog.Promise} promise The rejected Promise.
+ * @param {*} reason The Promise rejection reason.
+ * @private
  */
-ol.renderer.webgl.map.shader.Color.Locations = function(gl, program) {
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_colorMatrix = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_colorMatrix' : 'f');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_opacity = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_opacity' : 'g');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_projectionMatrix = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_projectionMatrix' : 'e');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_texCoordMatrix = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_texCoordMatrix' : 'd');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_texture = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_texture' : 'h');
-
-  /**
-   * @type {number}
-   */
-  this.a_position = gl.getAttribLocation(
-      program, goog.DEBUG ? 'a_position' : 'b');
+goog.Promise.addUnhandledRejection_ = function(promise, reason) {
+  if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
+    promise.unhandledRejectionId_ = goog.global.setTimeout(function() {
+      promise.appendLongStack_(reason);
+      goog.Promise.handleRejection_.call(null, reason);
+    }, goog.Promise.UNHANDLED_REJECTION_DELAY);
 
-  /**
-   * @type {number}
-   */
-  this.a_texCoord = gl.getAttribLocation(
-      program, goog.DEBUG ? 'a_texCoord' : 'c');
+  } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
+    promise.hadUnhandledRejection_ = true;
+    goog.async.run(function() {
+      if (promise.hadUnhandledRejection_) {
+        promise.appendLongStack_(reason);
+        goog.Promise.handleRejection_.call(null, reason);
+      }
+    });
+  }
 };
 
-// This file is automatically generated, do not edit
-goog.provide('ol.renderer.webgl.map.shader.Default');
-
-goog.require('ol.webgl.shader');
-
-
 
 /**
- * @constructor
- * @extends {ol.webgl.shader.Fragment}
- * @struct
+ * A method that is invoked with the rejection reasons for Promises that are
+ * rejected but have no {@code onRejected} callbacks registered yet.
+ * @type {function(*)}
+ * @private
  */
-ol.renderer.webgl.map.shader.DefaultFragment = function() {
-  goog.base(this, ol.renderer.webgl.map.shader.DefaultFragment.SOURCE);
-};
-goog.inherits(ol.renderer.webgl.map.shader.DefaultFragment, ol.webgl.shader.Fragment);
-goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultFragment);
+goog.Promise.handleRejection_ = goog.async.throwException;
 
 
 /**
- * @const
- * @type {string}
+ * Sets a handler that will be called with reasons from unhandled rejected
+ * Promises. If the rejected Promise (or one of its descendants) has an
+ * {@code onRejected} callback registered, the rejection will be considered
+ * handled, and the rejection handler will not be called.
+ *
+ * By default, unhandled rejections are rethrown so that the error may be
+ * captured by the developer console or a {@code window.onerror} handler.
+ *
+ * @param {function(*)} handler A function that will be called with reasons from
+ *     rejected Promises. Defaults to {@code goog.async.throwException}.
  */
-ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_texture, v_texCoord);\n  gl_FragColor.rgb = texColor.rgb;\n  gl_FragColor.a = texColor.a * u_opacity;\n}\n';
+goog.Promise.setUnhandledRejectionHandler = function(handler) {
+  goog.Promise.handleRejection_ = handler;
+};
+
 
 
 /**
- * @const
- * @type {string}
+ * Error used as a rejection reason for canceled Promises.
+ *
+ * @param {string=} opt_message
+ * @constructor
+ * @extends {goog.debug.Error}
+ * @final
  */
-ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}';
+goog.Promise.CancellationError = function(opt_message) {
+  goog.Promise.CancellationError.base(this, 'constructor', opt_message);
+};
+goog.inherits(goog.Promise.CancellationError, goog.debug.Error);
 
 
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.map.shader.DefaultFragment.SOURCE = goog.DEBUG ?
-    ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE :
-    ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE;
+/** @override */
+goog.Promise.CancellationError.prototype.name = 'cancel';
 
 
 
 /**
+ * Internal implementation of the resolver interface.
+ *
+ * @param {!goog.Promise<TYPE>} promise
+ * @param {function((TYPE|goog.Promise<TYPE>|Thenable)=)} resolve
+ * @param {function(*): void} reject
+ * @implements {goog.promise.Resolver<TYPE>}
+ * @final @struct
  * @constructor
- * @extends {ol.webgl.shader.Vertex}
- * @struct
+ * @private
+ * @template TYPE
  */
-ol.renderer.webgl.map.shader.DefaultVertex = function() {
-  goog.base(this, ol.renderer.webgl.map.shader.DefaultVertex.SOURCE);
+goog.Promise.Resolver_ = function(promise, resolve, reject) {
+  /** @const */
+  this.promise = promise;
+
+  /** @const */
+  this.resolve = resolve;
+
+  /** @const */
+  this.reject = reject;
 };
-goog.inherits(ol.renderer.webgl.map.shader.DefaultVertex, ol.webgl.shader.Vertex);
-goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultVertex);
 
+// Copyright 2007 Bob Ippolito. All Rights Reserved.
+// Modifications Copyright 2009 The Closure Library Authors. All Rights
+// Reserved.
 
 /**
- * @const
- * @type {string}
+ * @license Portions of this code are from MochiKit, received by
+ * The Closure Authors under the MIT license. All other code is Copyright
+ * 2005-2009 The Closure Authors. All Rights Reserved.
  */
-ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n  v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n';
-
 
 /**
- * @const
- * @type {string}
+ * @fileoverview Classes for tracking asynchronous operations and handling the
+ * results. The Deferred object here is patterned after the Deferred object in
+ * the Twisted python networking framework.
+ *
+ * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
+ *
+ * Based on the Dojo code which in turn is based on the MochiKit code.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author brenneman@google.com (Shawn Brenneman)
  */
-ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}';
 
+goog.provide('goog.async.Deferred');
+goog.provide('goog.async.Deferred.AlreadyCalledError');
+goog.provide('goog.async.Deferred.CanceledError');
 
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.map.shader.DefaultVertex.SOURCE = goog.DEBUG ?
-    ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE :
-    ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE;
+goog.require('goog.Promise');
+goog.require('goog.Thenable');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.debug.Error');
 
 
 
 /**
+ * A Deferred represents the result of an asynchronous operation. A Deferred
+ * instance has no result when it is created, and is "fired" (given an initial
+ * result) by calling {@code callback} or {@code errback}.
+ *
+ * Once fired, the result is passed through a sequence of callback functions
+ * registered with {@code addCallback} or {@code addErrback}. The functions may
+ * mutate the result before it is passed to the next function in the sequence.
+ *
+ * Callbacks and errbacks may be added at any time, including after the Deferred
+ * has been "fired". If there are no pending actions in the execution sequence
+ * of a fired Deferred, any new callback functions will be called with the last
+ * computed result. Adding a callback function is the only way to access the
+ * result of the Deferred.
+ *
+ * If a Deferred operation is canceled, an optional user-provided cancellation
+ * function is invoked which may perform any special cleanup, followed by firing
+ * the Deferred's errback sequence with a {@code CanceledError}. If the
+ * Deferred has already fired, cancellation is ignored.
+ *
+ * Deferreds may be templated to a specific type they produce using generics
+ * with syntax such as:
+ * <code>
+ *   /** @type {goog.async.Deferred<string>} *&#47;
+ *   var d = new goog.async.Deferred();
+ *   // Compiler can infer that foo is a string.
+ *   d.addCallback(function(foo) {...});
+ *   d.callback('string');  // Checked to be passed a string
+ * </code>
+ * Since deferreds are often used to produce different values across a chain,
+ * the type information is not propagated across chains, but rather only
+ * associated with specifically cast objects.
+ *
+ * @param {Function=} opt_onCancelFunction A function that will be called if the
+ *     Deferred is canceled. If provided, this function runs before the
+ *     Deferred is fired with a {@code CanceledError}.
+ * @param {Object=} opt_defaultScope The default object context to call
+ *     callbacks and errbacks in.
  * @constructor
- * @param {WebGLRenderingContext} gl GL.
- * @param {WebGLProgram} program Program.
- * @struct
+ * @implements {goog.Thenable<VALUE>}
+ * @template VALUE
  */
-ol.renderer.webgl.map.shader.Default.Locations = function(gl, program) {
-
+goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
   /**
-   * @type {WebGLUniformLocation}
+   * Entries in the sequence are arrays containing a callback, an errback, and
+   * an optional scope. The callback or errback in an entry may be null.
+   * @type {!Array<!Array>}
+   * @private
    */
-  this.u_opacity = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_opacity' : 'f');
+  this.sequence_ = [];
 
   /**
-   * @type {WebGLUniformLocation}
+   * Optional function that will be called if the Deferred is canceled.
+   * @type {Function|undefined}
+   * @private
    */
-  this.u_projectionMatrix = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_projectionMatrix' : 'e');
+  this.onCancelFunction_ = opt_onCancelFunction;
 
   /**
-   * @type {WebGLUniformLocation}
+   * The default scope to execute callbacks and errbacks in.
+   * @type {Object}
+   * @private
    */
-  this.u_texCoordMatrix = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_texCoordMatrix' : 'd');
+  this.defaultScope_ = opt_defaultScope || null;
 
   /**
-   * @type {WebGLUniformLocation}
+   * Whether the Deferred has been fired.
+   * @type {boolean}
+   * @private
    */
-  this.u_texture = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_texture' : 'g');
+  this.fired_ = false;
 
   /**
-   * @type {number}
+   * Whether the last result in the execution sequence was an error.
+   * @type {boolean}
+   * @private
    */
-  this.a_position = gl.getAttribLocation(
-      program, goog.DEBUG ? 'a_position' : 'b');
+  this.hadError_ = false;
 
   /**
-   * @type {number}
+   * The current Deferred result, updated as callbacks and errbacks are
+   * executed.
+   * @type {*}
+   * @private
    */
-  this.a_texCoord = gl.getAttribLocation(
-      program, goog.DEBUG ? 'a_texCoord' : 'c');
-};
-
-goog.provide('ol.structs.IntegerSet');
-
-goog.require('goog.asserts');
-
+  this.result_ = undefined;
 
+  /**
+   * Whether the Deferred is blocked waiting on another Deferred to fire. If a
+   * callback or errback returns a Deferred as a result, the execution sequence
+   * is blocked until that Deferred result becomes available.
+   * @type {boolean}
+   * @private
+   */
+  this.blocked_ = false;
 
-/**
- * A set of integers represented as a set of integer ranges.
- * This implementation is designed for the case when the number of distinct
- * integer ranges is small.
- * @constructor
- * @struct
- * @param {Array.<number>=} opt_arr Array.
- */
-ol.structs.IntegerSet = function(opt_arr) {
+  /**
+   * Whether this Deferred is blocking execution of another Deferred. If this
+   * instance was returned as a result in another Deferred's execution
+   * sequence,that other Deferred becomes blocked until this instance's
+   * execution sequence completes. No additional callbacks may be added to a
+   * Deferred once it is blocking another instance.
+   * @type {boolean}
+   * @private
+   */
+  this.blocking_ = false;
 
   /**
+   * Whether the Deferred has been canceled without having a custom cancel
+   * function.
+   * @type {boolean}
    * @private
-   * @type {Array.<number>}
    */
-  this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
+  this.silentlyCanceled_ = false;
 
-  if (goog.DEBUG) {
-    this.assertValid();
-  }
+  /**
+   * If an error is thrown during Deferred execution with no errback to catch
+   * it, the error is rethrown after a timeout. Reporting the error after a
+   * timeout allows execution to continue in the calling context (empty when
+   * no error is scheduled).
+   * @type {number}
+   * @private
+   */
+  this.unhandledErrorId_ = 0;
 
-};
+  /**
+   * If this Deferred was created by branch(), this will be the "parent"
+   * Deferred.
+   * @type {goog.async.Deferred}
+   * @private
+   */
+  this.parent_ = null;
 
+  /**
+   * The number of Deferred objects that have been branched off this one. This
+   * will be decremented whenever a branch is fired or canceled.
+   * @type {number}
+   * @private
+   */
+  this.branches_ = 0;
 
-/**
- * @param {number} addStart Start.
- * @param {number} addStop Stop.
- */
-ol.structs.IntegerSet.prototype.addRange = function(addStart, addStop) {
-  goog.asserts.assert(addStart <= addStop);
-  if (addStart == addStop) {
-    return;
-  }
-  var arr = this.arr_;
-  var n = arr.length;
-  var i;
-  for (i = 0; i < n; i += 2) {
-    if (addStart <= arr[i]) {
-      // FIXME check if splice is really needed
-      arr.splice(i, 0, addStart, addStop);
-      this.compactRanges_();
-      return;
+  if (goog.async.Deferred.LONG_STACK_TRACES) {
+    /**
+     * Holds the stack trace at time of deferred creation if the JS engine
+     * provides the Error.captureStackTrace API.
+     * @private {?string}
+     */
+    this.constructorStack_ = null;
+    if (Error.captureStackTrace) {
+      var target = { stack: '' };
+      Error.captureStackTrace(target, goog.async.Deferred);
+      // Check if Error.captureStackTrace worked. It fails in gjstest.
+      if (typeof target.stack == 'string') {
+        // Remove first line and force stringify to prevent memory leak due to
+        // holding on to actual stack frames.
+        this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, '');
+      }
     }
   }
-  arr.push(addStart, addStop);
-  this.compactRanges_();
-};
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.structs.IntegerSet.prototype.assertValid = function() {
-  var arr = this.arr_;
-  var n = arr.length;
-  goog.asserts.assert(n % 2 === 0);
-  var i;
-  for (i = 1; i < n; ++i) {
-    goog.asserts.assert(arr[i] > arr[i - 1]);
-  }
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @define {boolean} Whether unhandled errors should always get rethrown to the
+ * global scope. Defaults to the value of goog.DEBUG.
  */
-ol.structs.IntegerSet.prototype.clear = function() {
-  this.arr_.length = 0;
-};
+goog.define('goog.async.Deferred.STRICT_ERRORS', false);
 
 
 /**
- * @private
+ * @define {boolean} Whether to attempt to make stack traces long.  Defaults to
+ * the value of goog.DEBUG.
  */
-ol.structs.IntegerSet.prototype.compactRanges_ = function() {
-  var arr = this.arr_;
-  var n = arr.length;
-  var rangeIndex = 0;
-  var i;
-  for (i = 0; i < n; i += 2) {
-    if (arr[i] == arr[i + 1]) {
-      // pass
-    } else if (rangeIndex > 0 &&
-               arr[rangeIndex - 2] <= arr[i] &&
-               arr[i] <= arr[rangeIndex - 1]) {
-      arr[rangeIndex - 1] = Math.max(arr[rangeIndex - 1], arr[i + 1]);
-    } else {
-      arr[rangeIndex++] = arr[i];
-      arr[rangeIndex++] = arr[i + 1];
-    }
-  }
-  arr.length = rangeIndex;
-};
+goog.define('goog.async.Deferred.LONG_STACK_TRACES', false);
 
 
 /**
- * Finds the start of smallest range that is at least of length minSize, or -1
- * if no such range exists.
- * @param {number} minSize Minimum size.
- * @return {number} Index.
+ * Cancels a Deferred that has not yet been fired, or is blocked on another
+ * deferred operation. If this Deferred is waiting for a blocking Deferred to
+ * fire, the blocking Deferred will also be canceled.
+ *
+ * If this Deferred was created by calling branch() on a parent Deferred with
+ * opt_propagateCancel set to true, the parent may also be canceled. If
+ * opt_deepCancel is set, cancel() will be called on the parent (as well as any
+ * other ancestors if the parent is also a branch). If one or more branches were
+ * created with opt_propagateCancel set to true, the parent will be canceled if
+ * cancel() is called on all of those branches.
+ *
+ * @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
+ *     if cancel() hasn't been called on some of the parent's branches. Has no
+ *     effect on a branch without opt_propagateCancel set to true.
  */
-ol.structs.IntegerSet.prototype.findRange = function(minSize) {
-  goog.asserts.assert(minSize > 0);
-  var arr = this.arr_;
-  var n = arr.length;
-  var bestIndex = -1;
-  var bestSize, i, size;
-  for (i = 0; i < n; i += 2) {
-    size = arr[i + 1] - arr[i];
-    if (size == minSize) {
-      return arr[i];
-    } else if (size > minSize && (bestIndex == -1 || size < bestSize)) {
-      bestIndex = arr[i];
-      bestSize = size;
+goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
+  if (!this.hasFired()) {
+    if (this.parent_) {
+      // Get rid of the parent reference before potentially running the parent's
+      // canceler function to ensure that this cancellation isn't
+      // double-counted.
+      var parent = this.parent_;
+      delete this.parent_;
+      if (opt_deepCancel) {
+        parent.cancel(opt_deepCancel);
+      } else {
+        parent.branchCancel_();
+      }
     }
-  }
-  return bestIndex;
-};
-
-
-/**
- * Calls f with each integer range.
- * @param {function(this: T, number, number)} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
- * @template T
- */
-ol.structs.IntegerSet.prototype.forEachRange = function(f, opt_this) {
-  var arr = this.arr_;
-  var n = arr.length;
-  var i;
-  for (i = 0; i < n; i += 2) {
-    f.call(opt_this, arr[i], arr[i + 1]);
-  }
-};
 
-
-/**
- * Calls f with each integer range not in [start, stop) - 'this'.
- * @param {number} start Start.
- * @param {number} stop Stop.
- * @param {function(this: T, number, number)} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
- * @template T
- */
-ol.structs.IntegerSet.prototype.forEachRangeInverted =
-    function(start, stop, f, opt_this) {
-  goog.asserts.assert(start < stop);
-  var arr = this.arr_;
-  var n = arr.length;
-  if (n === 0) {
-    f.call(opt_this, start, stop);
-  } else {
-    if (start < arr[0]) {
-      f.call(opt_this, start, arr[0]);
-    }
-    var i;
-    for (i = 1; i < n - 1; i += 2) {
-      f.call(opt_this, arr[i], arr[i + 1]);
+    if (this.onCancelFunction_) {
+      // Call in user-specified scope.
+      this.onCancelFunction_.call(this.defaultScope_, this);
+    } else {
+      this.silentlyCanceled_ = true;
     }
-    if (arr[n - 1] < stop) {
-      f.call(opt_this, arr[n - 1], stop);
+    if (!this.hasFired()) {
+      this.errback(new goog.async.Deferred.CanceledError(this));
     }
+  } else if (this.result_ instanceof goog.async.Deferred) {
+    this.result_.cancel();
   }
 };
 
 
 /**
- * @return {Array.<number>} Array.
- */
-ol.structs.IntegerSet.prototype.getArray = function() {
-  return this.arr_;
-};
-
-
-/**
- * Returns the first element in the set, or -1 if the set is empty.
- * @return {number} Start.
- */
-ol.structs.IntegerSet.prototype.getFirst = function() {
-  return this.arr_.length === 0 ? -1 : this.arr_[0];
-};
-
-
-/**
- * Returns the first integer after the last element in the set, or -1 if the
- * set is empty.
- * @return {number} Last.
- */
-ol.structs.IntegerSet.prototype.getLast = function() {
-  var n = this.arr_.length;
-  return n === 0 ? -1 : this.arr_[n - 1];
-};
-
-
-/**
- * Returns the number of integers in the set.
- * @return {number} Size.
- */
-ol.structs.IntegerSet.prototype.getSize = function() {
-  var arr = this.arr_;
-  var n = arr.length;
-  var size = 0;
-  var i;
-  for (i = 0; i < n; i += 2) {
-    size += arr[i + 1] - arr[i];
-  }
-  return size;
-};
-
-
-/**
- * @param {number} start Start.
- * @param {number} stop Stop.
- * @return {boolean} Intersects range.
+ * Handle a single branch being canceled. Once all branches are canceled, this
+ * Deferred will be canceled as well.
+ *
+ * @private
  */
-ol.structs.IntegerSet.prototype.intersectsRange = function(start, stop) {
-  goog.asserts.assert(start <= stop);
-  if (start == stop) {
-    return false;
-  } else {
-    var arr = this.arr_;
-    var n = arr.length;
-    var i = 0;
-    for (i = 0; i < n; i += 2) {
-      if (arr[i] <= start && start < arr[i + 1] ||
-          arr[i] < stop && stop - 1 < arr[i + 1] ||
-          start < arr[i] && arr[i + 1] <= stop) {
-        return true;
-      }
-    }
-    return false;
+goog.async.Deferred.prototype.branchCancel_ = function() {
+  this.branches_--;
+  if (this.branches_ <= 0) {
+    this.cancel();
   }
 };
 
 
 /**
- * @return {boolean} Is empty.
+ * Called after a blocking Deferred fires. Unblocks this Deferred and resumes
+ * its execution sequence.
+ *
+ * @param {boolean} isSuccess Whether the result is a success or an error.
+ * @param {*} res The result of the blocking Deferred.
+ * @private
  */
-ol.structs.IntegerSet.prototype.isEmpty = function() {
-  return this.arr_.length === 0;
+goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
+  this.blocked_ = false;
+  this.updateResult_(isSuccess, res);
 };
 
 
 /**
- * @return {Array.<number>} Array.
+ * Updates the current result based on the success or failure of the last action
+ * in the execution sequence.
+ *
+ * @param {boolean} isSuccess Whether the new result is a success or an error.
+ * @param {*} res The result.
+ * @private
  */
-ol.structs.IntegerSet.prototype.pack = function() {
-  return this.arr_;
+goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
+  this.fired_ = true;
+  this.result_ = res;
+  this.hadError_ = !isSuccess;
+  this.fire_();
 };
 
 
 /**
- * @param {number} removeStart Start.
- * @param {number} removeStop Stop.
+ * Verifies that the Deferred has not yet been fired.
+ *
+ * @private
+ * @throws {Error} If this has already been fired.
  */
-ol.structs.IntegerSet.prototype.removeRange =
-    function(removeStart, removeStop) {
-  // FIXME this could be more efficient
-  goog.asserts.assert(removeStart <= removeStop);
-  var arr = this.arr_;
-  var n = arr.length;
-  var i;
-  for (i = 0; i < n; i += 2) {
-    if (removeStop < arr[i] || arr[i + 1] < removeStart) {
-      continue;
-    } else if (arr[i] > removeStop) {
-      break;
-    }
-    if (removeStart < arr[i]) {
-      if (removeStop == arr[i]) {
-        break;
-      } else if (removeStop < arr[i + 1]) {
-        arr[i] = Math.max(arr[i], removeStop);
-        break;
-      } else {
-        arr.splice(i, 2);
-        i -= 2;
-        n -= 2;
-      }
-    } else if (removeStart == arr[i]) {
-      if (removeStop < arr[i + 1]) {
-        arr[i] = removeStop;
-        break;
-      } else if (removeStop == arr[i + 1]) {
-        arr.splice(i, 2);
-        break;
-      } else {
-        arr.splice(i, 2);
-        i -= 2;
-        n -= 2;
-      }
-    } else {
-      if (removeStop < arr[i + 1]) {
-        arr.splice(i, 2, arr[i], removeStart, removeStop, arr[i + 1]);
-        break;
-      } else if (removeStop == arr[i + 1]) {
-        arr[i + 1] = removeStart;
-        break;
-      } else {
-        arr[i + 1] = removeStart;
-      }
+goog.async.Deferred.prototype.check_ = function() {
+  if (this.hasFired()) {
+    if (!this.silentlyCanceled_) {
+      throw new goog.async.Deferred.AlreadyCalledError(this);
     }
+    this.silentlyCanceled_ = false;
   }
-  this.compactRanges_();
-};
-
-
-if (goog.DEBUG) {
-
-  /**
-   * @return {string} String.
-   */
-  ol.structs.IntegerSet.prototype.toString = function() {
-    var arr = this.arr_;
-    var n = arr.length;
-    var result = new Array(n / 2);
-    var resultIndex = 0;
-    var i;
-    for (i = 0; i < n; i += 2) {
-      result[resultIndex++] = arr[i] + '-' + arr[i + 1];
-    }
-    return result.join(', ');
-  };
-
-}
-
-goog.provide('ol.structs.Buffer');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.webgl');
-goog.require('ol');
-goog.require('ol.structs.IntegerSet');
-
-
-/**
- * @enum {number}
- */
-ol.structs.BufferUsage = {
-  STATIC_DRAW: goog.webgl.STATIC_DRAW,
-  STREAM_DRAW: goog.webgl.STREAM_DRAW,
-  DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW
 };
 
 
-
 /**
- * @constructor
- * @param {Array.<number>=} opt_arr Array.
- * @param {number=} opt_used Used.
- * @param {number=} opt_usage Usage.
- * @struct
+ * Fire the execution sequence for this Deferred by passing the starting result
+ * to the first registered callback.
+ * @param {VALUE=} opt_result The starting result.
  */
-ol.structs.Buffer = function(opt_arr, opt_used, opt_usage) {
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
-
-  /**
-   * @private
-   * @type {Array.<ol.structs.IntegerSet>}
-   */
-  this.dirtySets_ = [];
-
-  /**
-   * @private
-   * @type {ol.structs.IntegerSet}
-   */
-  this.freeSet_ = new ol.structs.IntegerSet();
-
-  var used = goog.isDef(opt_used) ? opt_used : this.arr_.length;
-  if (used < this.arr_.length) {
-    this.freeSet_.addRange(used, this.arr_.length);
-  }
-  if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) {
-    var arr = this.arr_;
-    var n = arr.length;
-    var i;
-    for (i = used; i < n; ++i) {
-      arr[i] = NaN;
-    }
-  }
-
-  /**
-   * @private
-   * @type {?Float32Array}
-   */
-  this.split32_ = null;
-
-  /**
-   * @private
-   * @type {ol.structs.IntegerSet}
-   */
-  this.split32DirtySet_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.usage_ = goog.isDef(opt_usage) ?
-      opt_usage : ol.structs.BufferUsage.STATIC_DRAW;
-
+goog.async.Deferred.prototype.callback = function(opt_result) {
+  this.check_();
+  this.assertNotDeferred_(opt_result);
+  this.updateResult_(true /* isSuccess */, opt_result);
 };
 
 
 /**
- * @param {number} size Size.
- * @return {number} Offset.
+ * Fire the execution sequence for this Deferred by passing the starting error
+ * result to the first registered errback.
+ * @param {*=} opt_result The starting error.
  */
-ol.structs.Buffer.prototype.allocate = function(size) {
-  goog.asserts.assert(size > 0);
-  var offset = this.freeSet_.findRange(size);
-  goog.asserts.assert(offset != -1);  // FIXME
-  this.freeSet_.removeRange(offset, offset + size);
-  return offset;
+goog.async.Deferred.prototype.errback = function(opt_result) {
+  this.check_();
+  this.assertNotDeferred_(opt_result);
+  this.makeStackTraceLong_(opt_result);
+  this.updateResult_(false /* isSuccess */, opt_result);
 };
 
 
 /**
- * @param {Array.<number>} values Values.
- * @return {number} Offset.
+ * Attempt to make the error's stack trace be long in that it contains the
+ * stack trace from the point where the deferred was created on top of the
+ * current stack trace to give additional context.
+ * @param {*} error
+ * @private
  */
-ol.structs.Buffer.prototype.add = function(values) {
-  var size = values.length;
-  var offset = this.allocate(size);
-  var i;
-  for (i = 0; i < size; ++i) {
-    this.arr_[offset + i] = values[i];
+goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
+  if (!goog.async.Deferred.LONG_STACK_TRACES) {
+    return;
+  }
+  if (this.constructorStack_ && goog.isObject(error) && error.stack &&
+      // Stack looks like it was system generated. See
+      // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
+      (/^[^\n]+(\n   [^\n]+)+/).test(error.stack)) {
+    error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
+        this.constructorStack_;
   }
-  this.markDirty(size, offset);
-  return offset;
 };
 
 
 /**
- * @param {ol.structs.IntegerSet} dirtySet Dirty set.
+ * Asserts that an object is not a Deferred.
+ * @param {*} obj The object to test.
+ * @throws {Error} Throws an exception if the object is a Deferred.
+ * @private
  */
-ol.structs.Buffer.prototype.addDirtySet = function(dirtySet) {
-  goog.asserts.assert(!goog.array.contains(this.dirtySets_, dirtySet));
-  this.dirtySets_.push(dirtySet);
+goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
+  goog.asserts.assert(
+      !(obj instanceof goog.async.Deferred),
+      'An execution sequence may not be initiated with a blocking Deferred.');
 };
 
 
 /**
- * @param {function(this: T, number, number)} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
+ * Register a callback function to be called with a successful result. If no
+ * value is returned by the callback function, the result value is unchanged. If
+ * a new value is returned, it becomes the Deferred result and will be passed to
+ * the next callback in the execution sequence.
+ *
+ * If the function throws an error, the error becomes the new result and will be
+ * passed to the next errback in the execution chain.
+ *
+ * If the function returns a Deferred, the execution sequence will be blocked
+ * until that Deferred fires. Its result will be passed to the next callback (or
+ * errback if it is an error result) in this Deferred's execution sequence.
+ *
+ * @param {!function(this:T,VALUE):?} cb The function to be called with a
+ *     successful result.
+ * @param {T=} opt_scope An optional scope to call the callback in.
+ * @return {!goog.async.Deferred} This Deferred.
  * @template T
  */
-ol.structs.Buffer.prototype.forEachRange = function(f, opt_this) {
-  if (this.arr_.length !== 0) {
-    this.freeSet_.forEachRangeInverted(0, this.arr_.length, f, opt_this);
-  }
+goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
+  return this.addCallbacks(cb, null, opt_scope);
 };
 
 
 /**
- * @return {Array.<number>} Array.
+ * Register a callback function to be called with an error result. If no value
+ * is returned by the function, the error result is unchanged. If a new error
+ * value is returned or thrown, that error becomes the Deferred result and will
+ * be passed to the next errback in the execution sequence.
+ *
+ * If the errback function handles the error by returning a non-error value,
+ * that result will be passed to the next normal callback in the sequence.
+ *
+ * If the function returns a Deferred, the execution sequence will be blocked
+ * until that Deferred fires. Its result will be passed to the next callback (or
+ * errback if it is an error result) in this Deferred's execution sequence.
+ *
+ * @param {!function(this:T,?):?} eb The function to be called on an
+ *     unsuccessful result.
+ * @param {T=} opt_scope An optional scope to call the errback in.
+ * @return {!goog.async.Deferred<VALUE>} This Deferred.
+ * @template T
  */
-ol.structs.Buffer.prototype.getArray = function() {
-  return this.arr_;
+goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
+  return this.addCallbacks(null, eb, opt_scope);
 };
 
 
 /**
- * @return {number} Count.
+ * Registers one function as both a callback and errback.
+ *
+ * @param {!function(this:T,?):?} f The function to be called on any result.
+ * @param {T=} opt_scope An optional scope to call the function in.
+ * @return {!goog.async.Deferred} This Deferred.
+ * @template T
  */
-ol.structs.Buffer.prototype.getCount = function() {
-  return this.arr_.length - this.freeSet_.getSize();
+goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
+  return this.addCallbacks(f, f, opt_scope);
 };
 
 
 /**
- * @return {ol.structs.IntegerSet} Free set.
+ * Registers a callback function and an errback function at the same position
+ * in the execution sequence. Only one of these functions will execute,
+ * depending on the error state during the execution sequence.
+ *
+ * NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
+ * the callback is invoked, the errback will be skipped, and vice versa.
+ *
+ * @param {(function(this:T,VALUE):?)|null} cb The function to be called on a
+ *     successful result.
+ * @param {(function(this:T,?):?)|null} eb The function to be called on an
+ *     unsuccessful result.
+ * @param {T=} opt_scope An optional scope to call the functions in.
+ * @return {!goog.async.Deferred} This Deferred.
+ * @template T
  */
-ol.structs.Buffer.prototype.getFreeSet = function() {
-  return this.freeSet_;
+goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
+  goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
+  this.sequence_.push([cb, eb, opt_scope]);
+  if (this.hasFired()) {
+    this.fire_();
+  }
+  return this;
 };
 
 
 /**
- * Returns a Float32Array twice the length of the buffer containing each value
- * split into two 32-bit floating point values that, when added together,
- * approximate the original value. Even indicies contain the high bits, odd
- * indicies contain the low bits.
- * @see http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/
- * @return {Float32Array} Split.
+ * Implements {@see goog.Thenable} for seamless integration with
+ * {@see goog.Promise}.
+ * Deferred results are mutable and may represent multiple values over
+ * their lifetime. Calling {@code then} on a Deferred returns a Promise
+ * with the result of the Deferred at that point in its callback chain.
+ * Note that if the Deferred result is never mutated, and only
+ * {@code then} calls are made, the Deferred will behave like a Promise.
+ *
+ * @override
  */
-ol.structs.Buffer.prototype.getSplit32 = function() {
-  var arr = this.arr_;
-  var n = arr.length;
-  if (goog.isNull(this.split32DirtySet_)) {
-    this.split32DirtySet_ = new ol.structs.IntegerSet([0, n]);
-    this.addDirtySet(this.split32DirtySet_);
-  }
-  if (goog.isNull(this.split32_)) {
-    this.split32_ = new Float32Array(2 * n);
-  }
-  var split32 = this.split32_;
-  this.split32DirtySet_.forEachRange(function(start, stop) {
-    var doubleHigh, i, j, value;
-    for (i = start, j = 2 * start; i < stop; ++i, j += 2) {
-      value = arr[i];
-      if (value < 0) {
-        doubleHigh = 65536 * Math.floor(-value / 65536);
-        split32[j] = -doubleHigh;
-        split32[j + 1] = value + doubleHigh;
-      } else {
-        doubleHigh = 65536 * Math.floor(value / 65536);
-        split32[j] = doubleHigh;
-        split32[j + 1] = value - doubleHigh;
-      }
+goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected,
+    opt_context) {
+  var resolve, reject;
+  var promise = new goog.Promise(function(res, rej) {
+    // Copying resolvers to outer scope, so that they are available when the
+    // deferred callback fires (which may be synchronous).
+    resolve = res;
+    reject = rej;
+  });
+  this.addCallbacks(resolve, function(reason) {
+    if (reason instanceof goog.async.Deferred.CanceledError) {
+      promise.cancel();
+    } else {
+      reject(reason);
     }
   });
-  this.split32DirtySet_.clear();
-  return this.split32_;
+  return promise.then(opt_onFulfilled, opt_onRejected, opt_context);
 };
+goog.Thenable.addImplementation(goog.async.Deferred);
 
 
 /**
- * @return {number} Usage.
+ * Links another Deferred to the end of this Deferred's execution sequence. The
+ * result of this execution sequence will be passed as the starting result for
+ * the chained Deferred, invoking either its first callback or errback.
+ *
+ * @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
+ * @return {!goog.async.Deferred} This Deferred.
  */
-ol.structs.Buffer.prototype.getUsage = function() {
-  return this.usage_;
+goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
+  this.addCallbacks(
+      otherDeferred.callback, otherDeferred.errback, otherDeferred);
+  return this;
 };
 
 
 /**
- * @param {number} size Size.
- * @param {number} offset Offset.
+ * Makes this Deferred wait for another Deferred's execution sequence to
+ * complete before continuing.
+ *
+ * This is equivalent to adding a callback that returns {@code otherDeferred},
+ * but doesn't prevent additional callbacks from being added to
+ * {@code otherDeferred}.
+ *
+ * @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred
+ *     to wait for.
+ * @return {!goog.async.Deferred} This Deferred.
  */
-ol.structs.Buffer.prototype.markDirty = function(size, offset) {
-  var i, ii;
-  for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) {
-    this.dirtySets_[i].addRange(offset, offset + size);
+goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
+  if (!(otherDeferred instanceof goog.async.Deferred)) {
+    // The Thenable case.
+    return this.addCallback(function() {
+      return otherDeferred;
+    });
   }
+  return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
 };
 
 
 /**
- * @param {number} size Size.
- * @param {number} offset Offset.
+ * Creates a branch off this Deferred's execution sequence, and returns it as a
+ * new Deferred. The branched Deferred's starting result will be shared with the
+ * parent at the point of the branch, even if further callbacks are added to the
+ * parent.
+ *
+ * All branches at the same stage in the execution sequence will receive the
+ * same starting value.
+ *
+ * @param {boolean=} opt_propagateCancel If cancel() is called on every child
+ *     branch created with opt_propagateCancel, the parent will be canceled as
+ *     well.
+ * @return {!goog.async.Deferred<VALUE>} A Deferred that will be started with
+ *     the computed result from this stage in the execution sequence.
  */
-ol.structs.Buffer.prototype.remove = function(size, offset) {
-  var i, ii;
-  this.freeSet_.addRange(offset, offset + size);
-  for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) {
-    this.dirtySets_[i].removeRange(offset, offset + size);
-  }
-  if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) {
-    var arr = this.arr_;
-    for (i = 0; i < size; ++i) {
-      arr[offset + i] = NaN;
-    }
+goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
+  var d = new goog.async.Deferred();
+  this.chainDeferred(d);
+  if (opt_propagateCancel) {
+    d.parent_ = this;
+    this.branches_++;
   }
+  return d;
 };
 
 
 /**
- * @param {ol.structs.IntegerSet} dirtySet Dirty set.
+ * @return {boolean} Whether the execution sequence has been started on this
+ *     Deferred by invoking {@code callback} or {@code errback}.
  */
-ol.structs.Buffer.prototype.removeDirtySet = function(dirtySet) {
-  var removed = goog.array.remove(this.dirtySets_, dirtySet);
-  goog.asserts.assert(removed);
+goog.async.Deferred.prototype.hasFired = function() {
+  return this.fired_;
 };
 
 
 /**
- * @param {Array.<number>} values Values.
- * @param {number} offset Offset.
+ * @param {*} res The latest result in the execution sequence.
+ * @return {boolean} Whether the current result is an error that should cause
+ *     the next errback to fire. May be overridden by subclasses to handle
+ *     special error types.
+ * @protected
  */
-ol.structs.Buffer.prototype.set = function(values, offset) {
-  var arr = this.arr_;
-  var n = values.length;
-  goog.asserts.assert(0 <= offset && offset + n <= arr.length);
-  var i;
-  for (i = 0; i < n; ++i) {
-    arr[offset + i] = values[i];
-  }
-  this.markDirty(n, offset);
+goog.async.Deferred.prototype.isError = function(res) {
+  return res instanceof Error;
 };
 
-goog.provide('ol.renderer.webgl.Layer');
-
-goog.require('goog.vec.Mat4');
-goog.require('goog.webgl');
-goog.require('ol.color.Matrix');
-goog.require('ol.layer.Layer');
-goog.require('ol.render.Event');
-goog.require('ol.render.EventType');
-goog.require('ol.render.webgl.Immediate');
-goog.require('ol.renderer.Layer');
-goog.require('ol.renderer.webgl.map.shader.Color');
-goog.require('ol.renderer.webgl.map.shader.Default');
-goog.require('ol.structs.Buffer');
-
-
 
 /**
- * @constructor
- * @extends {ol.renderer.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Layer} layer Layer.
+ * @return {boolean} Whether an errback exists in the remaining sequence.
+ * @private
  */
-ol.renderer.webgl.Layer = function(mapRenderer, layer) {
-
-  goog.base(this, mapRenderer, layer);
-
-  /**
-   * @private
-   * @type {ol.structs.Buffer}
-   */
-  this.arrayBuffer_ = new ol.structs.Buffer([
-    -1, -1, 0, 0,
-    1, -1, 1, 0,
-    -1, 1, 0, 1,
-    1, 1, 1, 1
-  ]);
-
-  /**
-   * @protected
-   * @type {WebGLTexture}
-   */
-  this.texture = null;
-
-  /**
-   * @protected
-   * @type {WebGLFramebuffer}
-   */
-  this.framebuffer = null;
-
-  /**
-   * @protected
-   * @type {number|undefined}
-   */
-  this.framebufferDimension = undefined;
-
-  /**
-   * @protected
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.texCoordMatrix = goog.vec.Mat4.createNumber();
-
-  /**
-   * @protected
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.projectionMatrix = goog.vec.Mat4.createNumberIdentity();
-
-  /**
-   * @private
-   * @type {ol.color.Matrix}
-   */
-  this.colorMatrix_ = new ol.color.Matrix();
-
-  /**
-   * @private
-   * @type {ol.renderer.webgl.map.shader.Color.Locations}
-   */
-  this.colorLocations_ = null;
-
-  /**
-   * @private
-   * @type {ol.renderer.webgl.map.shader.Default.Locations}
-   */
-  this.defaultLocations_ = null;
-
+goog.async.Deferred.prototype.hasErrback_ = function() {
+  return goog.array.some(this.sequence_, function(sequenceRow) {
+    // The errback is the second element in the array.
+    return goog.isFunction(sequenceRow[1]);
+  });
 };
-goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @param {number} framebufferDimension Framebuffer dimension.
- * @protected
+ * Exhausts the execution sequence while a result is available. The result may
+ * be modified by callbacks or errbacks, and execution will block if the
+ * returned result is an incomplete Deferred.
+ *
+ * @private
  */
-ol.renderer.webgl.Layer.prototype.bindFramebuffer =
-    function(frameState, framebufferDimension) {
-
-  var mapRenderer = this.getWebGLMapRenderer();
-  var gl = mapRenderer.getGL();
-
-  if (!goog.isDef(this.framebufferDimension) ||
-      this.framebufferDimension != framebufferDimension) {
-
-    frameState.postRenderFunctions.push(
-        goog.partial(
-            /**
-             * @param {WebGLRenderingContext} gl GL.
-             * @param {WebGLFramebuffer} framebuffer Framebuffer.
-             * @param {WebGLTexture} texture Texture.
-             */
-            function(gl, framebuffer, texture) {
-              if (!gl.isContextLost()) {
-                gl.deleteFramebuffer(framebuffer);
-                gl.deleteTexture(texture);
-              }
-            }, gl, this.framebuffer, this.texture));
-
-    var texture = gl.createTexture();
-    gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
-    gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
-        framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA,
-        goog.webgl.UNSIGNED_BYTE, null);
-    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER,
-        goog.webgl.LINEAR);
-    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER,
-        goog.webgl.LINEAR);
-
-    var framebuffer = gl.createFramebuffer();
-    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer);
-    gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER,
-        goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0);
-
-    this.texture = texture;
-    this.framebuffer = framebuffer;
-    this.framebufferDimension = framebufferDimension;
+goog.async.Deferred.prototype.fire_ = function() {
+  if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) {
+    // It is possible to add errbacks after the Deferred has fired. If a new
+    // errback is added immediately after the Deferred encountered an unhandled
+    // error, but before that error is rethrown, the error is unscheduled.
+    goog.async.Deferred.unscheduleError_(this.unhandledErrorId_);
+    this.unhandledErrorId_ = 0;
+  }
 
-  } else {
-    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer);
+  if (this.parent_) {
+    this.parent_.branches_--;
+    delete this.parent_;
   }
 
-};
+  var res = this.result_;
+  var unhandledException = false;
+  var isNewlyBlocked = false;
 
+  while (this.sequence_.length && !this.blocked_) {
+    var sequenceEntry = this.sequence_.shift();
 
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.layer.LayerState} layerState Layer state.
- * @param {ol.webgl.Context} context Context.
- */
-ol.renderer.webgl.Layer.prototype.composeFrame =
-    function(frameState, layerState, context) {
+    var callback = sequenceEntry[0];
+    var errback = sequenceEntry[1];
+    var scope = sequenceEntry[2];
 
-  this.dispatchComposeEvent_(
-      ol.render.EventType.PRECOMPOSE, context, frameState);
+    var f = this.hadError_ ? errback : callback;
+    if (f) {
+      /** @preserveTry */
+      try {
+        var ret = f.call(scope || this.defaultScope_, res);
 
-  context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
+        // If no result, then use previous result.
+        if (goog.isDef(ret)) {
+          // Bubble up the error as long as the return value hasn't changed.
+          this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
+          this.result_ = res = ret;
+        }
 
-  var gl = context.getGL();
+        if (goog.Thenable.isImplementedBy(res)) {
+          isNewlyBlocked = true;
+          this.blocked_ = true;
+        }
 
-  var useColor =
-      layerState.brightness ||
-      layerState.contrast != 1 ||
-      layerState.hue ||
-      layerState.saturation != 1;
+      } catch (ex) {
+        res = ex;
+        this.hadError_ = true;
+        this.makeStackTraceLong_(res);
 
-  var fragmentShader, vertexShader;
-  if (useColor) {
-    fragmentShader = ol.renderer.webgl.map.shader.ColorFragment.getInstance();
-    vertexShader = ol.renderer.webgl.map.shader.ColorVertex.getInstance();
-  } else {
-    fragmentShader =
-        ol.renderer.webgl.map.shader.DefaultFragment.getInstance();
-    vertexShader = ol.renderer.webgl.map.shader.DefaultVertex.getInstance();
+        if (!this.hasErrback_()) {
+          // If an error is thrown with no additional errbacks in the queue,
+          // prepare to rethrow the error.
+          unhandledException = true;
+        }
+      }
+    }
   }
 
-  var program = context.getProgram(fragmentShader, vertexShader);
+  this.result_ = res;
 
-  // FIXME colorLocations_ and defaultLocations_ should be shared somehow
-  var locations;
-  if (useColor) {
-    if (goog.isNull(this.colorLocations_)) {
-      locations =
-          new ol.renderer.webgl.map.shader.Color.Locations(gl, program);
-      this.colorLocations_ = locations;
-    } else {
-      locations = this.colorLocations_;
-    }
-  } else {
-    if (goog.isNull(this.defaultLocations_)) {
-      locations =
-          new ol.renderer.webgl.map.shader.Default.Locations(gl, program);
-      this.defaultLocations_ = locations;
+  if (isNewlyBlocked) {
+    var onCallback = goog.bind(this.continue_, this, true /* isSuccess */);
+    var onErrback = goog.bind(this.continue_, this, false /* isSuccess */);
+
+    if (res instanceof goog.async.Deferred) {
+      res.addCallbacks(onCallback, onErrback);
+      res.blocking_ = true;
     } else {
-      locations = this.defaultLocations_;
+      res.then(onCallback, onErrback);
     }
+  } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
+      !(res instanceof goog.async.Deferred.CanceledError)) {
+    this.hadError_ = true;
+    unhandledException = true;
   }
 
-  if (context.useProgram(program)) {
-    gl.enableVertexAttribArray(locations.a_position);
-    gl.vertexAttribPointer(
-        locations.a_position, 2, goog.webgl.FLOAT, false, 16, 0);
-    gl.enableVertexAttribArray(locations.a_texCoord);
-    gl.vertexAttribPointer(
-        locations.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8);
-    gl.uniform1i(locations.u_texture, 0);
-  }
-
-  gl.uniformMatrix4fv(
-      locations.u_texCoordMatrix, false, this.getTexCoordMatrix());
-  gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
-      this.getProjectionMatrix());
-  if (useColor) {
-    gl.uniformMatrix4fv(locations.u_colorMatrix, false,
-        this.colorMatrix_.getMatrix(
-            layerState.brightness,
-            layerState.contrast,
-            layerState.hue,
-            layerState.saturation
-        ));
-  }
-  gl.uniform1f(locations.u_opacity, layerState.opacity);
-  gl.bindTexture(goog.webgl.TEXTURE_2D, this.getTexture());
-  gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
-
-  this.dispatchComposeEvent_(
-      ol.render.EventType.POSTCOMPOSE, context, frameState);
-
-};
-
-
-/**
- * @param {ol.render.EventType} type Event type.
- * @param {ol.webgl.Context} context WebGL context.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ =
-    function(type, context, frameState) {
-  var layer = this.getLayer();
-  if (layer.hasListener(type)) {
-    var render = new ol.render.webgl.Immediate(context, frameState.pixelRatio);
-    var composeEvent = new ol.render.Event(
-        type, layer, render, null, frameState, null, context);
-    layer.dispatchEvent(composeEvent);
+  if (unhandledException) {
+    // Rethrow the unhandled error after a timeout. Execution will continue, but
+    // the error will be seen by global handlers and the user. The throw will
+    // be canceled if another errback is appended before the timeout executes.
+    // The error's original stack trace is preserved where available.
+    this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res);
   }
 };
 
 
 /**
- * @protected
- * @return {ol.renderer.webgl.Map} MapRenderer.
+ * Creates a Deferred that has an initial result.
+ *
+ * @param {*=} opt_result The result.
+ * @return {!goog.async.Deferred} The new Deferred.
  */
-ol.renderer.webgl.Layer.prototype.getWebGLMapRenderer = function() {
-  return /** @type {ol.renderer.webgl.Map} */ (this.getMapRenderer());
+goog.async.Deferred.succeed = function(opt_result) {
+  var d = new goog.async.Deferred();
+  d.callback(opt_result);
+  return d;
 };
 
 
 /**
- * @return {!goog.vec.Mat4.Number} Matrix.
+ * Creates a Deferred that fires when the given promise resolves.
+ * Use only during migration to Promises.
+ *
+ * @param {!goog.Promise<T>} promise
+ * @return {!goog.async.Deferred<T>} The new Deferred.
+ * @template T
  */
-ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
-  return this.texCoordMatrix;
+goog.async.Deferred.fromPromise = function(promise) {
+  var d = new goog.async.Deferred();
+  d.callback();
+  d.addCallback(function() {
+    return promise;
+  });
+  return d;
 };
 
 
 /**
- * @return {WebGLTexture} Texture.
+ * Creates a Deferred that has an initial error result.
+ *
+ * @param {*} res The error result.
+ * @return {!goog.async.Deferred} The new Deferred.
  */
-ol.renderer.webgl.Layer.prototype.getTexture = function() {
-  return this.texture;
+goog.async.Deferred.fail = function(res) {
+  var d = new goog.async.Deferred();
+  d.errback(res);
+  return d;
 };
 
 
 /**
- * @return {!goog.vec.Mat4.Number} Matrix.
+ * Creates a Deferred that has already been canceled.
+ *
+ * @return {!goog.async.Deferred} The new Deferred.
  */
-ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
-  return this.projectionMatrix;
+goog.async.Deferred.canceled = function() {
+  var d = new goog.async.Deferred();
+  d.cancel();
+  return d;
 };
 
 
 /**
- * Handle webglcontextlost.
+ * Normalizes values that may or may not be Deferreds.
+ *
+ * If the input value is a Deferred, the Deferred is branched (so the original
+ * execution sequence is not modified) and the input callback added to the new
+ * branch. The branch is returned to the caller.
+ *
+ * If the input value is not a Deferred, the callback will be executed
+ * immediately and an already firing Deferred will be returned to the caller.
+ *
+ * In the following (contrived) example, if <code>isImmediate</code> is true
+ * then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
+ *
+ * <pre>
+ * var value;
+ * if (isImmediate) {
+ *   value = 3;
+ * } else {
+ *   value = new goog.async.Deferred();
+ *   setTimeout(function() { value.callback(6); }, 2000);
+ * }
+ *
+ * var d = goog.async.Deferred.when(value, alert);
+ * </pre>
+ *
+ * @param {*} value Deferred or normal value to pass to the callback.
+ * @param {!function(this:T, ?):?} callback The callback to execute.
+ * @param {T=} opt_scope An optional scope to call the callback in.
+ * @return {!goog.async.Deferred} A new Deferred that will call the input
+ *     callback with the input value.
+ * @template T
  */
-ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
-  this.texture = null;
-  this.framebuffer = null;
-  this.framebufferDimension = undefined;
+goog.async.Deferred.when = function(value, callback, opt_scope) {
+  if (value instanceof goog.async.Deferred) {
+    return value.branch(true).addCallback(callback, opt_scope);
+  } else {
+    return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
+  }
 };
 
-goog.provide('ol.renderer.webgl.ImageLayer');
-
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.vec.Mat4');
-goog.require('goog.webgl');
-goog.require('ol.Coordinate');
-goog.require('ol.Extent');
-goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
-goog.require('ol.ViewHint');
-goog.require('ol.extent');
-goog.require('ol.layer.Image');
-goog.require('ol.renderer.webgl.Layer');
-
 
 
 /**
+ * An error sub class that is used when a Deferred has already been called.
+ * @param {!goog.async.Deferred} deferred The Deferred.
+ *
  * @constructor
- * @extends {ol.renderer.webgl.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Image} imageLayer Tile layer.
+ * @extends {goog.debug.Error}
  */
-ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) {
-
-  goog.base(this, mapRenderer, imageLayer);
+goog.async.Deferred.AlreadyCalledError = function(deferred) {
+  goog.debug.Error.call(this);
 
   /**
-   * The last rendered image.
-   * @private
-   * @type {?ol.ImageBase}
+   * The Deferred that raised this error.
+   * @type {goog.async.Deferred}
    */
-  this.image_ = null;
-
+  this.deferred = deferred;
 };
-goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);
-
-
-/**
- * @param {ol.ImageBase} image Image.
- * @private
- * @return {WebGLTexture} Texture.
- */
-ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
-
-  // We meet the conditions to work with non-power of two textures.
-  // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support
-  // http://learningwebgl.com/blog/?p=2101
+goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
 
-  var imageElement = image.getImageElement();
-  var gl = this.getWebGLMapRenderer().getGL();
 
-  var texture = gl.createTexture();
+/** @override */
+goog.async.Deferred.AlreadyCalledError.prototype.message =
+    'Deferred has already fired';
 
-  gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
-  gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
-      goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, imageElement);
 
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S,
-      goog.webgl.CLAMP_TO_EDGE);
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T,
-      goog.webgl.CLAMP_TO_EDGE);
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR);
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR);
+/** @override */
+goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
 
-  return texture;
-};
 
 
 /**
- * @inheritDoc
+ * An error sub class that is used when a Deferred is canceled.
+ *
+ * @param {!goog.async.Deferred} deferred The Deferred object.
+ * @constructor
+ * @extends {goog.debug.Error}
  */
-ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtPixel =
-    function(coordinate, frameState, callback, thisArg) {
-  var layer = this.getLayer();
-  var source = layer.getSource();
-  var extent = frameState.extent;
-  var resolution = frameState.viewState.resolution;
-  var rotation = frameState.viewState.rotation;
-  var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtPixel(
-      extent, resolution, rotation, coordinate, skippedFeatureUids,
+goog.async.Deferred.CanceledError = function(deferred) {
+  goog.debug.Error.call(this);
 
-      /**
-       * @param {ol.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        return callback.call(thisArg, feature, layer);
-      });
+  /**
+   * The Deferred that raised this error.
+   * @type {goog.async.Deferred}
+   */
+  this.deferred = deferred;
 };
+goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
 
 
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.ImageLayer.prototype.prepareFrame =
-    function(frameState, layerState) {
-
-  var gl = this.getWebGLMapRenderer().getGL();
-
-  var viewState = frameState.viewState;
-  var viewCenter = viewState.center;
-  var viewResolution = viewState.resolution;
-  var viewRotation = viewState.rotation;
-
-  var image = this.image_;
-  var texture = this.texture;
-  var imageLayer = this.getLayer();
-  goog.asserts.assertInstanceof(imageLayer, ol.layer.Image);
-  var imageSource = imageLayer.getSource();
-
-  var hints = frameState.viewHints;
-
-  var renderedExtent = frameState.extent;
-  if (goog.isDef(layerState.extent)) {
-    renderedExtent = ol.extent.getIntersection(
-        renderedExtent, layerState.extent);
-  }
-  if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
-      !ol.extent.isEmpty(renderedExtent)) {
-    var image_ = imageSource.getImage(renderedExtent, viewResolution,
-        frameState.pixelRatio, viewState.projection);
-    if (!goog.isNull(image_)) {
-      var imageState = image_.getState();
-      if (imageState == ol.ImageState.IDLE) {
-        goog.events.listenOnce(image_, goog.events.EventType.CHANGE,
-            this.handleImageChange, false, this);
-        image_.load();
-      } else if (imageState == ol.ImageState.LOADED) {
-        image = image_;
-        texture = this.createTexture_(image_);
-        if (!goog.isNull(this.texture)) {
-          frameState.postRenderFunctions.push(
-              goog.partial(
-                  /**
-                   * @param {WebGLRenderingContext} gl GL.
-                   * @param {WebGLTexture} texture Texture.
-                   */
-                  function(gl, texture) {
-                    if (!gl.isContextLost()) {
-                      gl.deleteTexture(texture);
-                    }
-                  }, gl, this.texture));
-        }
-      }
-    }
-  }
-
-  if (!goog.isNull(image)) {
-    goog.asserts.assert(!goog.isNull(texture));
-
-    var canvas = this.getWebGLMapRenderer().getContext().getCanvas();
-
-    this.updateProjectionMatrix_(canvas.width, canvas.height,
-        viewCenter, viewResolution, viewRotation, image.getExtent());
-
-    // Translate and scale to flip the Y coord.
-    var texCoordMatrix = this.texCoordMatrix;
-    goog.vec.Mat4.makeIdentity(texCoordMatrix);
-    goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1);
-    goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0);
+/** @override */
+goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
 
-    this.image_ = image;
-    this.texture = texture;
 
-    this.updateAttributions(frameState.attributions, image.getAttributions());
-    this.updateLogos(frameState, imageSource);
-  }
+/** @override */
+goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';
 
-  return true;
-};
 
 
 /**
- * @param {number} canvasWidth Canvas width.
- * @param {number} canvasHeight Canvas height.
- * @param {ol.Coordinate} viewCenter View center.
- * @param {number} viewResolution View resolution.
- * @param {number} viewRotation View rotation.
- * @param {ol.Extent} imageExtent Image extent.
+ * Wrapper around errors that are scheduled to be thrown by failing deferreds
+ * after a timeout.
+ *
+ * @param {*} error Error from a failing deferred.
+ * @constructor
+ * @final
  * @private
+ * @struct
  */
-ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ =
-    function(canvasWidth, canvasHeight, viewCenter,
-        viewResolution, viewRotation, imageExtent) {
-
-  var canvasExtentWidth = canvasWidth * viewResolution;
-  var canvasExtentHeight = canvasHeight * viewResolution;
-
-  var projectionMatrix = this.projectionMatrix;
-  goog.vec.Mat4.makeIdentity(projectionMatrix);
-  goog.vec.Mat4.scale(projectionMatrix,
-      2 / canvasExtentWidth, 2 / canvasExtentHeight, 1);
-  goog.vec.Mat4.rotateZ(projectionMatrix, -viewRotation);
-  goog.vec.Mat4.translate(projectionMatrix,
-      imageExtent[0] - viewCenter[0],
-      imageExtent[1] - viewCenter[1],
-      0);
-  goog.vec.Mat4.scale(projectionMatrix,
-      (imageExtent[2] - imageExtent[0]) / 2,
-      (imageExtent[3] - imageExtent[1]) / 2,
-      1);
-  goog.vec.Mat4.translate(projectionMatrix, 1, 1, 0);
+goog.async.Deferred.Error_ = function(error) {
+  /** @const @private {number} */
+  this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0);
 
+  /** @const @private {*} */
+  this.error_ = error;
 };
 
-// This file is automatically generated, do not edit
-goog.provide('ol.renderer.webgl.tilelayer.shader');
-
-goog.require('ol.webgl.shader');
-
-
 
 /**
- * @constructor
- * @extends {ol.webgl.shader.Fragment}
- * @struct
+ * Actually throws the error and removes it from the list of pending
+ * deferred errors.
  */
-ol.renderer.webgl.tilelayer.shader.Fragment = function() {
-  goog.base(this, ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE);
+goog.async.Deferred.Error_.prototype.throwError = function() {
+  goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_],
+      'Cannot throw an error that is not scheduled.');
+  delete goog.async.Deferred.errorMap_[this.id_];
+  throw this.error_;
 };
-goog.inherits(ol.renderer.webgl.tilelayer.shader.Fragment, ol.webgl.shader.Fragment);
-goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Fragment);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n';
 
 
 /**
- * @const
- * @type {string}
+ * Resets the error throw timer.
  */
-ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}';
+goog.async.Deferred.Error_.prototype.resetTimer = function() {
+  goog.global.clearTimeout(this.id_);
+};
 
 
 /**
- * @const
- * @type {string}
+ * Map of unhandled errors scheduled to be rethrown in a future timestep.
+ * @private {!Object<number|string, goog.async.Deferred.Error_>}
  */
-ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE = goog.DEBUG ?
-    ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE :
-    ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE;
-
+goog.async.Deferred.errorMap_ = {};
 
 
 /**
- * @constructor
- * @extends {ol.webgl.shader.Vertex}
- * @struct
+ * Schedules an error to be thrown after a delay.
+ * @param {*} error Error from a failing deferred.
+ * @return {number} Id of the error.
+ * @private
  */
-ol.renderer.webgl.tilelayer.shader.Vertex = function() {
-  goog.base(this, ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE);
+goog.async.Deferred.scheduleError_ = function(error) {
+  var deferredError = new goog.async.Deferred.Error_(error);
+  goog.async.Deferred.errorMap_[deferredError.id_] = deferredError;
+  return deferredError.id_;
 };
-goog.inherits(ol.renderer.webgl.tilelayer.shader.Vertex, ol.webgl.shader.Vertex);
-goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Vertex);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n  gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n  v_texCoord = a_texCoord;\n}\n\n\n';
 
 
 /**
- * @const
- * @type {string}
+ * Unschedules an error from being thrown.
+ * @param {number} id Id of the deferred error to unschedule.
+ * @private
  */
-ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}';
+goog.async.Deferred.unscheduleError_ = function(id) {
+  var error = goog.async.Deferred.errorMap_[id];
+  if (error) {
+    error.resetTimer();
+    delete goog.async.Deferred.errorMap_[id];
+  }
+};
 
 
 /**
- * @const
- * @type {string}
+ * Asserts that there are no pending deferred errors. If there are any
+ * scheduled errors, one will be thrown immediately to make this function fail.
  */
-ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE = goog.DEBUG ?
-    ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE :
-    ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE;
-
+goog.async.Deferred.assertNoErrors = function() {
+  var map = goog.async.Deferred.errorMap_;
+  for (var key in map) {
+    var error = map[key];
+    error.resetTimer();
+    error.throwError();
+  }
+};
 
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @constructor
- * @param {WebGLRenderingContext} gl GL.
- * @param {WebGLProgram} program Program.
- * @struct
+ * @fileoverview A wrapper for the HTML5 FileError object.
+ *
  */
-ol.renderer.webgl.tilelayer.shader.Locations = function(gl, program) {
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_texture = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_texture' : 'e');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_tileOffset = gl.getUniformLocation(
-      program, goog.DEBUG ? 'u_tileOffset' : 'd');
-
-  /**
-   * @type {number}
-   */
-  this.a_position = gl.getAttribLocation(
-      program, goog.DEBUG ? 'a_position' : 'b');
-
-  /**
-   * @type {number}
-   */
-  this.a_texCoord = gl.getAttribLocation(
-      program, goog.DEBUG ? 'a_texCoord' : 'c');
-};
-
-// FIXME large resolutions lead to too large framebuffers :-(
-// FIXME animated shaders! check in redraw
 
-goog.provide('ol.renderer.webgl.TileLayer');
+goog.provide('goog.fs.Error');
+goog.provide('goog.fs.Error.ErrorCode');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
+goog.require('goog.debug.Error');
 goog.require('goog.object');
-goog.require('goog.vec.Mat4');
-goog.require('goog.vec.Vec4');
-goog.require('goog.webgl');
-goog.require('ol.TileRange');
-goog.require('ol.TileState');
-goog.require('ol.extent');
-goog.require('ol.layer.Tile');
-goog.require('ol.math');
-goog.require('ol.renderer.webgl.Layer');
-goog.require('ol.renderer.webgl.tilelayer.shader');
-goog.require('ol.structs.Buffer');
-goog.require('ol.tilecoord');
+goog.require('goog.string');
 
 
 
 /**
+ * A filesystem error. Since the filesystem API is asynchronous, stack traces
+ * are less useful for identifying where errors come from, so this includes a
+ * large amount of metadata in the message.
+ *
+ * @param {!DOMError} error
+ * @param {string} action The action being undertaken when the error was raised.
  * @constructor
- * @extends {ol.renderer.webgl.Layer}
- * @param {ol.renderer.Map} mapRenderer Map renderer.
- * @param {ol.layer.Tile} tileLayer Tile layer.
+ * @extends {goog.debug.Error}
+ * @final
  */
-ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
-
-  goog.base(this, mapRenderer, tileLayer);
-
-  /**
-   * @private
-   * @type {ol.webgl.shader.Fragment}
-   */
-  this.fragmentShader_ =
-      ol.renderer.webgl.tilelayer.shader.Fragment.getInstance();
-
-  /**
-   * @private
-   * @type {ol.webgl.shader.Vertex}
-   */
-  this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance();
-
-  /**
-   * @private
-   * @type {ol.renderer.webgl.tilelayer.shader.Locations}
-   */
-  this.locations_ = null;
-
-  /**
-   * @private
-   * @type {ol.structs.Buffer}
-   */
-  this.renderArrayBuffer_ = new ol.structs.Buffer([
-    0, 0, 0, 1,
-    1, 0, 1, 1,
-    0, 1, 0, 0,
-    1, 1, 1, 0
-  ]);
+goog.fs.Error = function(error, action) {
+  /** @type {string} */
+  this.name;
 
   /**
-   * @private
-   * @type {ol.TileRange}
+   * @type {goog.fs.Error.ErrorCode}
+   * @deprecated Use the 'name' or 'message' field instead.
    */
-  this.renderedTileRange_ = null;
+  this.code;
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.renderedFramebufferExtent_ = null;
+  if (goog.isDef(error.name)) {
+    this.name = error.name;
+    // TODO(user): Remove warning suppression after JSCompiler stops
+    // firing a spurious warning here.
+    /** @suppress {deprecated} */
+    this.code = goog.fs.Error.getCodeFromName_(error.name);
+  } else {
+    this.code = error.code;
+    this.name = goog.fs.Error.getNameFromCode_(error.code);
+  }
+  goog.fs.Error.base(this, 'constructor',
+      goog.string.subs('%s %s', this.name, action));
+};
+goog.inherits(goog.fs.Error, goog.debug.Error);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = -1;
 
+/**
+ * Names of errors that may be thrown by the File API, the File System API, or
+ * the File Writer API.
+ *
+ * @see http://dev.w3.org/2006/webapi/FileAPI/#ErrorAndException
+ * @see http://www.w3.org/TR/file-system-api/#definitions
+ * @see http://dev.w3.org/2009/dap/file-system/file-writer.html#definitions
+ * @enum {string}
+ */
+goog.fs.Error.ErrorName = {
+  ABORT: 'AbortError',
+  ENCODING: 'EncodingError',
+  INVALID_MODIFICATION: 'InvalidModificationError',
+  INVALID_STATE: 'InvalidStateError',
+  NOT_FOUND: 'NotFoundError',
+  NOT_READABLE: 'NotReadableError',
+  NO_MODIFICATION_ALLOWED: 'NoModificationAllowedError',
+  PATH_EXISTS: 'PathExistsError',
+  QUOTA_EXCEEDED: 'QuotaExceededError',
+  SECURITY: 'SecurityError',
+  SYNTAX: 'SyntaxError',
+  TYPE_MISMATCH: 'TypeMismatchError'
 };
-goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
 
 
 /**
- * @inheritDoc
+ * Error codes for file errors.
+ * @see http://www.w3.org/TR/file-system-api/#idl-def-FileException
+ *
+ * @enum {number}
+ * @deprecated Use the 'name' or 'message' attribute instead.
  */
-ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
-  var mapRenderer = this.getWebGLMapRenderer();
-  var context = mapRenderer.getContext();
-  context.deleteBuffer(this.renderArrayBuffer_);
-  goog.base(this, 'disposeInternal');
+goog.fs.Error.ErrorCode = {
+  NOT_FOUND: 1,
+  SECURITY: 2,
+  ABORT: 3,
+  NOT_READABLE: 4,
+  ENCODING: 5,
+  NO_MODIFICATION_ALLOWED: 6,
+  INVALID_STATE: 7,
+  SYNTAX: 8,
+  INVALID_MODIFICATION: 9,
+  QUOTA_EXCEEDED: 10,
+  TYPE_MISMATCH: 11,
+  PATH_EXISTS: 12
 };
 
 
 /**
- * @inheritDoc
+ * @param {goog.fs.Error.ErrorCode} code
+ * @return {string} name
+ * @private
  */
-ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
-  goog.base(this, 'handleWebGLContextLost');
-  this.locations_ = null;
+goog.fs.Error.getNameFromCode_ = function(code) {
+  var name = goog.object.findKey(goog.fs.Error.NameToCodeMap_, function(c) {
+    return code == c;
+  });
+  if (!goog.isDef(name)) {
+    throw new Error('Invalid code: ' + code);
+  }
+  return name;
 };
 
 
 /**
- * @inheritDoc
+ * Returns the code that corresponds to the given name.
+ * @param {string} name
+ * @return {goog.fs.Error.ErrorCode} code
+ * @private
  */
-ol.renderer.webgl.TileLayer.prototype.prepareFrame =
-    function(frameState, layerState) {
+goog.fs.Error.getCodeFromName_ = function(name) {
+  return goog.fs.Error.NameToCodeMap_[name];
+};
 
-  var mapRenderer = this.getWebGLMapRenderer();
-  var context = mapRenderer.getContext();
-  var gl = mapRenderer.getGL();
 
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
+/**
+ * Mapping from error names to values from the ErrorCode enum.
+ * @see http://www.w3.org/TR/file-system-api/#definitions.
+ * @private {!Object<string, goog.fs.Error.ErrorCode>}
+ */
+goog.fs.Error.NameToCodeMap_ = goog.object.create(
+    goog.fs.Error.ErrorName.ABORT,
+    goog.fs.Error.ErrorCode.ABORT,
 
-  var tileLayer = this.getLayer();
-  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
-  var tileSource = tileLayer.getSource();
-  var tileGrid = tileSource.getTileGridForProjection(projection);
-  var z = tileGrid.getZForResolution(viewState.resolution);
-  var tileResolution = tileGrid.getResolution(z);
+    goog.fs.Error.ErrorName.ENCODING,
+    goog.fs.Error.ErrorCode.ENCODING,
 
-  var tilePixelSize =
-      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
-  var pixelRatio = tilePixelSize / tileGrid.getTileSize(z);
-  var tilePixelResolution = tileResolution / pixelRatio;
-  var tileGutter = tileSource.getGutter();
+    goog.fs.Error.ErrorName.INVALID_MODIFICATION,
+    goog.fs.Error.ErrorCode.INVALID_MODIFICATION,
 
-  var center = viewState.center;
-  var extent;
-  if (tileResolution == viewState.resolution) {
-    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
-    extent = ol.extent.getForViewAndSize(
-        center, tileResolution, viewState.rotation, frameState.size);
-  } else {
-    extent = frameState.extent;
-  }
-  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
-      extent, tileResolution);
+    goog.fs.Error.ErrorName.INVALID_STATE,
+    goog.fs.Error.ErrorCode.INVALID_STATE,
 
-  var framebufferExtent;
-  if (!goog.isNull(this.renderedTileRange_) &&
-      this.renderedTileRange_.equals(tileRange) &&
-      this.renderedRevision_ == tileSource.getRevision()) {
-    framebufferExtent = this.renderedFramebufferExtent_;
-  } else {
+    goog.fs.Error.ErrorName.NOT_FOUND,
+    goog.fs.Error.ErrorCode.NOT_FOUND,
 
-    var tileRangeSize = tileRange.getSize();
+    goog.fs.Error.ErrorName.NOT_READABLE,
+    goog.fs.Error.ErrorCode.NOT_READABLE,
 
-    var maxDimension = Math.max(
-        tileRangeSize[0] * tilePixelSize, tileRangeSize[1] * tilePixelSize);
-    var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension);
-    var framebufferExtentDimension = tilePixelResolution * framebufferDimension;
-    var origin = tileGrid.getOrigin(z);
-    var minX = origin[0] + tileRange.minX * tilePixelSize * tilePixelResolution;
-    var minY = origin[1] + tileRange.minY * tilePixelSize * tilePixelResolution;
-    framebufferExtent = [
-      minX, minY,
-      minX + framebufferExtentDimension, minY + framebufferExtentDimension
-    ];
+    goog.fs.Error.ErrorName.NO_MODIFICATION_ALLOWED,
+    goog.fs.Error.ErrorCode.NO_MODIFICATION_ALLOWED,
 
-    this.bindFramebuffer(frameState, framebufferDimension);
-    gl.viewport(0, 0, framebufferDimension, framebufferDimension);
+    goog.fs.Error.ErrorName.PATH_EXISTS,
+    goog.fs.Error.ErrorCode.PATH_EXISTS,
 
-    gl.clearColor(0, 0, 0, 0);
-    gl.clear(goog.webgl.COLOR_BUFFER_BIT);
-    gl.disable(goog.webgl.BLEND);
+    goog.fs.Error.ErrorName.QUOTA_EXCEEDED,
+    goog.fs.Error.ErrorCode.QUOTA_EXCEEDED,
 
-    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
-    context.useProgram(program);
-    if (goog.isNull(this.locations_)) {
-      this.locations_ =
-          new ol.renderer.webgl.tilelayer.shader.Locations(gl, program);
-    }
+    goog.fs.Error.ErrorName.SECURITY,
+    goog.fs.Error.ErrorCode.SECURITY,
 
-    context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.renderArrayBuffer_);
-    gl.enableVertexAttribArray(this.locations_.a_position);
-    gl.vertexAttribPointer(
-        this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0);
-    gl.enableVertexAttribArray(this.locations_.a_texCoord);
-    gl.vertexAttribPointer(
-        this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8);
-    gl.uniform1i(this.locations_.u_texture, 0);
+    goog.fs.Error.ErrorName.SYNTAX,
+    goog.fs.Error.ErrorCode.SYNTAX,
 
-    /**
-     * @type {Object.<number, Object.<string, ol.Tile>>}
-     */
-    var tilesToDrawByZ = {};
-    tilesToDrawByZ[z] = {};
+    goog.fs.Error.ErrorName.TYPE_MISMATCH,
+    goog.fs.Error.ErrorCode.TYPE_MISMATCH);
 
-    var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
-      return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED &&
-          mapRenderer.isTileTextureLoaded(tile);
-    }, tileSource, pixelRatio, projection);
-    var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
-        tilesToDrawByZ, getTileIfLoaded);
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-    if (!goog.isDef(useInterimTilesOnError)) {
-      useInterimTilesOnError = true;
-    }
+/**
+ * @fileoverview A wrapper for the HTML5 File ProgressEvent objects.
+ *
+ */
+goog.provide('goog.fs.ProgressEvent');
 
-    var allTilesLoaded = true;
-    var tmpExtent = ol.extent.createEmpty();
-    var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-    var childTileRange, fullyLoaded, tile, tileState, x, y, tileExtent;
-    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+goog.require('goog.events.Event');
 
-        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-        if (goog.isDef(layerState.extent)) {
-          // ignore tiles outside layer extent
-          tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
-          if (!ol.extent.intersects(tileExtent, layerState.extent)) {
-            continue;
-          }
-        }
-        tileState = tile.getState();
-        if (tileState == ol.TileState.LOADED) {
-          if (mapRenderer.isTileTextureLoaded(tile)) {
-            tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
-            continue;
-          }
-        } else if (tileState == ol.TileState.EMPTY ||
-                   (tileState == ol.TileState.ERROR &&
-                    !useInterimTilesOnError)) {
-          continue;
-        }
 
-        allTilesLoaded = false;
-        fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
-            tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
-        if (!fullyLoaded) {
-          childTileRange = tileGrid.getTileCoordChildTileRange(
-              tile.tileCoord, tmpTileRange, tmpExtent);
-          if (!goog.isNull(childTileRange)) {
-            findLoadedTiles(z + 1, childTileRange);
-          }
-        }
 
-      }
+/**
+ * A wrapper for the progress events emitted by the File APIs.
+ *
+ * @param {!ProgressEvent} event The underlying event object.
+ * @param {!Object} target The file access object emitting the event.
+ * @extends {goog.events.Event}
+ * @constructor
+ * @final
+ */
+goog.fs.ProgressEvent = function(event, target) {
+  goog.fs.ProgressEvent.base(this, 'constructor', event.type, target);
 
-    }
+  /**
+   * The underlying event object.
+   * @type {!ProgressEvent}
+   * @private
+   */
+  this.event_ = event;
+};
+goog.inherits(goog.fs.ProgressEvent, goog.events.Event);
 
-    /** @type {Array.<number>} */
-    var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
-    goog.array.sort(zs);
-    var u_tileOffset = goog.vec.Vec4.createFloat32();
-    var i, ii, sx, sy, tileKey, tilesToDraw, tx, ty;
-    for (i = 0, ii = zs.length; i < ii; ++i) {
-      tilesToDraw = tilesToDrawByZ[zs[i]];
-      for (tileKey in tilesToDraw) {
-        tile = tilesToDraw[tileKey];
-        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
-        sx = 2 * (tileExtent[2] - tileExtent[0]) /
-            framebufferExtentDimension;
-        sy = 2 * (tileExtent[3] - tileExtent[1]) /
-            framebufferExtentDimension;
-        tx = 2 * (tileExtent[0] - framebufferExtent[0]) /
-            framebufferExtentDimension - 1;
-        ty = 2 * (tileExtent[1] - framebufferExtent[1]) /
-            framebufferExtentDimension - 1;
-        goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty);
-        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
-        mapRenderer.bindTileTexture(tile, tilePixelSize,
-            tileGutter * pixelRatio, goog.webgl.LINEAR, goog.webgl.LINEAR);
-        gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
-      }
-    }
 
-    if (allTilesLoaded) {
-      this.renderedTileRange_ = tileRange;
-      this.renderedFramebufferExtent_ = framebufferExtent;
-      this.renderedRevision_ = tileSource.getRevision();
-    } else {
-      this.renderedTileRange_ = null;
-      this.renderedFramebufferExtent_ = null;
-      this.renderedRevision_ = -1;
-      frameState.animate = true;
-    }
+/**
+ * @return {boolean} Whether or not the total size of the of the file being
+ *     saved is known.
+ */
+goog.fs.ProgressEvent.prototype.isLengthComputable = function() {
+  return this.event_.lengthComputable;
+};
 
-  }
 
-  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
-  var tileTextureQueue = mapRenderer.getTileTextureQueue();
-  this.manageTilePyramid(
-      frameState, tileSource, tileGrid, pixelRatio, projection, extent, z,
-      tileLayer.getPreload(),
-      /**
-       * @param {ol.Tile} tile Tile.
-       */
-      function(tile) {
-        if (tile.getState() == ol.TileState.LOADED &&
-            !mapRenderer.isTileTextureLoaded(tile) &&
-            !tileTextureQueue.isKeyQueued(tile.getKey())) {
-          tileTextureQueue.enqueue([
-            tile,
-            tileGrid.getTileCoordCenter(tile.tileCoord),
-            tileGrid.getResolution(tile.tileCoord[0]),
-            tilePixelSize, tileGutter * pixelRatio
-          ]);
-        }
-      }, this);
-  this.scheduleExpireCache(frameState, tileSource);
-  this.updateLogos(frameState, tileSource);
+/**
+ * @return {number} The number of bytes saved so far.
+ */
+goog.fs.ProgressEvent.prototype.getLoaded = function() {
+  return this.event_.loaded;
+};
 
-  var texCoordMatrix = this.texCoordMatrix;
-  goog.vec.Mat4.makeIdentity(texCoordMatrix);
-  goog.vec.Mat4.translate(texCoordMatrix,
-      (center[0] - framebufferExtent[0]) /
-          (framebufferExtent[2] - framebufferExtent[0]),
-      (center[1] - framebufferExtent[1]) /
-          (framebufferExtent[3] - framebufferExtent[1]),
-      0);
-  if (viewState.rotation !== 0) {
-    goog.vec.Mat4.rotateZ(texCoordMatrix, viewState.rotation);
-  }
-  goog.vec.Mat4.scale(texCoordMatrix,
-      frameState.size[0] * viewState.resolution /
-          (framebufferExtent[2] - framebufferExtent[0]),
-      frameState.size[1] * viewState.resolution /
-          (framebufferExtent[3] - framebufferExtent[1]),
-      1);
-  goog.vec.Mat4.translate(texCoordMatrix,
-      -0.5,
-      -0.5,
-      0);
 
-  return true;
+/**
+ * @return {number} The total number of bytes in the file being saved.
+ */
+goog.fs.ProgressEvent.prototype.getTotal = function() {
+  return this.event_.total;
 };
 
-goog.provide('ol.structs.LRUCache');
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-goog.require('goog.asserts');
-goog.require('goog.object');
+/**
+ * @fileoverview A wrapper for the HTML5 FileReader object.
+ *
+ */
+
+goog.provide('goog.fs.FileReader');
+goog.provide('goog.fs.FileReader.EventType');
+goog.provide('goog.fs.FileReader.ReadyState');
+
+goog.require('goog.async.Deferred');
+goog.require('goog.events.EventTarget');
+goog.require('goog.fs.Error');
+goog.require('goog.fs.ProgressEvent');
 
 
 
 /**
- * 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.
+ * An object for monitoring the reading of files. This emits ProgressEvents of
+ * the types listed in {@link goog.fs.FileReader.EventType}.
+ *
  * @constructor
- * @struct
- * @template T
+ * @extends {goog.events.EventTarget}
+ * @final
  */
-ol.structs.LRUCache = function() {
+goog.fs.FileReader = function() {
+  goog.fs.FileReader.base(this, 'constructor');
 
   /**
+   * The underlying FileReader object.
+   *
+   * @type {!FileReader}
    * @private
-   * @type {number}
    */
-  this.count_ = 0;
+  this.reader_ = new FileReader();
+
+  this.reader_.onloadstart = goog.bind(this.dispatchProgressEvent_, this);
+  this.reader_.onprogress = goog.bind(this.dispatchProgressEvent_, this);
+  this.reader_.onload = goog.bind(this.dispatchProgressEvent_, this);
+  this.reader_.onabort = goog.bind(this.dispatchProgressEvent_, this);
+  this.reader_.onerror = goog.bind(this.dispatchProgressEvent_, this);
+  this.reader_.onloadend = goog.bind(this.dispatchProgressEvent_, this);
+};
+goog.inherits(goog.fs.FileReader, goog.events.EventTarget);
+
 
+/**
+ * Possible states for a FileReader.
+ *
+ * @enum {number}
+ */
+goog.fs.FileReader.ReadyState = {
   /**
-   * @private
-   * @type {Object.<string, ol.structs.LRUCacheEntry>}
+   * The object has been constructed, but there is no pending read.
    */
-  this.entries_ = {};
-
+  INIT: 0,
   /**
-   * @private
-   * @type {?ol.structs.LRUCacheEntry}
+   * Data is being read.
    */
-  this.oldest_ = null;
-
+  LOADING: 1,
   /**
-   * @private
-   * @type {?ol.structs.LRUCacheEntry}
+   * The data has been read from the file, the read was aborted, or an error
+   * occurred.
    */
-  this.newest_ = null;
+  DONE: 2
+};
+
 
+/**
+ * Events emitted by a FileReader.
+ *
+ * @enum {string}
+ */
+goog.fs.FileReader.EventType = {
+  /**
+   * Emitted when the reading begins. readyState will be LOADING.
+   */
+  LOAD_START: 'loadstart',
+  /**
+   * Emitted when progress has been made in reading the file. readyState will be
+   * LOADING.
+   */
+  PROGRESS: 'progress',
+  /**
+   * Emitted when the data has been successfully read. readyState will be
+   * LOADING.
+   */
+  LOAD: 'load',
+  /**
+   * Emitted when the reading has been aborted. readyState will be LOADING.
+   */
+  ABORT: 'abort',
+  /**
+   * Emitted when an error is encountered or the reading has been aborted.
+   * readyState will be LOADING.
+   */
+  ERROR: 'error',
+  /**
+   * Emitted when the reading is finished, whether successfully or not.
+   * readyState will be DONE.
+   */
+  LOAD_END: 'loadend'
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * Abort the reading of the file.
  */
-ol.structs.LRUCache.prototype.assertValid = function() {
-  if (this.count_ === 0) {
-    goog.asserts.assert(goog.object.isEmpty(this.entries_));
-    goog.asserts.assert(goog.isNull(this.oldest_));
-    goog.asserts.assert(goog.isNull(this.newest_));
-  } else {
-    goog.asserts.assert(goog.object.getCount(this.entries_) == this.count_);
-    goog.asserts.assert(!goog.isNull(this.oldest_));
-    goog.asserts.assert(goog.isNull(this.oldest_.older));
-    goog.asserts.assert(!goog.isNull(this.newest_));
-    goog.asserts.assert(goog.isNull(this.newest_.newer));
-    var i, entry;
-    var older = null;
-    i = 0;
-    for (entry = this.oldest_; !goog.isNull(entry); entry = entry.newer) {
-      goog.asserts.assert(entry.older === older);
-      older = entry;
-      ++i;
-    }
-    goog.asserts.assert(i == this.count_);
-    var newer = null;
-    i = 0;
-    for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) {
-      goog.asserts.assert(entry.newer === newer);
-      newer = entry;
-      ++i;
-    }
-    goog.asserts.assert(i == this.count_);
+goog.fs.FileReader.prototype.abort = function() {
+  try {
+    this.reader_.abort();
+  } catch (e) {
+    throw new goog.fs.Error(e, 'aborting read');
   }
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @return {goog.fs.FileReader.ReadyState} The current state of the FileReader.
  */
-ol.structs.LRUCache.prototype.clear = function() {
-  this.count_ = 0;
-  this.entries_ = {};
-  this.oldest_ = null;
-  this.newest_ = null;
+goog.fs.FileReader.prototype.getReadyState = function() {
+  return /** @type {goog.fs.FileReader.ReadyState} */ (this.reader_.readyState);
 };
 
 
 /**
- * @param {string} key Key.
- * @return {boolean} Contains key.
+ * @return {*} The result of the file read.
  */
-ol.structs.LRUCache.prototype.containsKey = function(key) {
-  return this.entries_.hasOwnProperty(key);
+goog.fs.FileReader.prototype.getResult = function() {
+  return this.reader_.result;
 };
 
 
 /**
- * @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
+ * @return {goog.fs.Error} The error encountered while reading, if any.
  */
-ol.structs.LRUCache.prototype.forEach = function(f, opt_this) {
-  var entry = this.oldest_;
-  while (!goog.isNull(entry)) {
-    f.call(opt_this, entry.value_, entry.key_, this);
-    entry = entry.newer;
-  }
+goog.fs.FileReader.prototype.getError = function() {
+  return this.reader_.error &&
+      new goog.fs.Error(this.reader_.error, 'reading file');
 };
 
 
 /**
- * @param {string} key Key.
- * @return {T} Value.
+ * Wrap a progress event emitted by the underlying file reader and re-emit it.
+ *
+ * @param {!ProgressEvent} event The underlying event.
+ * @private
  */
-ol.structs.LRUCache.prototype.get = function(key) {
-  var entry = this.entries_[key];
-  goog.asserts.assert(goog.isDef(entry));
-  if (entry === this.newest_) {
-    return entry.value_;
-  } else if (entry === this.oldest_) {
-    this.oldest_ = this.oldest_.newer;
-    this.oldest_.older = null;
-  } else {
-    entry.newer.older = entry.older;
-    entry.older.newer = entry.newer;
-  }
-  entry.newer = null;
-  entry.older = this.newest_;
-  this.newest_.newer = entry;
-  this.newest_ = entry;
-  return entry.value_;
+goog.fs.FileReader.prototype.dispatchProgressEvent_ = function(event) {
+  this.dispatchEvent(new goog.fs.ProgressEvent(event, this));
 };
 
 
-/**
- * @return {number} Count.
- */
-ol.structs.LRUCache.prototype.getCount = function() {
-  return this.count_;
+/** @override */
+goog.fs.FileReader.prototype.disposeInternal = function() {
+  goog.fs.FileReader.base(this, 'disposeInternal');
+  delete this.reader_;
 };
 
 
 /**
- * @return {Array.<string>} Keys.
+ * Starts reading a blob as a binary string.
+ * @param {!Blob} blob The blob to read.
  */
-ol.structs.LRUCache.prototype.getKeys = function() {
-  var keys = new Array(this.count_);
-  var i = 0;
-  var entry;
-  for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) {
-    keys[i++] = entry.key_;
-  }
-  goog.asserts.assert(i == this.count_);
-  return keys;
+goog.fs.FileReader.prototype.readAsBinaryString = function(blob) {
+  this.reader_.readAsBinaryString(blob);
 };
 
 
 /**
- * @return {Array.<T>} Values.
+ * Reads a blob as a binary string.
+ * @param {!Blob} blob The blob to read.
+ * @return {!goog.async.Deferred} The deferred Blob contents as a binary string.
+ *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
  */
-ol.structs.LRUCache.prototype.getValues = function() {
-  var values = new Array(this.count_);
-  var i = 0;
-  var entry;
-  for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) {
-    values[i++] = entry.value_;
-  }
-  goog.asserts.assert(i == this.count_);
-  return values;
+goog.fs.FileReader.readAsBinaryString = function(blob) {
+  var reader = new goog.fs.FileReader();
+  var d = goog.fs.FileReader.createDeferred_(reader);
+  reader.readAsBinaryString(blob);
+  return d;
 };
 
 
 /**
- * @return {T} Last value.
+ * Starts reading a blob as an array buffer.
+ * @param {!Blob} blob The blob to read.
  */
-ol.structs.LRUCache.prototype.peekLast = function() {
-  goog.asserts.assert(!goog.isNull(this.oldest_));
-  return this.oldest_.value_;
+goog.fs.FileReader.prototype.readAsArrayBuffer = function(blob) {
+  this.reader_.readAsArrayBuffer(blob);
 };
 
 
 /**
- * @return {string} Last key.
+ * Reads a blob as an array buffer.
+ * @param {!Blob} blob The blob to read.
+ * @return {!goog.async.Deferred} The deferred Blob contents as an array buffer.
+ *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
  */
-ol.structs.LRUCache.prototype.peekLastKey = function() {
-  goog.asserts.assert(!goog.isNull(this.oldest_));
-  return this.oldest_.key_;
+goog.fs.FileReader.readAsArrayBuffer = function(blob) {
+  var reader = new goog.fs.FileReader();
+  var d = goog.fs.FileReader.createDeferred_(reader);
+  reader.readAsArrayBuffer(blob);
+  return d;
 };
 
 
 /**
- * @return {T} value Value.
+ * Starts reading a blob as text.
+ * @param {!Blob} blob The blob to read.
+ * @param {string=} opt_encoding The name of the encoding to use.
  */
-ol.structs.LRUCache.prototype.pop = function() {
-  goog.asserts.assert(!goog.isNull(this.oldest_));
-  goog.asserts.assert(!goog.isNull(this.newest_));
-  var entry = this.oldest_;
-  goog.asserts.assert(entry.key_ in this.entries_);
-  delete this.entries_[entry.key_];
-  if (!goog.isNull(entry.newer)) {
-    entry.newer.older = null;
-  }
-  this.oldest_ = entry.newer;
-  if (goog.isNull(this.oldest_)) {
-    this.newest_ = null;
-  }
-  --this.count_;
-  return entry.value_;
+goog.fs.FileReader.prototype.readAsText = function(blob, opt_encoding) {
+  this.reader_.readAsText(blob, opt_encoding);
 };
 
 
 /**
- * @param {string} key Key.
- * @param {T} value Value.
+ * Reads a blob as text.
+ * @param {!Blob} blob The blob to read.
+ * @param {string=} opt_encoding The name of the encoding to use.
+ * @return {!goog.async.Deferred} The deferred Blob contents as text.
+ *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
  */
-ol.structs.LRUCache.prototype.set = function(key, value) {
-  goog.asserts.assert(!(key in {}));
-  goog.asserts.assert(!(key in this.entries_));
-  var entry = {
-    key_: key,
-    newer: null,
-    older: this.newest_,
-    value_: value
-  };
-  if (goog.isNull(this.newest_)) {
-    this.oldest_ = entry;
-  } else {
-    this.newest_.newer = entry;
-  }
-  this.newest_ = entry;
-  this.entries_[key] = entry;
-  ++this.count_;
+goog.fs.FileReader.readAsText = function(blob, opt_encoding) {
+  var reader = new goog.fs.FileReader();
+  var d = goog.fs.FileReader.createDeferred_(reader);
+  reader.readAsText(blob, opt_encoding);
+  return d;
 };
 
 
 /**
- * @typedef {{key_: string,
- *            newer: ol.structs.LRUCacheEntry,
- *            older: ol.structs.LRUCacheEntry,
- *            value_: *}}
+ * Starts reading a blob as a data URL.
+ * @param {!Blob} blob The blob to read.
  */
-ol.structs.LRUCacheEntry;
+goog.fs.FileReader.prototype.readAsDataUrl = function(blob) {
+  this.reader_.readAsDataURL(blob);
+};
 
-goog.provide('ol.webgl.Context');
 
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.log');
-goog.require('goog.object');
-goog.require('ol.structs.Buffer');
-goog.require('ol.structs.IntegerSet');
-goog.require('ol.webgl.WebGLContextEventType');
+/**
+ * Reads a blob as a data URL.
+ * @param {!Blob} blob The blob to read.
+ * @return {!goog.async.Deferred} The deferred Blob contents as a data URL.
+ *     If an error occurs, the errback is called with a {@link goog.fs.Error}.
+ */
+goog.fs.FileReader.readAsDataUrl = function(blob) {
+  var reader = new goog.fs.FileReader();
+  var d = goog.fs.FileReader.createDeferred_(reader);
+  reader.readAsDataUrl(blob);
+  return d;
+};
 
 
 /**
- * @typedef {{buf: ol.structs.Buffer,
- *            buffer: WebGLBuffer,
- *            dirtySet: ol.structs.IntegerSet}}
+ * Creates a new deferred object for the results of a read method.
+ * @param {goog.fs.FileReader} reader The reader to create a deferred for.
+ * @return {!goog.async.Deferred} The deferred results.
+ * @private
  */
-ol.webgl.BufferCacheEntry;
+goog.fs.FileReader.createDeferred_ = function(reader) {
+  var deferred = new goog.async.Deferred();
+  reader.listen(goog.fs.FileReader.EventType.LOAD_END,
+      goog.partial(function(d, r, e) {
+        var result = r.getResult();
+        var error = r.getError();
+        if (result != null && !error) {
+          d.callback(result);
+        } else {
+          d.errback(error);
+        }
+        r.dispose();
+      }, deferred, reader));
+  return deferred;
+};
+
+// FIXME should handle all geo-referenced data, not just vector data
+
+goog.provide('ol.interaction.DragAndDrop');
+goog.provide('ol.interaction.DragAndDropEvent');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.FileDropHandler');
+goog.require('goog.events.FileDropHandler.EventType');
+goog.require('goog.fs.FileReader');
+goog.require('goog.functions');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.proj');
 
 
 
 /**
  * @classdesc
- * A WebGL context for accessing low-level WebGL capabilities.
+ * Handles input of vector data by drag and drop.
  *
  * @constructor
- * @extends {goog.events.EventTarget}
- * @param {HTMLCanvasElement} canvas Canvas.
- * @param {WebGLRenderingContext} gl GL.
- * @api
+ * @extends {ol.interaction.Interaction}
+ * @fires ol.interaction.DragAndDropEvent
+ * @param {olx.interaction.DragAndDropOptions=} opt_options Options.
+ * @api stable
  */
-ol.webgl.Context = function(canvas, gl) {
+ol.interaction.DragAndDrop = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this, {
+    handleEvent: ol.interaction.DragAndDrop.handleEvent
+  });
 
   /**
    * @private
-   * @type {HTMLCanvasElement}
+   * @type {Array.<function(new: ol.format.Feature)>}
    */
-  this.canvas_ = canvas;
+  this.formatConstructors_ = goog.isDef(options.formatConstructors) ?
+      options.formatConstructors : [];
 
   /**
    * @private
-   * @type {WebGLRenderingContext}
+   * @type {ol.proj.Projection}
    */
-  this.gl_ = gl;
+  this.projection_ = goog.isDef(options.projection) ?
+      ol.proj.get(options.projection) : null;
 
   /**
    * @private
-   * @type {Object.<number, ol.webgl.BufferCacheEntry>}
+   * @type {goog.events.FileDropHandler}
    */
-  this.bufferCache_ = {};
+  this.fileDropHandler_ = null;
 
   /**
    * @private
-   * @type {Object.<number, WebGLShader>}
+   * @type {goog.events.Key|undefined}
    */
-  this.shaderCache_ = {};
+  this.dropListenKey_ = undefined;
+
+};
+goog.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragAndDrop.prototype.disposeInternal = function() {
+  if (goog.isDef(this.dropListenKey_)) {
+    goog.events.unlistenByKey(this.dropListenKey_);
+  }
+  goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @param {goog.events.BrowserEvent} event Event.
+ * @private
+ */
+ol.interaction.DragAndDrop.prototype.handleDrop_ = function(event) {
+  var files = event.getBrowserEvent().dataTransfer.files;
+  var i, ii, file;
+  for (i = 0, ii = files.length; i < ii; ++i) {
+    file = files[i];
+    // The empty string param is a workaround for
+    // https://code.google.com/p/closure-library/issues/detail?id=524
+    var reader = goog.fs.FileReader.readAsText(file, '');
+    reader.addCallback(goog.partial(this.handleResult_, file), this);
+  }
+};
+
+
+/**
+ * @param {File} file File.
+ * @param {string} result Result.
+ * @private
+ */
+ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, result) {
+  var map = this.getMap();
+  goog.asserts.assert(!goog.isNull(map));
+  var projection = this.projection_;
+  if (goog.isNull(projection)) {
+    var view = map.getView();
+    goog.asserts.assert(!goog.isNull(view));
+    projection = view.getProjection();
+    goog.asserts.assert(goog.isDef(projection));
+  }
+  var formatConstructors = this.formatConstructors_;
+  var features = [];
+  var i, ii;
+  for (i = 0, ii = formatConstructors.length; i < ii; ++i) {
+    var formatConstructor = formatConstructors[i];
+    var format = new formatConstructor();
+    var readFeatures = this.tryReadFeatures_(format, result);
+    if (!goog.isNull(readFeatures)) {
+      var featureProjection = format.readProjection(result);
+      var transform = ol.proj.getTransform(featureProjection, projection);
+      var j, jj;
+      for (j = 0, jj = readFeatures.length; j < jj; ++j) {
+        var feature = readFeatures[j];
+        var geometry = feature.getGeometry();
+        if (goog.isDefAndNotNull(geometry)) {
+          geometry.applyTransform(transform);
+        }
+        features.push(feature);
+      }
+    }
+  }
+  this.dispatchEvent(
+      new ol.interaction.DragAndDropEvent(
+          ol.interaction.DragAndDropEventType.ADD_FEATURES, this, file,
+          features, projection));
+};
+
+
+/**
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.DragAndDrop}
+ * @api
+ */
+ol.interaction.DragAndDrop.handleEvent = goog.functions.TRUE;
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragAndDrop.prototype.setMap = function(map) {
+  if (goog.isDef(this.dropListenKey_)) {
+    goog.events.unlistenByKey(this.dropListenKey_);
+    this.dropListenKey_ = undefined;
+  }
+  if (!goog.isNull(this.fileDropHandler_)) {
+    goog.dispose(this.fileDropHandler_);
+    this.fileDropHandler_ = null;
+  }
+  goog.asserts.assert(!goog.isDef(this.dropListenKey_));
+  goog.base(this, 'setMap', map);
+  if (!goog.isNull(map)) {
+    this.fileDropHandler_ = new goog.events.FileDropHandler(map.getViewport());
+    this.dropListenKey_ = goog.events.listen(
+        this.fileDropHandler_, goog.events.FileDropHandler.EventType.DROP,
+        this.handleDrop_, false, this);
+  }
+};
+
+
+/**
+ * @param {ol.format.Feature} format Format.
+ * @param {string} text Text.
+ * @private
+ * @return {Array.<ol.Feature>} Features.
+ */
+ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text) {
+  try {
+    return format.readFeatures(text);
+  } catch (e) {
+    return null;
+  }
+};
+
 
+/**
+ * @enum {string}
+ */
+ol.interaction.DragAndDropEventType = {
   /**
-   * @private
-   * @type {Object.<string, WebGLProgram>}
+   * Triggered when features are added
+   * @event ol.interaction.DragAndDropEvent#addfeatures
+   * @api stable
    */
-  this.programCache_ = {};
+  ADD_FEATURES: 'addfeatures'
+};
+
+
+
+/**
+ * @classdesc
+ * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances
+ * of this type.
+ *
+ * @constructor
+ * @extends {goog.events.Event}
+ * @implements {oli.interaction.DragAndDropEvent}
+ * @param {ol.interaction.DragAndDropEventType} type Type.
+ * @param {Object} target Target.
+ * @param {File} file File.
+ * @param {Array.<ol.Feature>=} opt_features Features.
+ * @param {ol.proj.Projection=} opt_projection Projection.
+ */
+ol.interaction.DragAndDropEvent =
+    function(type, target, file, opt_features, opt_projection) {
+
+  goog.base(this, type, target);
 
   /**
-   * @private
-   * @type {WebGLProgram}
+   * @type {Array.<ol.Feature>|undefined}
+   * @api stable
    */
-  this.currentProgram_ = null;
+  this.features = opt_features;
 
-  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST,
-      this.handleWebGLContextLost, false, this);
-  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED,
-      this.handleWebGLContextRestored, false, this);
+  /**
+   * @type {File}
+   * @api stable
+   */
+  this.file = file;
+
+  /**
+   * @type {ol.proj.Projection|undefined}
+   * @api
+   */
+  this.projection = opt_projection;
 
 };
+goog.inherits(ol.interaction.DragAndDropEvent, goog.events.Event);
 
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * @param {number} target Target.
- * @param {ol.structs.Buffer} buf Buffer.
+ * @fileoverview Defines a 2-element vector class that can be used for
+ * coordinate math, useful for animation systems and point manipulation.
+ *
+ * Vec2 objects inherit from goog.math.Coordinate and may be used wherever a
+ * Coordinate is required. Where appropriate, Vec2 functions accept both Vec2
+ * and Coordinate objects as input.
+ *
+ * @author brenneman@google.com (Shawn Brenneman)
  */
-ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
-  var gl = this.getGL();
-  var arr = buf.getArray();
-  var bufferKey = goog.getUid(buf);
-  if (bufferKey in this.bufferCache_) {
-    var bufferCacheEntry = this.bufferCache_[bufferKey];
-    gl.bindBuffer(target, bufferCacheEntry.buffer);
-    bufferCacheEntry.dirtySet.forEachRange(function(start, stop) {
-      // FIXME check if slice is really efficient here
-      var slice = arr.slice(start, stop);
-      gl.bufferSubData(
-          target,
-          start,
-          target == goog.webgl.ARRAY_BUFFER ?
-          new Float32Array(slice) :
-          new Uint16Array(slice));
-    });
-    bufferCacheEntry.dirtySet.clear();
-  } else {
-    var buffer = gl.createBuffer();
-    gl.bindBuffer(target, buffer);
-    gl.bufferData(
-        target,
-        target == goog.webgl.ARRAY_BUFFER ?
-        new Float32Array(arr) : new Uint16Array(arr),
-        buf.getUsage());
-    var dirtySet = new ol.structs.IntegerSet();
-    buf.addDirtySet(dirtySet);
-    this.bufferCache_[bufferKey] = {
-      buf: buf,
-      buffer: buffer,
-      dirtySet: dirtySet
-    };
-  }
-};
+
+goog.provide('goog.math.Vec2');
+
+goog.require('goog.math');
+goog.require('goog.math.Coordinate');
+
 
 
 /**
- * @param {ol.structs.Buffer} buf Buffer.
+ * Class for a two-dimensional vector object and assorted functions useful for
+ * manipulating points.
+ *
+ * @param {number} x The x coordinate for the vector.
+ * @param {number} y The y coordinate for the vector.
+ * @constructor
+ * @extends {goog.math.Coordinate}
  */
-ol.webgl.Context.prototype.deleteBuffer = function(buf) {
-  var gl = this.getGL();
-  var bufferKey = goog.getUid(buf);
-  goog.asserts.assert(bufferKey in this.bufferCache_);
-  var bufferCacheEntry = this.bufferCache_[bufferKey];
-  bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet);
-  if (!gl.isContextLost()) {
-    gl.deleteBuffer(bufferCacheEntry.buffer);
-  }
-  delete this.bufferCache_[bufferKey];
+goog.math.Vec2 = function(x, y) {
+  /**
+   * X-value
+   * @type {number}
+   */
+  this.x = x;
+
+  /**
+   * Y-value
+   * @type {number}
+   */
+  this.y = y;
 };
+goog.inherits(goog.math.Vec2, goog.math.Coordinate);
 
 
 /**
- * @inheritDoc
+ * @return {!goog.math.Vec2} A random unit-length vector.
  */
-ol.webgl.Context.prototype.disposeInternal = function() {
-  goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
-    bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet);
-  });
-  var gl = this.getGL();
-  if (!gl.isContextLost()) {
-    goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
-      gl.deleteBuffer(bufferCacheEntry.buffer);
-    });
-    goog.object.forEach(this.programCache_, function(program) {
-      gl.deleteProgram(program);
-    });
-    goog.object.forEach(this.shaderCache_, function(shader) {
-      gl.deleteShader(shader);
-    });
-  }
+goog.math.Vec2.randomUnit = function() {
+  var angle = Math.random() * Math.PI * 2;
+  return new goog.math.Vec2(Math.cos(angle), Math.sin(angle));
 };
 
 
 /**
- * @return {HTMLCanvasElement} Canvas.
+ * @return {!goog.math.Vec2} A random vector inside the unit-disc.
  */
-ol.webgl.Context.prototype.getCanvas = function() {
-  return this.canvas_;
+goog.math.Vec2.random = function() {
+  var mag = Math.sqrt(Math.random());
+  var angle = Math.random() * Math.PI * 2;
+
+  return new goog.math.Vec2(Math.cos(angle) * mag, Math.sin(angle) * mag);
 };
 
 
 /**
- * @return {WebGLRenderingContext} GL.
- * @api
+ * Returns a new Vec2 object from a given coordinate.
+ * @param {!goog.math.Coordinate} a The coordinate.
+ * @return {!goog.math.Vec2} A new vector object.
  */
-ol.webgl.Context.prototype.getGL = function() {
-  return this.gl_;
+goog.math.Vec2.fromCoordinate = function(a) {
+  return new goog.math.Vec2(a.x, a.y);
 };
 
 
 /**
- * @param {ol.webgl.Shader} shaderObject Shader object.
- * @return {WebGLShader} Shader.
+ * @return {!goog.math.Vec2} A new vector with the same coordinates as this one.
+ * @override
  */
-ol.webgl.Context.prototype.getShader = function(shaderObject) {
-  var shaderKey = goog.getUid(shaderObject);
-  if (shaderKey in this.shaderCache_) {
-    return this.shaderCache_[shaderKey];
-  } else {
-    var gl = this.getGL();
-    var shader = gl.createShader(shaderObject.getType());
-    gl.shaderSource(shader, shaderObject.getSource());
-    gl.compileShader(shader);
-    if (goog.DEBUG) {
-      if (!gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) &&
-          !gl.isContextLost()) {
-        goog.log.error(this.logger_, gl.getShaderInfoLog(shader));
-      }
-    }
-    goog.asserts.assert(
-        gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) ||
-        gl.isContextLost());
-    this.shaderCache_[shaderKey] = shader;
-    return shader;
-  }
+goog.math.Vec2.prototype.clone = function() {
+  return new goog.math.Vec2(this.x, this.y);
 };
 
 
 /**
- * @param {ol.webgl.shader.Fragment} fragmentShaderObject Fragment shader.
- * @param {ol.webgl.shader.Vertex} vertexShaderObject Vertex shader.
- * @return {WebGLProgram} Program.
+ * Returns the magnitude of the vector measured from the origin.
+ * @return {number} The length of the vector.
  */
-ol.webgl.Context.prototype.getProgram = function(
-    fragmentShaderObject, vertexShaderObject) {
-  var programKey =
-      goog.getUid(fragmentShaderObject) + '/' + goog.getUid(vertexShaderObject);
-  if (programKey in this.programCache_) {
-    return this.programCache_[programKey];
-  } else {
-    var gl = this.getGL();
-    var program = gl.createProgram();
-    gl.attachShader(program, this.getShader(fragmentShaderObject));
-    gl.attachShader(program, this.getShader(vertexShaderObject));
-    gl.linkProgram(program);
-    if (goog.DEBUG) {
-      if (!gl.getProgramParameter(program, goog.webgl.LINK_STATUS) &&
-          !gl.isContextLost()) {
-        goog.log.error(this.logger_, gl.getProgramInfoLog(program));
-      }
-    }
-    goog.asserts.assert(
-        gl.getProgramParameter(program, goog.webgl.LINK_STATUS) ||
-        gl.isContextLost());
-    this.programCache_[programKey] = program;
-    return program;
-  }
+goog.math.Vec2.prototype.magnitude = function() {
+  return Math.sqrt(this.x * this.x + this.y * this.y);
 };
 
 
 /**
- * FIXME empy description for jsdoc
+ * Returns the squared magnitude of the vector measured from the origin.
+ * NOTE(brenneman): Leaving out the square root is not a significant
+ * optimization in JavaScript.
+ * @return {number} The length of the vector, squared.
  */
-ol.webgl.Context.prototype.handleWebGLContextLost = function() {
-  goog.object.clear(this.bufferCache_);
-  goog.object.clear(this.shaderCache_);
-  goog.object.clear(this.programCache_);
-  this.currentProgram_ = null;
+goog.math.Vec2.prototype.squaredMagnitude = function() {
+  return this.x * this.x + this.y * this.y;
 };
 
 
 /**
- * FIXME empy description for jsdoc
+ * @return {!goog.math.Vec2} This coordinate after scaling.
+ * @override
  */
-ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
-};
+goog.math.Vec2.prototype.scale =
+    /** @type {function(number, number=):!goog.math.Vec2} */
+    (goog.math.Coordinate.prototype.scale);
 
 
 /**
- * @param {WebGLProgram} program Program.
- * @return {boolean} Changed.
- * @api
+ * Reverses the sign of the vector. Equivalent to scaling the vector by -1.
+ * @return {!goog.math.Vec2} The inverted vector.
  */
-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;
-  }
+goog.math.Vec2.prototype.invert = function() {
+  this.x = -this.x;
+  this.y = -this.y;
+  return this;
 };
 
 
 /**
- * @private
- * @type {goog.log.Logger}
+ * Normalizes the current vector to have a magnitude of 1.
+ * @return {!goog.math.Vec2} The normalized vector.
  */
-ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context');
-
-// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)
-
-goog.provide('ol.renderer.webgl.Map');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.log');
-goog.require('goog.log.Logger');
-goog.require('goog.object');
-goog.require('goog.style');
-goog.require('goog.webgl');
-goog.require('ol');
-goog.require('ol.RendererType');
-goog.require('ol.css');
-goog.require('ol.dom');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Layer');
-goog.require('ol.layer.Tile');
-goog.require('ol.render.Event');
-goog.require('ol.render.EventType');
-goog.require('ol.render.webgl.Immediate');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.webgl.ImageLayer');
-goog.require('ol.renderer.webgl.Layer');
-goog.require('ol.renderer.webgl.TileLayer');
-goog.require('ol.source.State');
-goog.require('ol.structs.LRUCache');
-goog.require('ol.structs.PriorityQueue');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Context');
-goog.require('ol.webgl.WebGLContextEventType');
+goog.math.Vec2.prototype.normalize = function() {
+  return this.scale(1 / this.magnitude());
+};
 
 
 /**
- * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}}
+ * Adds another vector to this vector in-place.
+ * @param {!goog.math.Coordinate} b The vector to add.
+ * @return {!goog.math.Vec2}  This vector with {@code b} added.
  */
-ol.renderer.webgl.TextureCacheEntry;
-
+goog.math.Vec2.prototype.add = function(b) {
+  this.x += b.x;
+  this.y += b.y;
+  return this;
+};
 
 
 /**
- * @constructor
- * @extends {ol.renderer.Map}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
+ * Subtracts another vector from this vector in-place.
+ * @param {!goog.math.Coordinate} b The vector to subtract.
+ * @return {!goog.math.Vec2} This vector with {@code b} subtracted.
  */
-ol.renderer.webgl.Map = function(container, map) {
-
-  goog.base(this, container, map);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = /** @type {HTMLCanvasElement} */
-      (goog.dom.createElement(goog.dom.TagName.CANVAS));
-  this.canvas_.style.width = '100%';
-  this.canvas_.style.height = '100%';
-  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
-  goog.dom.insertChildAt(container, this.canvas_, 0);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.clipTileCanvasSize_ = 0;
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.clipTileContext_ = ol.dom.createCanvasContext2D();
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
-
-  /**
-   * @private
-   * @type {WebGLRenderingContext}
-   */
-  this.gl_ = ol.webgl.getContext(this.canvas_, {
-    antialias: true,
-    depth: false,
-    failIfMajorPerformanceCaveat: true,
-    preserveDrawingBuffer: false,
-    stencil: true
-  });
-  goog.asserts.assert(!goog.isNull(this.gl_));
-
-  /**
-   * @private
-   * @type {ol.webgl.Context}
-   */
-  this.context_ = new ol.webgl.Context(this.canvas_, this.gl_);
-
-  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST,
-      this.handleWebGLContextLost, false, this);
-  goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED,
-      this.handleWebGLContextRestored, false, this);
-
-  /**
-   * @private
-   * @type {ol.structs.LRUCache.<ol.renderer.webgl.TextureCacheEntry|null>}
-   */
-  this.textureCache_ = new ol.structs.LRUCache();
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.focus_ = null;
-
-  /**
-   * @private
-   * @type {ol.structs.PriorityQueue.<Array>}
-   */
-  this.tileTextureQueue_ = new ol.structs.PriorityQueue(
-      /**
-       * @param {Array.<*>} element Element.
-       * @return {number} Priority.
-       */
-      goog.bind(
-          /**
-           * @param {Array.<*>} element Element.
-           * @return {number} Priority.
-           * @this {ol.renderer.webgl.Map}
-           */
-          function(element) {
-            var tileCenter = /** @type {ol.Coordinate} */ (element[1]);
-            var tileResolution = /** @type {number} */ (element[2]);
-            var deltaX = tileCenter[0] - this.focus_[0];
-            var deltaY = tileCenter[1] - this.focus_[1];
-            return 65536 * Math.log(tileResolution) +
-                Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
-          }, this),
-      /**
-       * @param {Array.<*>} element Element.
-       * @return {string} Key.
-       */
-      function(element) {
-        return /** @type {ol.Tile} */ (element[0]).getKey();
-      });
-
-  /**
-   * @private
-   * @type {ol.PostRenderFunction}
-   */
-  this.loadNextTileTexture_ = goog.bind(
-      function(map, frameState) {
-        if (!this.tileTextureQueue_.isEmpty()) {
-          this.tileTextureQueue_.reprioritize();
-          var element = this.tileTextureQueue_.dequeue();
-          var tile = /** @type {ol.Tile} */ (element[0]);
-          var tileSize = /** @type {number} */ (element[3]);
-          var tileGutter = /** @type {number} */ (element[4]);
-          this.bindTileTexture(
-              tile, tileSize, tileGutter, goog.webgl.LINEAR, goog.webgl.LINEAR);
-        }
-      }, this);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textureCacheFrameMarkerCount_ = 0;
-
-  this.initializeGL_();
-
+goog.math.Vec2.prototype.subtract = function(b) {
+  this.x -= b.x;
+  this.y -= b.y;
+  return this;
 };
-goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
 
 
 /**
- * @param {ol.Tile} tile Tile.
- * @param {number} tileSize Tile size.
- * @param {number} tileGutter Tile gutter.
- * @param {number} magFilter Mag filter.
- * @param {number} minFilter Min filter.
+ * Rotates this vector in-place by a given angle, specified in radians.
+ * @param {number} angle The angle, in radians.
+ * @return {!goog.math.Vec2} This vector rotated {@code angle} radians.
  */
-ol.renderer.webgl.Map.prototype.bindTileTexture =
-    function(tile, tileSize, tileGutter, magFilter, minFilter) {
-  var gl = this.getGL();
-  var tileKey = tile.getKey();
-  if (this.textureCache_.containsKey(tileKey)) {
-    var textureCacheEntry = this.textureCache_.get(tileKey);
-    goog.asserts.assert(!goog.isNull(textureCacheEntry));
-    gl.bindTexture(goog.webgl.TEXTURE_2D, textureCacheEntry.texture);
-    if (textureCacheEntry.magFilter != magFilter) {
-      gl.texParameteri(
-          goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter);
-      textureCacheEntry.magFilter = magFilter;
-    }
-    if (textureCacheEntry.minFilter != minFilter) {
-      gl.texParameteri(
-          goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, minFilter);
-      textureCacheEntry.minFilter = minFilter;
-    }
-  } else {
-    var texture = gl.createTexture();
-    gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
-    if (tileGutter > 0) {
-      var clipTileCanvas = this.clipTileContext_.canvas;
-      var clipTileContext = this.clipTileContext_;
-      if (this.clipTileCanvasSize_ != tileSize) {
-        clipTileCanvas.width = tileSize;
-        clipTileCanvas.height = tileSize;
-        this.clipTileCanvasSize_ = tileSize;
-      } else {
-        clipTileContext.clearRect(0, 0, tileSize, tileSize);
-      }
-      clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter,
-          tileSize, tileSize, 0, 0, tileSize, tileSize);
-      gl.texImage2D(goog.webgl.TEXTURE_2D, 0,
-          goog.webgl.RGBA, goog.webgl.RGBA,
-          goog.webgl.UNSIGNED_BYTE, clipTileCanvas);
-    } else {
-      gl.texImage2D(goog.webgl.TEXTURE_2D, 0,
-          goog.webgl.RGBA, goog.webgl.RGBA,
-          goog.webgl.UNSIGNED_BYTE, tile.getImage());
-    }
-    gl.texParameteri(
-        goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter);
-    gl.texParameteri(
-        goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, minFilter);
-    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S,
-        goog.webgl.CLAMP_TO_EDGE);
-    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T,
-        goog.webgl.CLAMP_TO_EDGE);
-    this.textureCache_.set(tileKey, {
-      texture: texture,
-      magFilter: magFilter,
-      minFilter: minFilter
-    });
-  }
+goog.math.Vec2.prototype.rotate = function(angle) {
+  var cos = Math.cos(angle);
+  var sin = Math.sin(angle);
+  var newX = this.x * cos - this.y * sin;
+  var newY = this.y * cos + this.x * sin;
+  this.x = newX;
+  this.y = newY;
+  return this;
 };
 
 
 /**
- * @inheritDoc
+ * Rotates a vector by a given angle, specified in radians, relative to a given
+ * axis rotation point. The returned vector is a newly created instance - no
+ * in-place changes are done.
+ * @param {!goog.math.Vec2} v A vector.
+ * @param {!goog.math.Vec2} axisPoint The rotation axis point.
+ * @param {number} angle The angle, in radians.
+ * @return {!goog.math.Vec2} The rotated vector in a newly created instance.
  */
-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 {
-    goog.asserts.fail();
-    return null;
-  }
+goog.math.Vec2.rotateAroundPoint = function(v, axisPoint, angle) {
+  var res = v.clone();
+  return res.subtract(axisPoint).rotate(angle).add(axisPoint);
 };
 
 
 /**
- * @param {ol.render.EventType} type Event type.
- * @param {olx.FrameState} frameState Frame state.
- * @private
+ * Compares this vector with another for equality.
+ * @param {!goog.math.Vec2} b The other vector.
+ * @return {boolean} Whether this vector has the same x and y as the given
+ *     vector.
  */
-ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ =
-    function(type, frameState) {
-  var map = this.getMap();
-  if (map.hasListener(type)) {
-    var context = this.getContext();
-    var render = new ol.render.webgl.Immediate(context, frameState.pixelRatio);
-    var composeEvent = new ol.render.Event(
-        type, map, render, null, frameState, null, context);
-    map.dispatchEvent(composeEvent);
-  }
+goog.math.Vec2.prototype.equals = function(b) {
+  return this == b || !!b && this.x == b.x && this.y == b.y;
 };
 
 
 /**
- * @inheritDoc
+ * Returns the distance between two vectors.
+ * @param {!goog.math.Coordinate} a The first vector.
+ * @param {!goog.math.Coordinate} b The second vector.
+ * @return {number} The distance.
  */
-ol.renderer.webgl.Map.prototype.disposeInternal = function() {
-  var gl = this.getGL();
-  if (!gl.isContextLost()) {
-    this.textureCache_.forEach(
-        /**
-         * @param {?ol.renderer.webgl.TextureCacheEntry} textureCacheEntry
-         *     Texture cache entry.
-         */
-        function(textureCacheEntry) {
-          if (!goog.isNull(textureCacheEntry)) {
-            gl.deleteTexture(textureCacheEntry.texture);
-          }
-        });
-  }
-  goog.dispose(this.context_);
-  goog.base(this, 'disposeInternal');
-};
+goog.math.Vec2.distance = goog.math.Coordinate.distance;
 
 
 /**
- * @param {ol.Map} map Map.
- * @param {olx.FrameState} frameState Frame state.
- * @private
+ * Returns the squared distance between two vectors.
+ * @param {!goog.math.Coordinate} a The first vector.
+ * @param {!goog.math.Coordinate} b The second vector.
+ * @return {number} The squared distance.
  */
-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 (goog.isNull(textureCacheEntry)) {
-      if (+this.textureCache_.peekLastKey() == frameState.index) {
-        break;
-      } else {
-        --this.textureCacheFrameMarkerCount_;
-      }
-    } else {
-      gl.deleteTexture(textureCacheEntry.texture);
-    }
-    this.textureCache_.pop();
-  }
-};
+goog.math.Vec2.squaredDistance = goog.math.Coordinate.squaredDistance;
 
 
 /**
- * @return {ol.webgl.Context}
+ * Compares vectors for equality.
+ * @param {!goog.math.Coordinate} a The first vector.
+ * @param {!goog.math.Coordinate} b The second vector.
+ * @return {boolean} Whether the vectors have the same x and y coordinates.
  */
-ol.renderer.webgl.Map.prototype.getContext = function() {
-  return this.context_;
-};
+goog.math.Vec2.equals = goog.math.Coordinate.equals;
 
 
 /**
- * @return {WebGLRenderingContext} GL.
+ * Returns the sum of two vectors as a new Vec2.
+ * @param {!goog.math.Coordinate} a The first vector.
+ * @param {!goog.math.Coordinate} b The second vector.
+ * @return {!goog.math.Vec2} The sum vector.
  */
-ol.renderer.webgl.Map.prototype.getGL = function() {
-  return this.gl_;
+goog.math.Vec2.sum = function(a, b) {
+  return new goog.math.Vec2(a.x + b.x, a.y + b.y);
 };
 
 
 /**
- * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
+ * Returns the difference between two vectors as a new Vec2.
+ * @param {!goog.math.Coordinate} a The first vector.
+ * @param {!goog.math.Coordinate} b The second vector.
+ * @return {!goog.math.Vec2} The difference vector.
  */
-ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
-  return this.tileTextureQueue_;
+goog.math.Vec2.difference = function(a, b) {
+  return new goog.math.Vec2(a.x - b.x, a.y - b.y);
 };
 
 
 /**
- * @inheritDoc
+ * Returns the dot-product of two vectors.
+ * @param {!goog.math.Coordinate} a The first vector.
+ * @param {!goog.math.Coordinate} b The second vector.
+ * @return {number} The dot-product of the two vectors.
  */
-ol.renderer.webgl.Map.prototype.getType = function() {
-  return ol.RendererType.WEBGL;
+goog.math.Vec2.dot = function(a, b) {
+  return a.x * b.x + a.y * b.y;
 };
 
 
 /**
- * @param {goog.events.Event} event Event.
- * @protected
+ * Returns a new Vec2 that is the linear interpolant between vectors a and b at
+ * scale-value x.
+ * @param {!goog.math.Coordinate} a Vector a.
+ * @param {!goog.math.Coordinate} b Vector b.
+ * @param {number} x The proportion between a and b.
+ * @return {!goog.math.Vec2} The interpolated vector.
  */
-ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) {
-  event.preventDefault();
-  this.textureCache_.clear();
-  this.textureCacheFrameMarkerCount_ = 0;
-  goog.object.forEach(this.getLayerRenderers(),
-      /**
-       * @param {ol.renderer.Layer} layerRenderer Layer renderer.
-       * @param {string} key Key.
-       * @param {Object.<string, ol.renderer.Layer>} object Object.
-       */
-      function(layerRenderer, key, object) {
-        goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
-        var webGLLayerRenderer = /** @type {ol.renderer.webgl.Layer} */
-            (layerRenderer);
-        webGLLayerRenderer.handleWebGLContextLost();
-      });
+goog.math.Vec2.lerp = function(a, b, x) {
+  return new goog.math.Vec2(goog.math.lerp(a.x, b.x, x),
+                            goog.math.lerp(a.y, b.y, x));
 };
 
+goog.provide('ol.interaction.DragRotateAndZoom');
+
+goog.require('goog.asserts');
+goog.require('goog.math.Vec2');
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.events.ConditionType');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
+
+
 
 /**
- * @protected
+ * @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.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
-  this.initializeGL_();
-  this.getMap().render();
+ol.interaction.DragRotateAndZoom = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this, {
+    handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_
+  });
+
+  /**
+   * @private
+   * @type {ol.events.ConditionType}
+   */
+  this.condition_ = goog.isDef(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;
+
 };
+goog.inherits(ol.interaction.DragRotateAndZoom,
+    ol.interaction.Pointer);
 
 
 /**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragRotateAndZoom}
  * @private
  */
-ol.renderer.webgl.Map.prototype.initializeGL_ = function() {
-  var gl = this.gl_;
-  gl.activeTexture(goog.webgl.TEXTURE0);
-  gl.blendFuncSeparate(
-      goog.webgl.SRC_ALPHA, goog.webgl.ONE_MINUS_SRC_ALPHA,
-      goog.webgl.ONE, goog.webgl.ONE_MINUS_SRC_ALPHA);
-  gl.disable(goog.webgl.CULL_FACE);
-  gl.disable(goog.webgl.DEPTH_TEST);
-  gl.disable(goog.webgl.SCISSOR_TEST);
-  gl.disable(goog.webgl.STENCIL_TEST);
+ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return;
+  }
+
+  var map = mapBrowserEvent.map;
+  var size = map.getSize();
+  var offset = mapBrowserEvent.pixel;
+  var delta = new goog.math.Vec2(
+      offset[0] - size[0] / 2,
+      size[1] / 2 - offset[1]);
+  var theta = Math.atan2(delta.y, delta.x);
+  var magnitude = delta.magnitude();
+  var view = map.getView();
+  var viewState = view.getState();
+  map.render();
+  if (goog.isDef(this.lastAngle_)) {
+    var angleDelta = theta - this.lastAngle_;
+    ol.interaction.Interaction.rotateWithoutConstraints(
+        map, view, viewState.rotation - angleDelta);
+  }
+  this.lastAngle_ = theta;
+  if (goog.isDef(this.lastMagnitude_)) {
+    var resolution = this.lastMagnitude_ * (viewState.resolution / magnitude);
+    ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution);
+  }
+  if (goog.isDef(this.lastMagnitude_)) {
+    this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
+  }
+  this.lastMagnitude_ = magnitude;
 };
 
 
 /**
- * @param {ol.Tile} tile Tile.
- * @return {boolean} Is tile texture loaded.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragRotateAndZoom}
+ * @private
  */
-ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) {
-  return this.textureCache_.containsKey(tile.getKey());
+ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return true;
+  }
+
+  var map = mapBrowserEvent.map;
+  var view = map.getView();
+  view.setHint(ol.ViewHint.INTERACTING, -1);
+  var viewState = view.getState();
+  var direction = this.lastScaleDelta_ - 1;
+  ol.interaction.Interaction.rotate(map, view, viewState.rotation);
+  ol.interaction.Interaction.zoom(map, view, viewState.resolution,
+      undefined, ol.DRAGROTATEANDZOOM_ANIMATION_DURATION,
+      direction);
+  this.lastScaleDelta_ = 0;
+  return false;
 };
 
 
 /**
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotateAndZoom}
  * @private
- * @type {goog.log.Logger}
  */
-ol.renderer.webgl.Map.prototype.logger_ =
-    goog.log.getLogger('ol.renderer.webgl.Map');
+ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return false;
+  }
 
+  if (this.condition_(mapBrowserEvent)) {
+    mapBrowserEvent.map.getView().setHint(ol.ViewHint.INTERACTING, 1);
+    this.lastAngle_ = undefined;
+    this.lastMagnitude_ = undefined;
+    return true;
+  } else {
+    return false;
+  }
+};
 
+goog.provide('ol.ext.rbush');
+/** @typedef {function(*)} */
+ol.ext.rbush;
+(function() {
+var exports = {};
+var module = {exports: exports};
 /**
- * @inheritDoc
+ * @fileoverview
+ * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
  */
-ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
+/*
+ (c) 2013, Vladimir Agafonkin
+ RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles.
+ https://github.com/mourner/rbush
+*/
 
-  var context = this.getContext();
-  var gl = this.getGL();
+(function () { 'use strict';
 
-  if (gl.isContextLost()) {
-    return false;
-  }
+function rbush(maxEntries, format) {
 
-  if (goog.isNull(frameState)) {
-    if (this.renderedVisible_) {
-      goog.style.setElementShown(this.canvas_, false);
-      this.renderedVisible_ = false;
+    // jshint newcap: false, validthis: true
+    if (!(this instanceof rbush)) return new rbush(maxEntries, format);
+
+    // max entries in a node is 9 by default; min node fill is 40% for best performance
+    this._maxEntries = Math.max(4, maxEntries || 9);
+    this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
+
+    if (format) {
+        this._initFormat(format);
     }
-    return false;
-  }
 
-  this.focus_ = frameState.focus;
+    this.clear();
+}
 
-  this.textureCache_.set((-frameState.index).toString(), null);
-  ++this.textureCacheFrameMarkerCount_;
+rbush.prototype = {
 
-  /** @type {Array.<ol.layer.LayerState>} */
-  var layerStatesToDraw = [];
-  var layerStatesArray = frameState.layerStatesArray;
-  var viewResolution = frameState.viewState.resolution;
-  var i, ii, layerRenderer, layerState;
-  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-    layerState = layerStatesArray[i];
-    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
-        layerState.sourceState == ol.source.State.READY) {
-      layerRenderer = this.getLayerRenderer(layerState.layer);
-      goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
-      if (layerRenderer.prepareFrame(frameState, layerState)) {
-        layerStatesToDraw.push(layerState);
-      }
-    }
-  }
+    all: function () {
+        return this._all(this.data, []);
+    },
 
-  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;
-  }
+    search: function (bbox) {
 
-  gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, null);
+        var node = this.data,
+            result = [],
+            toBBox = this.toBBox;
 
-  gl.clearColor(0, 0, 0, 0);
-  gl.clear(goog.webgl.COLOR_BUFFER_BIT);
-  gl.enable(goog.webgl.BLEND);
-  gl.viewport(0, 0, this.canvas_.width, this.canvas_.height);
+        if (!intersects(bbox, node.bbox)) return result;
 
-  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+        var nodesToSearch = [],
+            i, len, child, childBBox;
 
-  for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) {
-    layerState = layerStatesToDraw[i];
-    layerRenderer = this.getLayerRenderer(layerState.layer);
-    goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
-    layerRenderer.composeFrame(frameState, layerState, context);
-  }
+        while (node) {
+            for (i = 0, len = node.children.length; i < len; i++) {
 
-  if (!this.renderedVisible_) {
-    goog.style.setElementShown(this.canvas_, true);
-    this.renderedVisible_ = true;
-  }
+                child = node.children[i];
+                childBBox = node.leaf ? toBBox(child) : child.bbox;
 
-  this.calculateMatrices2D(frameState);
+                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();
+        }
 
-  if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
-      ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
-    frameState.postRenderFunctions.push(goog.bind(this.expireCache_, this));
-  }
+        return result;
+    },
 
-  if (!this.tileTextureQueue_.isEmpty()) {
-    frameState.postRenderFunctions.push(this.loadNextTileTexture_);
-    frameState.animate = true;
-  }
+    load: function (data) {
+        if (!(data && data.length)) return this;
 
-  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
+        if (data.length < this._minEntries) {
+            for (var i = 0, len = data.length; i < len; i++) {
+                this.insert(data[i]);
+            }
+            return this;
+        }
 
-  this.scheduleRemoveUnusedLayerRenderers(frameState);
-  this.scheduleExpireIconCache(frameState);
+        // 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;
 
-// FIXME recheck layer/map projection compatability when projection changes
-// FIXME layer renderers should skip when they can't reproject
-// FIXME add tilt and height?
+        } else if (this.data.height === node.height) {
+            // split root if trees have the same height
+            this._splitRoot(this.data, node);
 
-goog.provide('ol.Map');
-goog.provide('ol.MapProperty');
+        } else {
+            if (this.data.height < node.height) {
+                // swap trees if inserted one is bigger
+                var tmpNode = this.data;
+                this.data = node;
+                node = tmpNode;
+            }
 
-goog.require('goog.Uri.QueryData');
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.async.AnimationDelay');
-goog.require('goog.async.nextTick');
-goog.require('goog.debug.Console');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.dom.ViewportSizeMonitor');
-goog.require('goog.events');
-goog.require('goog.events.BrowserEvent');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventType');
-goog.require('goog.events.KeyHandler');
-goog.require('goog.events.KeyHandler.EventType');
-goog.require('goog.events.MouseWheelHandler');
-goog.require('goog.events.MouseWheelHandler.EventType');
-goog.require('goog.log');
-goog.require('goog.log.Level');
-goog.require('goog.object');
-goog.require('goog.style');
-goog.require('goog.vec.Mat4');
-goog.require('ol.Collection');
-goog.require('ol.CollectionEventType');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserEventHandler');
-goog.require('ol.MapEvent');
-goog.require('ol.MapEventType');
-goog.require('ol.Object');
-goog.require('ol.ObjectEvent');
-goog.require('ol.ObjectEventType');
-goog.require('ol.Pixel');
-goog.require('ol.PostRenderFunction');
-goog.require('ol.PreRenderFunction');
-goog.require('ol.RendererType');
-goog.require('ol.Size');
-goog.require('ol.TileQueue');
-goog.require('ol.View');
-goog.require('ol.ViewHint');
-goog.require('ol.control');
-goog.require('ol.extent');
-goog.require('ol.has');
-goog.require('ol.interaction');
-goog.require('ol.layer.Base');
-goog.require('ol.layer.Group');
-goog.require('ol.proj');
-goog.require('ol.proj.common');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.canvas.Map');
-goog.require('ol.renderer.dom.Map');
-goog.require('ol.renderer.webgl.Map');
-goog.require('ol.structs.PriorityQueue');
-goog.require('ol.tilecoord');
-goog.require('ol.vec.Mat4');
+            // insert the small tree into the large tree at appropriate level
+            this._insert(node, this.data.height - node.height - 1, true);
+        }
 
+        return this;
+    },
 
-/**
- * @const
- * @type {string}
- */
-ol.OL3_URL = 'http://openlayers.org/';
+    insert: function (item) {
+        if (item) this._insert(item, this.data.height - 1);
+        return this;
+    },
 
+    clear: function () {
+        this.data = {
+            children: [],
+            height: 1,
+            bbox: empty(),
+            leaf: true
+        };
+        return this;
+    },
+
+    remove: function (item) {
+        if (!item) return this;
+
+        var node = this.data,
+            bbox = this.toBBox(item),
+            path = [],
+            indexes = [],
+            i, parent, index, goingUp;
+
+        // depth-first iterative tree traversal
+        while (node || path.length) {
+
+            if (!node) { // go up
+                node = path.pop();
+                parent = path[path.length - 1];
+                i = indexes.pop();
+                goingUp = true;
+            }
 
-/**
- * @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';
+            if (node.leaf) { // check current node
+                index = node.children.indexOf(item);
 
+                if (index !== -1) {
+                    // item found, remove the item and condense tree upwards
+                    node.children.splice(index, 1);
+                    path.push(node);
+                    this._condense(path);
+                    return this;
+                }
+            }
 
-/**
- * @type {Array.<ol.RendererType>}
- */
-ol.DEFAULT_RENDERER_TYPES = [
-  ol.RendererType.CANVAS,
-  ol.RendererType.WEBGL,
-  ol.RendererType.DOM
-];
+            if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down
+                path.push(node);
+                indexes.push(i);
+                i = 0;
+                parent = node;
+                node = node.children[0];
 
+            } else if (parent) { // go right
+                i++;
+                node = parent.children[i];
+                goingUp = false;
 
-/**
- * @enum {string}
- */
-ol.MapProperty = {
-  LAYERGROUP: 'layergroup',
-  SIZE: 'size',
-  TARGET: 'target',
-  VIEW: 'view'
-};
+            } else node = null; // nothing found
+        }
 
+        return this;
+    },
 
+    toBBox: function (item) { return item; },
 
-/**
- * @classdesc
- * The map is the core component of OpenLayers. For a map to render, a view,
- * one or more layers, and a target container are needed:
- *
- *     var map = new ol.Map({
- *       view: new ol.View({
- *         center: [0, 0],
- *         zoom: 1
- *       }),
- *       layers: [
- *         new ol.layer.Tile({
- *           source: new ol.source.MapQuest({layer: 'osm'})
- *         })
- *       ],
- *       target: 'map'
- *     });
- *
- * The above snippet creates a map using a {@link ol.layer.Tile} to display
- * {@link ol.source.MapQuest} OSM data and render it to a DOM element with the
- * id `map`.
- *
- * The constructor places a viewport container (with CSS class name
- * `ol-viewport`) in the target element (see `getViewport()`), and then two
- * further elements within the viewport: one with CSS class name
- * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with
- * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent`
- * option of {@link ol.Overlay} for the difference). The map itself is placed in
- * a further element within the viewport, either DOM or Canvas, depending on the
- * renderer.
- *
- * @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) {
+    compareMinX: function (a, b) { return a[0] - b[0]; },
+    compareMinY: function (a, b) { return a[1] - b[1]; },
 
-  goog.base(this);
+    toJSON: function () { return this.data; },
 
-  var optionsInternal = ol.Map.createOptionsInternal(options);
+    fromJSON: function (data) {
+        this.data = data;
+        return this;
+    },
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = goog.isDef(options.pixelRatio) ?
-      options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;
+    _all: function (node, result) {
+        var nodesToSearch = [];
+        while (node) {
+            if (node.leaf) result.push.apply(result, node.children);
+            else nodesToSearch.push.apply(nodesToSearch, node.children);
 
-  /**
-   * @private
-   * @type {Object}
-   */
-  this.logos_ = optionsInternal.logos;
+            node = nodesToSearch.pop();
+        }
+        return result;
+    },
 
-  /**
-   * @private
-   * @type {goog.async.AnimationDelay}
-   */
-  this.animationDelay_ =
-      new goog.async.AnimationDelay(this.renderFrame_, undefined, this);
-  this.registerDisposable(this.animationDelay_);
+    _build: function (items, left, right, height) {
 
-  /**
-   * @private
-   * @type {goog.vec.Mat4.Number}
-   */
-  this.coordinateToPixelMatrix_ = goog.vec.Mat4.createNumber();
+        var N = right - left + 1,
+            M = this._maxEntries,
+            node;
 
-  /**
-   * @private
-   * @type {goog.vec.Mat4.Number}
-   */
-  this.pixelToCoordinateMatrix_ = goog.vec.Mat4.createNumber();
+        if (N <= M) {
+            // reached leaf level; return leaf
+            node = {
+                children: items.slice(left, right + 1),
+                height: 1,
+                bbox: null,
+                leaf: true
+            };
+            calcBBox(node, this.toBBox);
+            return node;
+        }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.frameIndex_ = 0;
+        if (!height) {
+            // target height of the bulk-loaded tree
+            height = Math.ceil(Math.log(N) / Math.log(M));
 
-  /**
-   * @private
-   * @type {?olx.FrameState}
-   */
-  this.frameState_ = null;
+            // target number of root entries to maximize storage utilization
+            M = Math.ceil(N / Math.pow(M, height - 1));
+        }
 
-  /**
-   * The extent at the previous 'moveend' event.
-   * @private
-   * @type {ol.Extent}
-   */
-  this.previousExtent_ = null;
+        // TODO eliminate recursion?
 
-  /**
-   * @private
-   * @type {goog.events.Key}
-   */
-  this.viewPropertyListenerKey_ = null;
+        node = {
+            children: [],
+            height: height,
+            bbox: null
+        };
 
-  /**
-   * @private
-   * @type {Array.<goog.events.Key>}
-   */
-  this.layerGroupPropertyListenerKeys_ = null;
+        // split the items into M mostly square tiles
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.viewport_ = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-viewport');
-  this.viewport_.style.position = 'relative';
-  this.viewport_.style.overflow = 'hidden';
-  this.viewport_.style.width = '100%';
-  this.viewport_.style.height = '100%';
-  // prevent page zoom on IE >= 10 browsers
-  this.viewport_.style.msTouchAction = 'none';
-  if (ol.has.TOUCH) {
-    this.viewport_.className = 'ol-touch';
-  }
+        var N2 = Math.ceil(N / M),
+            N1 = N2 * Math.ceil(Math.sqrt(M)),
+            i, j, right2, right3;
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.overlayContainer_ = goog.dom.createDom(goog.dom.TagName.DIV,
-      'ol-overlaycontainer');
-  goog.dom.appendChild(this.viewport_, this.overlayContainer_);
+        multiSelect(items, left, right, N1, this.compareMinX);
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.overlayContainerStopEvent_ = goog.dom.createDom(goog.dom.TagName.DIV,
-      'ol-overlaycontainer-stopevent');
-  goog.events.listen(this.overlayContainerStopEvent_, [
-    goog.events.EventType.CLICK,
-    goog.events.EventType.DBLCLICK,
-    goog.events.EventType.MOUSEDOWN,
-    goog.events.EventType.TOUCHSTART,
-    goog.events.EventType.MSPOINTERDOWN,
-    ol.MapBrowserEvent.EventType.POINTERDOWN,
-    // see https://github.com/google/closure-library/pull/308
-    goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel'
-  ], goog.events.Event.stopPropagation);
-  goog.dom.appendChild(this.viewport_, this.overlayContainerStopEvent_);
+        for (i = left; i <= right; i += N1) {
 
-  var mapBrowserEventHandler = new ol.MapBrowserEventHandler(this);
-  goog.events.listen(mapBrowserEventHandler,
-      goog.object.getValues(ol.MapBrowserEvent.EventType),
-      this.handleMapBrowserEvent, false, this);
-  this.registerDisposable(mapBrowserEventHandler);
+            right2 = Math.min(i + N1 - 1, right);
 
-  /**
-   * @private
-   * @type {Element|Document}
-   */
-  this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
+            multiSelect(items, i, right2, N2, this.compareMinY);
 
-  /**
-   * @private
-   * @type {goog.events.KeyHandler}
-   */
-  this.keyHandler_ = new goog.events.KeyHandler();
-  goog.events.listen(this.keyHandler_, goog.events.KeyHandler.EventType.KEY,
-      this.handleBrowserEvent, false, this);
-  this.registerDisposable(this.keyHandler_);
+            for (j = i; j <= right2; j += N2) {
 
-  var mouseWheelHandler = new goog.events.MouseWheelHandler(this.viewport_);
-  goog.events.listen(mouseWheelHandler,
-      goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
-      this.handleBrowserEvent, false, this);
-  this.registerDisposable(mouseWheelHandler);
+                right3 = Math.min(j + N2 - 1, right2);
 
-  /**
-   * @type {ol.Collection.<ol.control.Control>}
-   * @private
-   */
-  this.controls_ = optionsInternal.controls;
+                // pack each entry recursively
+                node.children.push(this._build(items, j, right3, height - 1));
+            }
+        }
 
-  /**
-   * @type {olx.DeviceOptions}
-   * @private
-   */
-  this.deviceOptions_ = optionsInternal.deviceOptions;
+        calcBBox(node, this.toBBox);
 
-  /**
-   * @type {ol.Collection.<ol.interaction.Interaction>}
-   * @private
-   */
-  this.interactions_ = optionsInternal.interactions;
+        return node;
+    },
 
-  /**
-   * @type {ol.Collection.<ol.Overlay>}
-   * @private
-   */
-  this.overlays_ = optionsInternal.overlays;
+    _chooseSubtree: function (bbox, node, level, path) {
 
-  /**
-   * @type {ol.renderer.Map}
-   * @private
-   */
-  this.renderer_ =
-      new optionsInternal.rendererConstructor(this.viewport_, this);
-  this.registerDisposable(this.renderer_);
+        var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
 
-  /**
-   * @private
-   */
-  this.viewportSizeMonitor_ = new goog.dom.ViewportSizeMonitor();
-  this.registerDisposable(this.viewportSizeMonitor_);
+        while (true) {
+            path.push(node);
 
-  goog.events.listen(this.viewportSizeMonitor_, goog.events.EventType.RESIZE,
-      this.updateSize, false, this);
+            if (node.leaf || path.length - 1 === level) break;
 
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.focus_ = null;
+            minArea = minEnlargement = Infinity;
 
-  /**
-   * @private
-   * @type {Array.<ol.PreRenderFunction>}
-   */
-  this.preRenderFunctions_ = [];
+            for (i = 0, len = node.children.length; i < len; i++) {
+                child = node.children[i];
+                area = bboxArea(child.bbox);
+                enlargement = enlargedArea(bbox, child.bbox) - area;
 
-  /**
-   * @private
-   * @type {Array.<ol.PostRenderFunction>}
-   */
-  this.postRenderFunctions_ = [];
+                // choose entry with the least area enlargement
+                if (enlargement < minEnlargement) {
+                    minEnlargement = enlargement;
+                    minArea = area < minArea ? area : minArea;
+                    targetNode = child;
 
-  /**
-   * @private
-   * @type {ol.TileQueue}
-   */
-  this.tileQueue_ = new ol.TileQueue(
-      goog.bind(this.getTilePriority, this),
-      goog.bind(this.handleTileChange_, this));
+                } else if (enlargement === minEnlargement) {
+                    // otherwise choose one with the smallest area
+                    if (area < minArea) {
+                        minArea = area;
+                        targetNode = child;
+                    }
+                }
+            }
 
-  /**
-   * Uids of features to skip at rendering time.
-   * @type {Object.<string, boolean>}
-   * @private
-   */
-  this.skippedFeatureUids_ = {};
+            node = targetNode;
+        }
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP),
-      this.handleLayerGroupChanged_, false, this);
-  goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
-      this.handleViewChanged_, false, this);
-  goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE),
-      this.handleSizeChanged_, false, this);
-  goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET),
-      this.handleTargetChanged_, false, this);
+        return node;
+    },
 
-  // setProperties will trigger the rendering of the map if the map
-  // is "defined" already.
-  this.setProperties(optionsInternal.values);
+    _insert: function (item, level, isNode) {
 
-  this.controls_.forEach(
-      /**
-       * @param {ol.control.Control} control Control.
-       * @this {ol.Map}
-       */
-      function(control) {
-        control.setMap(this);
-      }, this);
+        var toBBox = this.toBBox,
+            bbox = isNode ? item.bbox : toBBox(item),
+            insertPath = [];
 
-  goog.events.listen(this.controls_, ol.CollectionEventType.ADD,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(this);
-      }, false, this);
+        // find the best node for accommodating the item, saving all nodes along the path too
+        var node = this._chooseSubtree(bbox, this.data, level, insertPath);
 
-  goog.events.listen(this.controls_, ol.CollectionEventType.REMOVE,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, false, this);
+        // put the item into the node
+        node.children.push(item);
+        extend(node.bbox, bbox);
 
-  this.interactions_.forEach(
-      /**
-       * @param {ol.interaction.Interaction} interaction Interaction.
-       * @this {ol.Map}
-       */
-      function(interaction) {
-        interaction.setMap(this);
-      }, this);
+        // 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;
+        }
 
-  goog.events.listen(this.interactions_, ol.CollectionEventType.ADD,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(this);
-      }, false, this);
+        // adjust bboxes along the insertion path
+        this._adjustParentBBoxes(bbox, insertPath, level);
+    },
 
-  goog.events.listen(this.interactions_, ol.CollectionEventType.REMOVE,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, false, this);
+    // split overflowed node into two
+    _split: function (insertPath, level) {
 
-  this.overlays_.forEach(
-      /**
-       * @param {ol.Overlay} overlay Overlay.
-       * @this {ol.Map}
-       */
-      function(overlay) {
-        overlay.setMap(this);
-      }, this);
+        var node = insertPath[level],
+            M = node.children.length,
+            m = this._minEntries;
 
-  goog.events.listen(this.overlays_, ol.CollectionEventType.ADD,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(this);
-      }, false, this);
+        this._chooseSplitAxis(node, m, M);
 
-  goog.events.listen(this.overlays_, ol.CollectionEventType.REMOVE,
-      /**
-       * @param {ol.CollectionEvent} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, false, this);
+        var newNode = {
+            children: node.children.splice(this._chooseSplitIndex(node, m, M)),
+            height: node.height
+        };
 
-};
-goog.inherits(ol.Map, ol.Object);
+        if (node.leaf) newNode.leaf = true;
 
+        calcBBox(node, this.toBBox);
+        calcBBox(newNode, this.toBBox);
 
-/**
- * Add the given control to the map.
- * @param {ol.control.Control} control Control.
- * @api stable
- */
-ol.Map.prototype.addControl = function(control) {
-  var controls = this.getControls();
-  goog.asserts.assert(goog.isDef(controls));
-  controls.push(control);
-};
+        if (level) insertPath[level - 1].children.push(newNode);
+        else this._splitRoot(node, newNode);
+    },
 
+    _splitRoot: function (node, newNode) {
+        // split root node
+        this.data = {
+            children: [node, newNode],
+            height: node.height + 1
+        };
+        calcBBox(this.data, this.toBBox);
+    },
 
-/**
- * Add the given interaction to the map.
- * @param {ol.interaction.Interaction} interaction Interaction to add.
- * @api stable
- */
-ol.Map.prototype.addInteraction = function(interaction) {
-  var interactions = this.getInteractions();
-  goog.asserts.assert(goog.isDef(interactions));
-  interactions.push(interaction);
-};
+    _chooseSplitIndex: function (node, m, M) {
 
+        var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
 
-/**
- * Adds the given layer to the top of this map.
- * @param {ol.layer.Base} layer Layer.
- * @api stable
- */
-ol.Map.prototype.addLayer = function(layer) {
-  var layers = this.getLayerGroup().getLayers();
-  goog.asserts.assert(goog.isDef(layers));
-  layers.push(layer);
-};
+        minOverlap = minArea = Infinity;
 
+        for (i = m; i <= M - m; i++) {
+            bbox1 = distBBox(node, 0, i, this.toBBox);
+            bbox2 = distBBox(node, i, M, this.toBBox);
 
-/**
- * Add the given overlay to the map.
- * @param {ol.Overlay} overlay Overlay.
- * @api stable
- */
-ol.Map.prototype.addOverlay = function(overlay) {
-  var overlays = this.getOverlays();
-  goog.asserts.assert(goog.isDef(overlays));
-  overlays.push(overlay);
-};
+            overlap = intersectionArea(bbox1, bbox2);
+            area = bboxArea(bbox1) + bboxArea(bbox2);
 
+            // choose distribution with minimum overlap
+            if (overlap < minOverlap) {
+                minOverlap = overlap;
+                index = i;
 
-/**
- * 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);
-};
+                minArea = area < minArea ? area : minArea;
 
+            } else if (overlap === minOverlap) {
+                // otherwise choose distribution with minimum area
+                if (area < minArea) {
+                    minArea = area;
+                    index = i;
+                }
+            }
+        }
 
-/**
- * @param {ol.PreRenderFunction} preRenderFunction Pre-render function.
- * @return {boolean} Whether the preRenderFunction has been found and removed.
- */
-ol.Map.prototype.removePreRenderFunction = function(preRenderFunction) {
-  return goog.array.remove(this.preRenderFunctions_, preRenderFunction);
-};
+        return index;
+    },
 
+    // sorts node children by the best axis for split
+    _chooseSplitAxis: function (node, m, M) {
 
-/**
- *
- * @inheritDoc
- */
-ol.Map.prototype.disposeInternal = function() {
-  goog.dom.removeNode(this.viewport_);
-  goog.base(this, 'disposeInternal');
-};
+        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);
+    },
 
-/**
- * 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`. Feature overlays will always be
- * included in the detection.
- * @param {ol.Pixel} pixel Pixel.
- * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
- *     callback. If the detected feature is not on a layer, but on a
- *     {@link ol.FeatureOverlay}, then the 2nd argument to this function will
- *     be `null`. To stop detection, callback functions can return a truthy
- *     value.
- * @param {S=} opt_this Value to use as `this` when executing `callback`.
- * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
- *     filter function, only layers which are visible and for which this
- *     function returns `true` will be tested for features. By default, all
- *     visible layers will be tested. Feature overlays will always be tested.
- * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result, i.e. the return value of last
- * callback execution, or the first truthy callback return value.
- * @template S,T,U
- * @api stable
- */
-ol.Map.prototype.forEachFeatureAtPixel =
-    function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
-  if (goog.isNull(this.frameState_)) {
-    return;
-  }
-  var coordinate = this.getCoordinateFromPixel(pixel);
-  var thisArg = goog.isDef(opt_this) ? opt_this : null;
-  var layerFilter = goog.isDef(opt_layerFilter) ?
-      opt_layerFilter : goog.functions.TRUE;
-  var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null;
-  return this.renderer_.forEachFeatureAtPixel(
-      coordinate, this.frameState_, callback, thisArg,
-      layerFilter, thisArg2);
-};
+    // 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);
 
-/**
- * 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));
-};
+        var toBBox = this.toBBox,
+            leftBBox = distBBox(node, 0, m, toBBox),
+            rightBBox = distBBox(node, M - m, M, toBBox),
+            margin = bboxMargin(leftBBox) + bboxMargin(rightBBox),
+            i, child;
 
+        for (i = m; i < M - m; i++) {
+            child = node.children[i];
+            extend(leftBBox, node.leaf ? toBBox(child) : child.bbox);
+            margin += bboxMargin(leftBBox);
+        }
 
-/**
- * Returns the map pixel position for a browser event.
- * @param {Event} event Event.
- * @return {ol.Pixel} Pixel.
- * @api stable
- */
-ol.Map.prototype.getEventPixel = function(event) {
-  // goog.style.getRelativePosition is based on event.targetTouches,
-  // but touchend and touchcancel events have no targetTouches when
-  // the last finger is removed from the screen.
-  // So we ourselves compute the position of touch events.
-  // See https://github.com/google/closure-library/pull/323
-  if (goog.isDef(event.changedTouches)) {
-    var touch = event.changedTouches[0];
-    var viewportPosition = goog.style.getClientPosition(this.viewport_);
-    return [
-      touch.clientX - viewportPosition.x,
-      touch.clientY - viewportPosition.y
-    ];
-  } else {
-    var eventPosition = goog.style.getRelativePosition(event, this.viewport_);
-    return [eventPosition.x, eventPosition.y];
-  }
-};
+        for (i = M - m - 1; i >= m; i--) {
+            child = node.children[i];
+            extend(rightBBox, node.leaf ? toBBox(child) : child.bbox);
+            margin += bboxMargin(rightBBox);
+        }
 
+        return margin;
+    },
 
-/**
- * Get the target in which this map is rendered.
- * Note that this returns what is entered as an option or in setTarget:
- * if that was an element, it returns an element; if a string, it returns that.
- * @return {Element|string|undefined} The Element or id of the Element that the
- *     map is rendered in.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getTarget = function() {
-  return /** @type {Element|string|undefined} */ (
-      this.get(ol.MapProperty.TARGET));
-};
-goog.exportProperty(
-    ol.Map.prototype,
-    'getTarget',
-    ol.Map.prototype.getTarget);
+    _adjustParentBBoxes: function (bbox, path, level) {
+        // adjust bboxes along the given tree path
+        for (var i = level; i >= 0; i--) {
+            extend(path[i].bbox, bbox);
+        }
+    },
 
+    _condense: function (path) {
+        // go through the path, removing empty nodes and updating bboxes
+        for (var i = path.length - 1, siblings; i >= 0; i--) {
+            if (path[i].children.length === 0) {
+                if (i > 0) {
+                    siblings = path[i - 1].children;
+                    siblings.splice(siblings.indexOf(path[i]), 1);
 
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
- */
-ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
-  var frameState = this.frameState_;
-  if (goog.isNull(frameState)) {
-    return null;
-  } else {
-    var vec2 = pixel.slice();
-    return ol.vec.Mat4.multVec2(frameState.pixelToCoordinateMatrix, vec2, vec2);
-  }
-};
+                } else this.clear();
 
+            } else calcBBox(path[i], this.toBBox);
+        }
+    },
 
-/**
- * @return {ol.Collection.<ol.control.Control>} Controls.
- * @api stable
- */
-ol.Map.prototype.getControls = function() {
-  return this.controls_;
-};
+    _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
 
-/**
- * @return {ol.Collection.<ol.Overlay>} Overlays.
- * @api stable
- */
-ol.Map.prototype.getOverlays = function() {
-  return this.overlays_;
-};
+        // jshint evil: true
 
+        var compareArr = ['return a', ' - b', ';'];
 
-/**
- * Gets the collection of {@link ol.interaction.Interaction} instances
- * associated with this map. 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_;
+        this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
+        this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
+
+        this.toBBox = new Function('a', 'return [a' + format.join(', a') + '];');
+    }
 };
 
 
-/**
- * Get the layergroup associated with this map.
- * @return {ol.layer.Group} A layer group containing the layers in this map.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getLayerGroup = function() {
-  return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
-};
-goog.exportProperty(
-    ol.Map.prototype,
-    'getLayerGroup',
-    ol.Map.prototype.getLayerGroup);
+// calculate node's bbox from bboxes of its children
+function calcBBox(node, toBBox) {
+    node.bbox = distBBox(node, 0, node.children.length, toBBox);
+}
 
+// min bounding rectangle of node children from k to p-1
+function distBBox(node, k, p, toBBox) {
+    var bbox = empty();
 
-/**
- * Get the collection of layers associated with this map.
- * @return {ol.Collection.<ol.layer.Base>|undefined} Layers.
- * @api stable
- */
-ol.Map.prototype.getLayers = function() {
-  var layerGroup = this.getLayerGroup();
-  if (goog.isDef(layerGroup)) {
-    return layerGroup.getLayers();
-  } else {
-    return undefined;
-  }
-};
+    for (var i = k, child; i < p; i++) {
+        child = node.children[i];
+        extend(bbox, node.leaf ? toBBox(child) : child.bbox);
+    }
 
+    return bbox;
+}
 
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {ol.Pixel} Pixel.
- * @api stable
- */
-ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
-  var frameState = this.frameState_;
-  if (goog.isNull(frameState)) {
-    return null;
-  } else {
-    var vec2 = coordinate.slice(0, 2);
-    return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2);
-  }
-};
+function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; }
 
+function extend(a, b) {
+    a[0] = Math.min(a[0], b[0]);
+    a[1] = Math.min(a[1], b[1]);
+    a[2] = Math.max(a[2], b[2]);
+    a[3] = Math.max(a[3], b[3]);
+    return a;
+}
 
-/**
- * Get the map renderer.
- * @return {ol.renderer.Map} Renderer
- */
-ol.Map.prototype.getRenderer = function() {
-  return this.renderer_;
-};
+function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; }
+function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; }
 
+function bboxArea(a)   { return (a[2] - a[0]) * (a[3] - a[1]); }
+function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); }
 
-/**
- * Get the size of this map.
- * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getSize = function() {
-  return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
-};
-goog.exportProperty(
-    ol.Map.prototype,
-    'getSize',
-    ol.Map.prototype.getSize);
+function enlargedArea(a, b) {
+    return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) *
+           (Math.max(b[3], a[3]) - Math.min(b[1], a[1]));
+}
 
+function intersectionArea (a, b) {
+    var minX = Math.max(a[0], b[0]),
+        minY = Math.max(a[1], b[1]),
+        maxX = Math.min(a[2], b[2]),
+        maxY = Math.min(a[3], b[3]);
 
-/**
- * Get the view associated with this map. A view manages properties such as
- * center and resolution.
- * @return {ol.View|undefined} The view that controls this map.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getView = function() {
-  return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
-};
-goog.exportProperty(
-    ol.Map.prototype,
-    'getView',
-    ol.Map.prototype.getView);
+    return Math.max(0, maxX - minX) *
+           Math.max(0, maxY - minY);
+}
 
+function contains(a, b) {
+    return a[0] <= b[0] &&
+           a[1] <= b[1] &&
+           b[2] <= a[2] &&
+           b[3] <= a[3];
+}
 
-/**
- * @return {Element} Viewport.
- * @api stable
- */
-ol.Map.prototype.getViewport = function() {
-  return this.viewport_;
-};
+function intersects (a, b) {
+    return b[0] <= a[2] &&
+           b[1] <= a[3] &&
+           b[2] >= a[0] &&
+           b[3] >= a[1];
+}
 
+// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
+// combines selection algorithm with binary divide & conquer approach
 
-/**
- * @return {Element} The map's overlay container. 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.
- */
-ol.Map.prototype.getOverlayContainer = function() {
-  return this.overlayContainer_;
-};
+function multiSelect(arr, left, right, n, compare) {
+    var stack = [left, right],
+        mid;
 
+    while (stack.length) {
+        right = stack.pop();
+        left = stack.pop();
 
-/**
- * @return {Element} The map's overlay container. 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}.
- */
-ol.Map.prototype.getOverlayContainerStopEvent = function() {
-  return this.overlayContainerStopEvent_;
-};
+        if (right - left <= n) continue;
 
+        mid = left + Math.ceil((right - left) / n / 2) * n;
+        select(arr, left, right, mid, compare);
 
-/**
- * @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 (goog.isNull(frameState) || !(tileSourceKey in frameState.wantedTiles)) {
-    return ol.structs.PriorityQueue.DROP;
-  }
-  var coordKey = ol.tilecoord.toString(tile.tileCoord);
-  if (!frameState.wantedTiles[tileSourceKey][coordKey]) {
-    return ol.structs.PriorityQueue.DROP;
-  }
-  // Prioritize the highest zoom level tiles closest to the focus.
-  // Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
-  // Within a zoom level, tiles are prioritized by the distance in pixels
-  // between the center of the tile and the focus.  The factor of 65536 means
-  // that the prioritization should behave as desired for tiles up to
-  // 65536 * Math.log(2) = 45426 pixels from the focus.
-  var deltaX = tileCenter[0] - frameState.focus[0];
-  var deltaY = tileCenter[1] - frameState.focus[1];
-  return 65536 * Math.log(tileResolution) +
-      Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
-};
+        stack.push(left, mid, mid, right);
+    }
+}
+
+// sort array between left and right (inclusive) so that the smallest k elements come first (unordered)
+function select(arr, left, right, k, compare) {
+    var n, i, z, s, sd, newLeft, newRight, t, j;
+
+    while (right > left) {
+        if (right - left > 600) {
+            n = right - left + 1;
+            i = k - left + 1;
+            z = Math.log(n);
+            s = 0.5 * Math.exp(2 * z / 3);
+            sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1);
+            newLeft = Math.max(left, Math.floor(k - i * s / n + sd));
+            newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd));
+            select(arr, newLeft, newRight, k, compare);
+        }
+
+        t = arr[k];
+        i = left;
+        j = right;
+
+        swap(arr, left, k);
+        if (compare(arr[right], t) > 0) swap(arr, left, right);
+
+        while (i < j) {
+            swap(arr, i, j);
+            i++;
+            j--;
+            while (compare(arr[i], t) < 0) i++;
+            while (compare(arr[j], t) > 0) j--;
+        }
+
+        if (compare(arr[left], t) === 0) swap(arr, left, j);
+        else {
+            j++;
+            swap(arr, j, right);
+        }
+
+        if (j <= k) left = j + 1;
+        if (k <= j) right = j - 1;
+    }
+}
 
+function swap(arr, i, j) {
+    var tmp = arr[i];
+    arr[i] = arr[j];
+    arr[j] = tmp;
+}
 
-/**
- * @param {goog.events.BrowserEvent} browserEvent Browser event.
- * @param {string=} opt_type Type.
- */
-ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
-  var type = opt_type || browserEvent.type;
-  var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent);
-  this.handleMapBrowserEvent(mapBrowserEvent);
-};
 
+// export as AMD/CommonJS module or global variable
+if (typeof define === 'function' && define.amd) define(function() { return rbush; });
+else if (typeof module !== 'undefined') module.exports = rbush;
+else if (typeof self !== 'undefined') self.rbush = rbush;
+else window.rbush = rbush;
+
+})();
+
+ol.ext.rbush = module.exports;
+})();
+
+goog.provide('ol.structs.RBush');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('ol.ext.rbush');
 
-/**
- * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
- */
-ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) {
-  if (goog.isNull(this.frameState_)) {
-    // With no view defined, we cannot translate pixels into geographical
-    // coordinates so interactions cannot be used.
-    return;
-  }
-  this.focus_ = mapBrowserEvent.coordinate;
-  mapBrowserEvent.frameState = this.frameState_;
-  var interactions = this.getInteractions();
-  goog.asserts.assert(goog.isDef(interactions));
-  var interactionsArray = interactions.getArray();
-  var i;
-  if (this.dispatchEvent(mapBrowserEvent) !== false) {
-    for (i = interactionsArray.length - 1; i >= 0; i--) {
-      var interaction = interactionsArray[i];
-      var cont = interaction.handleMapBrowserEvent(mapBrowserEvent);
-      if (!cont) {
-        break;
-      }
-    }
-  }
-};
 
 
 /**
- * @protected
+ * Wrapper around the RBush by Vladimir Agafonkin.
+ *
+ * @constructor
+ * @param {number=} opt_maxEntries Max entries.
+ * @see https://github.com/mourner/rbush
+ * @struct
+ * @template T
  */
-ol.Map.prototype.handlePostRender = function() {
+ol.structs.RBush = function(opt_maxEntries) {
 
-  var frameState = this.frameState_;
+  /**
+   * @private
+   */
+  this.rbush_ = ol.ext.rbush(opt_maxEntries);
 
-  // Manage the tile queue
-  // Image loads are expensive and a limited resource, so try to use them
-  // efficiently:
-  // * When the view is static we allow a large number of parallel tile loads
-  //   to complete the frame as quickly as possible.
-  // * When animating or interacting, image loads can cause janks, so we reduce
-  //   the maximum number of loads per frame and limit the number of parallel
-  //   tile loads to remain reactive to view changes and to reduce the chance of
-  //   loading tiles that will quickly disappear from view.
-  var tileQueue = this.tileQueue_;
-  if (!tileQueue.isEmpty()) {
-    var maxTotalLoading = 16;
-    var maxNewLoads = maxTotalLoading;
-    var tileSourceCount = 0;
-    if (!goog.isNull(frameState)) {
-      var hints = frameState.viewHints;
-      var deviceOptions = this.deviceOptions_;
-      if (hints[ol.ViewHint.ANIMATING]) {
-        maxTotalLoading = deviceOptions.loadTilesWhileAnimating === false ?
-            0 : 8;
-        maxNewLoads = 2;
-      }
-      if (hints[ol.ViewHint.INTERACTING]) {
-        maxTotalLoading = deviceOptions.loadTilesWhileInteracting === false ?
-            0 : 8;
-        maxNewLoads = 2;
-      }
-      tileSourceCount = goog.object.getCount(frameState.wantedTiles);
-    }
-    maxTotalLoading *= tileSourceCount;
-    maxNewLoads *= tileSourceCount;
-    if (tileQueue.getTilesLoading() < maxTotalLoading) {
-      tileQueue.reprioritize(); // FIXME only call if view has changed
-      tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
-    }
-  }
+  /**
+   * A mapping between the objects added to this rbush wrapper
+   * and the objects that are actually added to the internal rbush.
+   * @private
+   * @type {Object.<number, Object>}
+   */
+  this.items_ = {};
 
-  var postRenderFunctions = this.postRenderFunctions_;
-  var i, ii;
-  for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
-    postRenderFunctions[i](this, frameState);
+  if (goog.DEBUG) {
+    /**
+     * @private
+     * @type {number}
+     */
+    this.readers_ = 0;
   }
-  postRenderFunctions.length = 0;
 };
 
 
 /**
- * @private
+ * Insert a value into the RBush.
+ * @param {ol.Extent} extent Extent.
+ * @param {T} value Value.
  */
-ol.Map.prototype.handleSizeChanged_ = function() {
-  this.render();
+ol.structs.RBush.prototype.insert = function(extent, value) {
+  if (goog.DEBUG && this.readers_) {
+    throw new Error('Can not insert value while reading');
+  }
+  var item = [
+    extent[0],
+    extent[1],
+    extent[2],
+    extent[3],
+    value
+  ];
+  this.rbush_.insert(item);
+  // remember the object that was added to the internal rbush
+  goog.object.add(this.items_, goog.getUid(value).toString(), item);
 };
 
 
 /**
- * @private
+ * Bulk-insert values into the RBush.
+ * @param {Array.<ol.Extent>} extents Extents.
+ * @param {Array.<T>} values Values.
  */
-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 target = this.getTarget();
-
-  /**
-   * @type {Element}
-   */
-  var targetElement = goog.isDef(target) ?
-      goog.dom.getElement(target) : null;
-
-  this.keyHandler_.detach();
+ol.structs.RBush.prototype.load = function(extents, values) {
+  if (goog.DEBUG && this.readers_) {
+    throw new Error('Can not insert values while reading');
+  }
+  goog.asserts.assert(extents.length === values.length);
 
-  if (goog.isNull(targetElement)) {
-    goog.dom.removeNode(this.viewport_);
-  } else {
-    goog.dom.appendChild(targetElement, this.viewport_);
+  var items = new Array(values.length);
+  for (var i = 0, l = values.length; i < l; i++) {
+    var extent = extents[i];
+    var value = values[i];
 
-    var keyboardEventTarget = goog.isNull(this.keyboardEventTarget_) ?
-        targetElement : this.keyboardEventTarget_;
-    this.keyHandler_.attach(keyboardEventTarget);
+    var item = [
+      extent[0],
+      extent[1],
+      extent[2],
+      extent[3],
+      value
+    ];
+    items[i] = item;
+    goog.object.add(this.items_, goog.getUid(value).toString(), item);
   }
-
-  this.updateSize();
-  // updateSize calls setSize, so no need to call this.render
-  // ourselves here.
+  this.rbush_.load(items);
 };
 
 
 /**
- * @private
+ * Remove a value from the RBush.
+ * @param {T} value Value.
+ * @return {boolean} Removed.
  */
-ol.Map.prototype.handleTileChange_ = function() {
-  this.render();
+ol.structs.RBush.prototype.remove = function(value) {
+  if (goog.DEBUG && this.readers_) {
+    throw new Error('Can not remove value while reading');
+  }
+  var uid = goog.getUid(value).toString();
+  goog.asserts.assert(goog.object.containsKey(this.items_, uid));
+
+  // get the object in which the value was wrapped when adding to the
+  // internal rbush. then use that object to do the removal.
+  var item = goog.object.get(this.items_, uid);
+  goog.object.remove(this.items_, uid);
+  return this.rbush_.remove(item) !== null;
 };
 
 
 /**
- * @private
+ * Update the extent of a value in the RBush.
+ * @param {ol.Extent} extent Extent.
+ * @param {T} value Value.
  */
-ol.Map.prototype.handleViewPropertyChanged_ = function() {
-  this.render();
+ol.structs.RBush.prototype.update = function(extent, value) {
+  this.remove(value);
+  this.insert(extent, value);
 };
 
 
 /**
- * @private
+ * Return all values in the RBush.
+ * @return {Array.<T>} All.
  */
-ol.Map.prototype.handleViewChanged_ = function() {
-  if (!goog.isNull(this.viewPropertyListenerKey_)) {
-    goog.events.unlistenByKey(this.viewPropertyListenerKey_);
-    this.viewPropertyListenerKey_ = null;
-  }
-  var view = this.getView();
-  if (goog.isDefAndNotNull(view)) {
-    this.viewPropertyListenerKey_ = goog.events.listen(
-        view, ol.ObjectEventType.PROPERTYCHANGE,
-        this.handleViewPropertyChanged_, false, this);
-  }
-  this.render();
+ol.structs.RBush.prototype.getAll = function() {
+  var items = this.rbush_.all();
+  return goog.array.map(items, function(item) {
+    return item[4];
+  });
 };
 
 
 /**
- * @param {goog.events.Event} event Event.
- * @private
+ * Return all values in the given extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {Array.<T>} All in extent.
  */
-ol.Map.prototype.handleLayerGroupMemberChanged_ = function(event) {
-  goog.asserts.assertInstanceof(event, goog.events.Event);
-  this.render();
+ol.structs.RBush.prototype.getInExtent = function(extent) {
+  var items = this.rbush_.search(extent);
+  return goog.array.map(items, function(item) {
+    return item[4];
+  });
 };
 
 
 /**
- * @param {ol.ObjectEvent} event Event.
- * @private
+ * 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.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) {
-  goog.asserts.assertInstanceof(event, ol.ObjectEvent);
-  this.render();
+ol.structs.RBush.prototype.forEach = function(callback, opt_this) {
+  if (goog.DEBUG) {
+    ++this.readers_;
+    try {
+      return this.forEach_(this.getAll(), callback, opt_this);
+    } finally {
+      --this.readers_;
+    }
+  } else {
+    return this.forEach_(this.getAll(), callback, opt_this);
+  }
 };
 
 
 /**
- * @private
+ * 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.Map.prototype.handleLayerGroupChanged_ = function() {
-  if (!goog.isNull(this.layerGroupPropertyListenerKeys_)) {
-    var length = this.layerGroupPropertyListenerKeys_.length;
-    for (var i = 0; i < length; ++i) {
-      goog.events.unlistenByKey(this.layerGroupPropertyListenerKeys_[i]);
+ol.structs.RBush.prototype.forEachInExtent =
+    function(extent, callback, opt_this) {
+  if (goog.DEBUG) {
+    ++this.readers_;
+    try {
+      return this.forEach_(this.getInExtent(extent), callback, opt_this);
+    } finally {
+      --this.readers_;
     }
-    this.layerGroupPropertyListenerKeys_ = null;
-  }
-  var layerGroup = this.getLayerGroup();
-  if (goog.isDefAndNotNull(layerGroup)) {
-    this.layerGroupPropertyListenerKeys_ = [
-      goog.events.listen(
-          layerGroup, ol.ObjectEventType.PROPERTYCHANGE,
-          this.handleLayerGroupPropertyChanged_, false, this),
-      goog.events.listen(
-          layerGroup, goog.events.EventType.CHANGE,
-          this.handleLayerGroupMemberChanged_, false, this)
-    ];
+  } else {
+    return this.forEach_(this.getInExtent(extent), callback, opt_this);
   }
-  this.render();
 };
 
 
 /**
- * Returns `true` if the map is defined, `false` otherwise. The map is defined
- * if it is contained in `document`, visible, has non-zero height and width, and
- * has a defined view.
- * @return {boolean} Is defined.
+ * @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.Map.prototype.isDef = function() {
-  if (!goog.dom.contains(document, this.viewport_)) {
-    return false;
-  }
-  if (!goog.style.isElementShown(this.viewport_)) {
-    return false;
-  }
-  var size = this.getSize();
-  if (!goog.isDefAndNotNull(size) || size[0] <= 0 || size[1] <= 0) {
-    return false;
-  }
-  var view = this.getView();
-  if (!goog.isDef(view) || !view.isDef()) {
-    return false;
+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 true;
+  return result;
 };
 
 
 /**
- * @return {boolean} Is rendered.
+ * @return {boolean} Is empty.
  */
-ol.Map.prototype.isRendered = function() {
-  return !goog.isNull(this.frameState_);
+ol.structs.RBush.prototype.isEmpty = function() {
+  return goog.object.isEmpty(this.items_);
 };
 
 
 /**
- * Requests an immediate render in a synchronous manner.
- * @api stable
+ * Remove all values from the RBush.
  */
-ol.Map.prototype.renderSync = function() {
-  this.animationDelay_.fire();
+ol.structs.RBush.prototype.clear = function() {
+  this.rbush_.clear();
+  this.items_ = {};
 };
 
 
 /**
- * Requests a render frame; rendering will effectively occur at the next browser
- * animation frame.
- * @api stable
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
  */
-ol.Map.prototype.render = function() {
-  if (!this.animationDelay_.isActive()) {
-    this.animationDelay_.start();
-  }
+ol.structs.RBush.prototype.getExtent = function(opt_extent) {
+  // FIXME add getExtent() to rbush
+  return this.rbush_.data.bbox;
 };
 
+// FIXME bulk feature upload - suppress events
+// FIXME put features in an ol.Collection
+// FIXME make change-detection more refined (notably, geometry hint)
+
+goog.provide('ol.source.Vector');
+goog.provide('ol.source.VectorEvent');
+goog.provide('ol.source.VectorEventType');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol.ObjectEventType');
+goog.require('ol.proj');
+goog.require('ol.source.Source');
+goog.require('ol.structs.RBush');
+
 
 /**
- * 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
+ * @enum {string}
  */
-ol.Map.prototype.removeControl = function(control) {
-  var controls = this.getControls();
-  goog.asserts.assert(goog.isDef(controls));
-  if (goog.isDef(controls.remove(control))) {
-    return control;
-  }
-  return undefined;
+ol.source.VectorEventType = {
+  /**
+   * Triggered when a feature is added to the source.
+   * @event ol.source.VectorEvent#addfeature
+   * @api stable
+   */
+  ADDFEATURE: 'addfeature',
+
+  /**
+   * Triggered when a feature is updated.
+   * @event ol.source.VectorEvent#changefeature
+   * @api
+   */
+  CHANGEFEATURE: 'changefeature',
+
+  /**
+   * Triggered when the clear method is called on the source.
+   * @event ol.source.VectorEvent#clear
+   * @api
+   */
+  CLEAR: 'clear',
+
+  /**
+   * Triggered when a feature is removed from the source.
+   * See {@link ol.source.Vector#clear source.clear()} for exceptions.
+   * @event ol.source.VectorEvent#removefeature
+   * @api stable
+   */
+  REMOVEFEATURE: 'removefeature'
 };
 
 
+
 /**
- * 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).
+ * @classdesc
+ * Provides a source of features for vector layers.
+ *
+ * @constructor
+ * @extends {ol.source.Source}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.VectorOptions=} opt_options Vector source options.
  * @api stable
  */
-ol.Map.prototype.removeInteraction = function(interaction) {
-  var removed;
-  var interactions = this.getInteractions();
-  goog.asserts.assert(goog.isDef(interactions));
-  if (goog.isDef(interactions.remove(interaction))) {
-    removed = interaction;
+ol.source.Vector = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this, {
+    attributions: options.attributions,
+    logo: options.logo,
+    projection: options.projection,
+    state: goog.isDef(options.state) ?
+        /** @type {ol.source.State} */ (options.state) : undefined
+  });
+
+  /**
+   * @private
+   * @type {ol.structs.RBush.<ol.Feature>}
+   */
+  this.rBush_ = new ol.structs.RBush();
+
+  /**
+   * @private
+   * @type {Object.<string, ol.Feature>}
+   */
+  this.nullGeometryFeatures_ = {};
+
+  /**
+   * A lookup of features by id (the return from feature.getId()).
+   * @private
+   * @type {Object.<string, ol.Feature>}
+   */
+  this.idIndex_ = {};
+
+  /**
+   * A lookup of features without id (keyed by goog.getUid(feature)).
+   * @private
+   * @type {Object.<string, ol.Feature>}
+   */
+  this.undefIdIndex_ = {};
+
+  /**
+   * @private
+   * @type {Object.<string, Array.<goog.events.Key>>}
+   */
+  this.featureChangeKeys_ = {};
+
+  if (goog.isDef(options.features)) {
+    this.addFeaturesInternal(options.features);
   }
-  return removed;
+
 };
+goog.inherits(ol.source.Vector, ol.source.Source);
 
 
 /**
- * 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).
+ * 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.Map.prototype.removeLayer = function(layer) {
-  var layers = this.getLayerGroup().getLayers();
-  goog.asserts.assert(goog.isDef(layers));
-  return layers.remove(layer);
+ol.source.Vector.prototype.addFeature = function(feature) {
+  this.addFeatureInternal(feature);
+  this.changed();
 };
 
 
 /**
- * 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
+ * Add a feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
  */
-ol.Map.prototype.removeOverlay = function(overlay) {
-  var overlays = this.getOverlays();
-  goog.asserts.assert(goog.isDef(overlays));
-  if (goog.isDef(overlays.remove(overlay))) {
-    return overlay;
+ol.source.Vector.prototype.addFeatureInternal = function(feature) {
+  var featureKey = goog.getUid(feature).toString();
+  this.setupChangeEvents_(featureKey, feature);
+
+  var geometry = feature.getGeometry();
+  if (goog.isDefAndNotNull(geometry)) {
+    var extent = geometry.getExtent();
+    this.rBush_.insert(extent, feature);
+  } else {
+    this.nullGeometryFeatures_[featureKey] = feature;
   }
-  return undefined;
+
+  this.addToIndex_(featureKey, feature);
+  this.dispatchEvent(
+      new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature));
 };
 
 
 /**
- * @param {number} time Time.
+ * @param {string} featureKey
+ * @param {ol.Feature} feature
  * @private
  */
-ol.Map.prototype.renderFrame_ = function(time) {
-
-  var i, ii, viewState;
+ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) {
+  goog.asserts.assert(!(featureKey in this.featureChangeKeys_));
+  this.featureChangeKeys_[featureKey] = [
+    goog.events.listen(feature,
+        goog.events.EventType.CHANGE,
+        this.handleFeatureChange_, false, this),
+    goog.events.listen(feature,
+        ol.ObjectEventType.PROPERTYCHANGE,
+        this.handleFeatureChange_, false, this)
+  ];
+};
 
-  /**
-   * Check whether a size has non-zero width and height.  Note that this
-   * function is here because the compiler doesn't recognize that size is
-   * defined in the frameState assignment below when the same code is inline in
-   * the condition below.  The compiler inlines this function itself, so the
-   * resulting code is the same.
-   *
-   * @param {ol.Size} size The size to test.
-   * @return {boolean} Has non-zero width and height.
-   */
-  function hasArea(size) {
-    return size[0] > 0 && size[1] > 0;
-  }
 
-  var size = this.getSize();
-  var view = this.getView();
-  /** @type {?olx.FrameState} */
-  var frameState = null;
-  if (goog.isDef(size) && hasArea(size) &&
-      goog.isDef(view) && view.isDef()) {
-    var viewHints = view.getHints();
-    var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
-    var layerStates = {};
-    for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-      layerStates[goog.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
-    }
-    viewState = view.getState();
-    frameState = /** @type {olx.FrameState} */ ({
-      animate: false,
-      attributions: {},
-      coordinateToPixelMatrix: this.coordinateToPixelMatrix_,
-      extent: null,
-      focus: goog.isNull(this.focus_) ? viewState.center : this.focus_,
-      index: this.frameIndex_++,
-      layerStates: layerStates,
-      layerStatesArray: layerStatesArray,
-      logos: this.logos_,
-      pixelRatio: this.pixelRatio_,
-      pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_,
-      postRenderFunctions: [],
-      size: size,
-      skippedFeatureUids: this.skippedFeatureUids_,
-      tileQueue: this.tileQueue_,
-      time: time,
-      usedTiles: {},
-      viewState: viewState,
-      viewHints: viewHints,
-      wantedTiles: {}
-    });
+/**
+ * @param {string} featureKey
+ * @param {ol.Feature} feature
+ * @private
+ */
+ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) {
+  var id = feature.getId();
+  if (goog.isDef(id)) {
+    this.idIndex_[id.toString()] = feature;
+  } else {
+    goog.asserts.assert(!(featureKey in this.undefIdIndex_),
+        'Feature already added to the source');
+    this.undefIdIndex_[featureKey] = feature;
   }
+};
 
-  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;
 
-  if (!goog.isNull(frameState)) {
-    frameState.extent = ol.extent.getForViewAndSize(viewState.center,
-        viewState.resolution, viewState.rotation, frameState.size);
-  }
+/**
+ * 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();
+};
 
-  this.frameState_ = frameState;
-  this.renderer_.renderFrame(frameState);
 
-  if (!goog.isNull(frameState)) {
-    if (frameState.animate) {
-      this.render();
+/**
+ * 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 validFeatures = [];
+  for (i = 0, length = features.length; i < length; i++) {
+    feature = features[i];
+    featureKey = goog.getUid(feature).toString();
+    this.setupChangeEvents_(featureKey, feature);
+
+    var geometry = feature.getGeometry();
+    if (goog.isDefAndNotNull(geometry)) {
+      var extent = geometry.getExtent();
+      extents.push(extent);
+      validFeatures.push(feature);
+    } else {
+      this.nullGeometryFeatures_[featureKey] = feature;
     }
-    Array.prototype.push.apply(
-        this.postRenderFunctions_, frameState.postRenderFunctions);
+  }
+  this.rBush_.load(extents, validFeatures);
 
-    var idle = this.preRenderFunctions_.length === 0 &&
-        !frameState.viewHints[ol.ViewHint.ANIMATING] &&
-        !frameState.viewHints[ol.ViewHint.INTERACTING] &&
-        (!this.previousExtent_ ||
-            !ol.extent.equals(frameState.extent, this.previousExtent_));
+  for (i = 0, length = features.length; i < length; i++) {
+    feature = features[i];
+    featureKey = goog.getUid(feature).toString();
+    this.addToIndex_(featureKey, feature);
+    this.dispatchEvent(new ol.source.VectorEvent(
+        ol.source.VectorEventType.ADDFEATURE, feature));
+  }
+};
 
-    if (idle) {
-      this.dispatchEvent(
-          new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
-      this.previousExtent_ = ol.extent.clone(frameState.extent);
+
+/**
+ * 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];
+      goog.array.forEach(keys, goog.events.unlistenByKey);
     }
+    this.featureChangeKeys_ = {};
+    this.idIndex_ = {};
+    this.undefIdIndex_ = {};
+  } else {
+    var rmFeatureInternal = this.removeFeatureInternal;
+    this.rBush_.forEach(rmFeatureInternal, this);
+    goog.object.forEach(this.nullGeometryFeatures_, rmFeatureInternal, this);
+    goog.asserts.assert(goog.object.isEmpty(this.featureChangeKeys_));
+    goog.asserts.assert(goog.object.isEmpty(this.idIndex_));
+    goog.asserts.assert(goog.object.isEmpty(this.undefIdIndex_));
   }
 
-  this.dispatchEvent(
-      new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));
-
-  goog.async.nextTick(this.handlePostRender, this);
+  this.rBush_.clear();
+  this.nullGeometryFeatures_ = {};
 
+  var clearEvent = new ol.source.VectorEvent(ol.source.VectorEventType.CLEAR);
+  this.dispatchEvent(clearEvent);
+  this.changed();
 };
 
 
 /**
- * Sets the layergroup of this map.
- * @param {ol.layer.Group} layerGroup A layer group containing the layers in
- *     this map.
- * @observable
+ * 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.Map.prototype.setLayerGroup = function(layerGroup) {
-  this.set(ol.MapProperty.LAYERGROUP, layerGroup);
+ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) {
+  return this.rBush_.forEach(callback, opt_this);
 };
-goog.exportProperty(
-    ol.Map.prototype,
-    'setLayerGroup',
-    ol.Map.prototype.setLayerGroup);
 
 
 /**
- * Set the size of this map.
- * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
- * @observable
- * @api
+ * 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.Map.prototype.setSize = function(size) {
-  this.set(ol.MapProperty.SIZE, size);
+ol.source.Vector.prototype.forEachFeatureAtCoordinate =
+    function(coordinate, callback, opt_this) {
+  var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]];
+  return this.forEachFeatureInExtent(extent, function(feature) {
+    var geometry = feature.getGeometry();
+    goog.asserts.assert(goog.isDefAndNotNull(geometry));
+    if (geometry.containsCoordinate(coordinate)) {
+      return callback.call(opt_this, feature);
+    } else {
+      return undefined;
+    }
+  });
 };
-goog.exportProperty(
-    ol.Map.prototype,
-    'setSize',
-    ol.Map.prototype.setSize);
 
 
 /**
- * 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
+ * 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.
+ *
+ * @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.Map.prototype.setTarget = function(target) {
-  this.set(ol.MapProperty.TARGET, target);
+ol.source.Vector.prototype.forEachFeatureInExtent =
+    function(extent, callback, opt_this) {
+  return this.rBush_.forEachInExtent(extent, callback, opt_this);
 };
-goog.exportProperty(
-    ol.Map.prototype,
-    'setTarget',
-    ol.Map.prototype.setTarget);
 
 
 /**
- * Set the view for this map.
- * @param {ol.View} view The view that controls this map.
- * @observable
- * @api stable
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {function(this: T, ol.Feature): S} f Callback.
+ * @param {T=} opt_this The object to use as `this` in `f`.
+ * @return {S|undefined}
+ * @template T,S
  */
-ol.Map.prototype.setView = function(view) {
-  this.set(ol.MapProperty.VIEW, view);
+ol.source.Vector.prototype.forEachFeatureInExtentAtResolution =
+    function(extent, resolution, f, opt_this) {
+  return this.forEachFeatureInExtent(extent, f, opt_this);
 };
-goog.exportProperty(
-    ol.Map.prototype,
-    'setView',
-    ol.Map.prototype.setView);
 
 
 /**
- * @param {ol.Feature} feature Feature.
+ * 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.Map.prototype.skipFeature = function(feature) {
-  var featureUid = goog.getUid(feature).toString();
-  this.skippedFeatureUids_[featureUid] = true;
-  this.render();
+ol.source.Vector.prototype.forEachFeatureIntersectingExtent =
+    function(extent, callback, opt_this) {
+  return this.forEachFeatureInExtent(extent,
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {S|undefined}
+       * @template S
+       */
+      function(feature) {
+        var geometry = feature.getGeometry();
+        goog.asserts.assert(goog.isDefAndNotNull(geometry));
+        if (geometry.intersectsExtent(extent)) {
+          var result = callback.call(opt_this, feature);
+          if (result) {
+            return result;
+          }
+        }
+      });
 };
 
 
 /**
- * Force a recalculation of the map viewport size.  This should be called when
- * third-party code changes the size of the map viewport.
+ * Get all features on the source.
+ * @return {Array.<ol.Feature>} Features.
  * @api stable
  */
-ol.Map.prototype.updateSize = function() {
-  var target = this.getTarget();
-
-  /**
-   * @type {Element}
-   */
-  var targetElement = goog.isDef(target) ? goog.dom.getElement(target) : null;
-
-  if (goog.isNull(targetElement)) {
-    this.setSize(undefined);
-  } else {
-    var size = goog.style.getContentBoxSize(targetElement);
-    this.setSize([size.width, size.height]);
+ol.source.Vector.prototype.getFeatures = function() {
+  var features = this.rBush_.getAll();
+  if (!goog.object.isEmpty(this.nullGeometryFeatures_)) {
+    goog.array.extend(
+        features, goog.object.getValues(this.nullGeometryFeatures_));
   }
+  return features;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
+ * Get all features whose geometry intersects the provided coordinate.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {Array.<ol.Feature>} Features.
+ * @api stable
  */
-ol.Map.prototype.unskipFeature = function(feature) {
-  var featureUid = goog.getUid(feature).toString();
-  delete this.skippedFeatureUids_[featureUid];
-  this.render();
+ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
+  var features = [];
+  this.forEachFeatureAtCoordinate(coordinate, function(feature) {
+    features.push(feature);
+  });
+  return features;
 };
 
 
 /**
- * @typedef {{controls: ol.Collection.<ol.control.Control>,
- *            deviceOptions: olx.DeviceOptions,
- *            interactions: ol.Collection.<ol.interaction.Interaction>,
- *            keyboardEventTarget: (Element|Document),
- *            logos: Object,
- *            overlays: ol.Collection.<ol.Overlay>,
- *            rendererConstructor:
- *                function(new: ol.renderer.Map, Element, ol.Map),
- *            values: Object.<string, *>}}
+ * @param {ol.Extent} extent Extent.
+ * @return {Array.<ol.Feature>} Features.
  */
-ol.MapOptionsInternal;
+ol.source.Vector.prototype.getFeaturesInExtent = function(extent) {
+  return this.rBush_.getInExtent(extent);
+};
 
 
 /**
- * @param {olx.MapOptions} options Map options.
- * @return {ol.MapOptionsInternal} Internal map options.
+ * Get the closest feature to the provided coordinate.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {ol.Feature} Closest feature.
+ * @api stable
  */
-ol.Map.createOptionsInternal = function(options) {
-
-  /**
-   * @type {Element|Document}
-   */
-  var keyboardEventTarget = null;
-  if (goog.isDef(options.keyboardEventTarget)) {
-    // cannot use goog.dom.getElement because its argument cannot be
-    // of type Document
-    keyboardEventTarget = goog.isString(options.keyboardEventTarget) ?
-        document.getElementById(options.keyboardEventTarget) :
-        options.keyboardEventTarget;
-  }
-
-  /**
-   * @type {Object.<string, *>}
-   */
-  var values = {};
+ol.source.Vector.prototype.getClosestFeatureToCoordinate =
+    function(coordinate) {
+  // Find the closest feature using branch and bound.  We start searching an
+  // infinite extent, and find the distance from the first feature found.  This
+  // becomes the closest feature.  We then compute a smaller extent which any
+  // closer feature must intersect.  We continue searching with this smaller
+  // extent, trying to find a closer feature.  Every time we find a closer
+  // feature, we update the extent being searched so that any even closer
+  // feature must intersect it.  We continue until we run out of features.
+  var x = coordinate[0];
+  var y = coordinate[1];
+  var closestFeature = null;
+  var closestPoint = [NaN, NaN];
+  var minSquaredDistance = Infinity;
+  var extent = [-Infinity, -Infinity, Infinity, Infinity];
+  this.rBush_.forEachInExtent(extent,
+      /**
+       * @param {ol.Feature} feature Feature.
+       */
+      function(feature) {
+        var geometry = feature.getGeometry();
+        goog.asserts.assert(goog.isDefAndNotNull(geometry));
+        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;
+};
 
-  var logos = {};
-  if (!goog.isDef(options.logo) ||
-      (goog.isBoolean(options.logo) && options.logo)) {
-    logos[ol.OL3_LOGO_URL] = ol.OL3_URL;
-  } else {
-    var logo = options.logo;
-    if (goog.isString(logo)) {
-      logos[logo] = '';
-    } else if (goog.isObject(logo)) {
-      goog.asserts.assertString(logo.href);
-      goog.asserts.assertString(logo.src);
-      logos[logo.src] = logo.href;
-    }
-  }
 
-  var layerGroup = (options.layers instanceof ol.layer.Group) ?
-      options.layers : new ol.layer.Group({layers: options.layers});
-  values[ol.MapProperty.LAYERGROUP] = layerGroup;
+/**
+ * Get the extent of the features currently in the source.
+ * @return {ol.Extent} Extent.
+ * @api stable
+ */
+ol.source.Vector.prototype.getExtent = function() {
+  return this.rBush_.getExtent();
+};
 
-  values[ol.MapProperty.TARGET] = options.target;
 
-  values[ol.MapProperty.VIEW] = goog.isDef(options.view) ?
-      options.view : new ol.View();
+/**
+ * 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 goog.isDef(feature) ? feature : null;
+};
 
-  /**
-   * @type {function(new: ol.renderer.Map, Element, ol.Map)}
-   */
-  var rendererConstructor = ol.renderer.Map;
 
-  /**
-   * @type {Array.<ol.RendererType>}
-   */
-  var rendererTypes;
-  if (goog.isDef(options.renderer)) {
-    if (goog.isArray(options.renderer)) {
-      rendererTypes = options.renderer;
-    } else if (goog.isString(options.renderer)) {
-      rendererTypes = [options.renderer];
-    } else {
-      goog.asserts.fail('Incorrect format for renderer option');
+/**
+ * @param {goog.events.Event} event Event.
+ * @private
+ */
+ol.source.Vector.prototype.handleFeatureChange_ = function(event) {
+  var feature = /** @type {ol.Feature} */ (event.target);
+  var featureKey = goog.getUid(feature).toString();
+  var geometry = feature.getGeometry();
+  if (!goog.isDefAndNotNull(geometry)) {
+    if (!(featureKey in this.nullGeometryFeatures_)) {
+      this.rBush_.remove(feature);
+      this.nullGeometryFeatures_[featureKey] = feature;
     }
   } else {
-    rendererTypes = ol.DEFAULT_RENDERER_TYPES;
-  }
-
-  var i, ii;
-  for (i = 0, ii = rendererTypes.length; i < ii; ++i) {
-    /** @type {ol.RendererType} */
-    var rendererType = rendererTypes[i];
-    if (ol.ENABLE_CANVAS && rendererType == ol.RendererType.CANVAS) {
-      if (ol.has.CANVAS) {
-        rendererConstructor = ol.renderer.canvas.Map;
-        break;
-      }
-    } else if (ol.ENABLE_DOM && rendererType == ol.RendererType.DOM) {
-      if (ol.has.DOM) {
-        rendererConstructor = ol.renderer.dom.Map;
-        break;
-      }
-    } else if (ol.ENABLE_WEBGL && rendererType == ol.RendererType.WEBGL) {
-      if (ol.has.WEBGL) {
-        rendererConstructor = ol.renderer.webgl.Map;
-        break;
-      }
+    var extent = geometry.getExtent();
+    if (featureKey in this.nullGeometryFeatures_) {
+      delete this.nullGeometryFeatures_[featureKey];
+      this.rBush_.insert(extent, feature);
+    } else {
+      this.rBush_.update(extent, feature);
     }
   }
-
-  var controls;
-  if (goog.isDef(options.controls)) {
-    if (goog.isArray(options.controls)) {
-      controls = new ol.Collection(goog.array.clone(options.controls));
+  var id = feature.getId();
+  var removed;
+  if (goog.isDef(id)) {
+    var sid = id.toString();
+    if (featureKey in this.undefIdIndex_) {
+      delete this.undefIdIndex_[featureKey];
+      this.idIndex_[sid] = feature;
     } else {
-      goog.asserts.assertInstanceof(options.controls, ol.Collection);
-      controls = options.controls;
+      if (this.idIndex_[sid] !== feature) {
+        removed = this.removeFromIdIndex_(feature);
+        goog.asserts.assert(removed,
+            'Expected feature to be removed from index');
+        this.idIndex_[sid] = feature;
+      }
     }
   } else {
-    controls = ol.control.defaults();
+    if (!(featureKey in this.undefIdIndex_)) {
+      removed = this.removeFromIdIndex_(feature);
+      goog.asserts.assert(removed,
+          'Expected feature to be removed from index');
+      this.undefIdIndex_[featureKey] = feature;
+    } else {
+      goog.asserts.assert(this.undefIdIndex_[featureKey] === feature);
+    }
   }
+  this.changed();
+  this.dispatchEvent(new ol.source.VectorEvent(
+      ol.source.VectorEventType.CHANGEFEATURE, feature));
+};
 
-  var deviceOptions = goog.isDef(options.deviceOptions) ?
-      options.deviceOptions : /** @type {olx.DeviceOptions} */ ({});
 
-  var interactions;
-  if (goog.isDef(options.interactions)) {
-    if (goog.isArray(options.interactions)) {
-      interactions = new ol.Collection(goog.array.clone(options.interactions));
-    } else {
-      goog.asserts.assertInstanceof(options.interactions, ol.Collection);
-      interactions = options.interactions;
-    }
+/**
+ * @return {boolean} Is empty.
+ */
+ol.source.Vector.prototype.isEmpty = function() {
+  return this.rBush_.isEmpty() &&
+      goog.object.isEmpty(this.nullGeometryFeatures_);
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.Projection} projection Projection.
+ */
+ol.source.Vector.prototype.loadFeatures = goog.nullFunction;
+
+
+/**
+ * Remove a single feature from the source.  If you want to remove all features
+ * at once, use the {@link ol.source.Vector#clear source.clear()} method
+ * instead.
+ * @param {ol.Feature} feature Feature to remove.
+ * @api stable
+ */
+ol.source.Vector.prototype.removeFeature = function(feature) {
+  var featureKey = goog.getUid(feature).toString();
+  if (featureKey in this.nullGeometryFeatures_) {
+    delete this.nullGeometryFeatures_[featureKey];
   } else {
-    interactions = ol.interaction.defaults();
+    this.rBush_.remove(feature);
   }
+  this.removeFeatureInternal(feature);
+  this.changed();
+};
 
-  var overlays;
-  if (goog.isDef(options.overlays)) {
-    if (goog.isArray(options.overlays)) {
-      overlays = new ol.Collection(goog.array.clone(options.overlays));
-    } else {
-      goog.asserts.assertInstanceof(options.overlays, ol.Collection);
-      overlays = options.overlays;
-    }
+
+/**
+ * Remove feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
+ */
+ol.source.Vector.prototype.removeFeatureInternal = function(feature) {
+  var featureKey = goog.getUid(feature).toString();
+  goog.asserts.assert(featureKey in this.featureChangeKeys_);
+  goog.array.forEach(this.featureChangeKeys_[featureKey],
+      goog.events.unlistenByKey);
+  delete this.featureChangeKeys_[featureKey];
+  var id = feature.getId();
+  if (goog.isDef(id)) {
+    delete this.idIndex_[id.toString()];
   } else {
-    overlays = new ol.Collection();
+    delete this.undefIdIndex_[featureKey];
   }
+  this.dispatchEvent(new ol.source.VectorEvent(
+      ol.source.VectorEventType.REMOVEFEATURE, feature));
+};
 
-  return {
-    controls: controls,
-    deviceOptions: deviceOptions,
-    interactions: interactions,
-    keyboardEventTarget: keyboardEventTarget,
-    logos: logos,
-    overlays: overlays,
-    rendererConstructor: rendererConstructor,
-    values: values
-  };
 
+/**
+ * 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;
 };
 
 
-ol.proj.common.add();
 
+/**
+ * @classdesc
+ * Events emitted by {@link ol.source.Vector} instances are instances of this
+ * type.
+ *
+ * @constructor
+ * @extends {goog.events.Event}
+ * @implements {oli.source.VectorEvent}
+ * @param {string} type Type.
+ * @param {ol.Feature=} opt_feature Feature.
+ */
+ol.source.VectorEvent = function(type, opt_feature) {
 
-if (goog.DEBUG) {
-  (function() {
-    goog.debug.Console.autoInstall();
-    var logger = goog.log.getLogger('ol');
-    logger.setLevel(goog.log.Level.FINEST);
-  })();
-}
+  goog.base(this, type);
 
-// Based on rbush https://github.com/mourner/rbush
-// Copyright (c) 2013 Vladimir Agafonkin
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
+  /**
+   * The feature being added or removed.
+   * @type {ol.Feature|undefined}
+   * @api stable
+   */
+  this.feature = opt_feature;
 
-// FIXME bulk inserts
-// FIXME is level argument needed to insert_?
+};
+goog.inherits(ol.source.VectorEvent, goog.events.Event);
 
-goog.provide('ol.structs.RBush');
+goog.provide('ol.DrawEvent');
+goog.provide('ol.interaction.Draw');
 
-goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('ol.extent');
-
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('ol.Collection');
+goog.require('ol.Coordinate');
+goog.require('ol.Feature');
+goog.require('ol.FeatureOverlay');
+goog.require('ol.Map');
+goog.require('ol.MapBrowserEvent');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.Object');
+goog.require('ol.events.condition');
+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.InteractionProperty');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Style');
 
 
 /**
- * @constructor
- * @param {ol.Extent} extent Extent.
- * @param {number} height Height.
- * @param {Array.<ol.structs.RBushNode.<T>>} children Children.
- * @param {?T} value Value.
- * @struct
- * @template T
+ * @enum {string}
  */
-ol.structs.RBushNode = function(extent, height, children, value) {
-
-  if (height === 0) {
-    goog.asserts.assert(goog.isNull(children));
-    goog.asserts.assert(!goog.isNull(value));
-  } else {
-    goog.asserts.assert(!goog.isNull(children));
-    goog.asserts.assert(goog.isNull(value));
-  }
-
-  /**
-   * @type {ol.Extent}
-   */
-  this.extent = extent;
-
-  /**
-   * @type {number}
-   */
-  this.height = height;
-
+ol.DrawEventType = {
   /**
-   * @type {Array.<ol.structs.RBushNode.<T>>}
+   * Triggered upon feature draw start
+   * @event ol.DrawEvent#drawstart
+   * @api stable
    */
-  this.children = children;
-
+  DRAWSTART: 'drawstart',
   /**
-   * @type {?T}
+   * Triggered upon feature draw end
+   * @event ol.DrawEvent#drawend
+   * @api stable
    */
-  this.value = value;
-
+  DRAWEND: 'drawend'
 };
 
 
+
 /**
- * @param {ol.structs.RBushNode.<T>} node1 Node 1.
- * @param {ol.structs.RBushNode.<T>} node2 Node 2.
- * @return {number} Compare minimum X.
- * @template T
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Draw} instances are instances of
+ * this type.
+ *
+ * @constructor
+ * @extends {goog.events.Event}
+ * @implements {oli.DrawEvent}
+ * @param {ol.DrawEventType} type Type.
+ * @param {ol.Feature} feature The feature drawn.
  */
-ol.structs.RBushNode.compareMinX = function(node1, node2) {
-  return node1.extent[0] - node2.extent[0];
-};
+ol.DrawEvent = function(type, feature) {
 
+  goog.base(this, type);
+
+  /**
+   * The feature being drawn.
+   * @type {ol.Feature}
+   * @api stable
+   */
+  this.feature = feature;
 
-/**
- * @param {ol.structs.RBushNode.<T>} node1 Node 1.
- * @param {ol.structs.RBushNode.<T>} node2 Node 2.
- * @return {number} Compare minimum Y.
- * @template T
- */
-ol.structs.RBushNode.compareMinY = function(node1, node2) {
-  return node1.extent[1] - node2.extent[1];
 };
+goog.inherits(ol.DrawEvent, goog.events.Event);
+
 
 
 /**
- * @param {number} maxEntries Max entries.
+ * @classdesc
+ * Interaction that allows drawing geometries.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.DrawEvent
+ * @param {olx.interaction.DrawOptions} options Options.
+ * @api stable
  */
-ol.structs.RBushNode.prototype.assertValid = function(maxEntries) {
-  if (this.height === 0) {
-    goog.asserts.assert(goog.isNull(this.children));
-    goog.asserts.assert(!goog.isNull(this.value));
-  } else {
-    goog.asserts.assert(!goog.isNull(this.children));
-    goog.asserts.assert(goog.isNull(this.value));
-    goog.asserts.assert(this.children.length <= maxEntries);
-    var i, ii;
-    for (i = 0, ii = this.children.length; i < ii; ++i) {
-      var child = this.children[i];
-      goog.asserts.assert(ol.extent.containsExtent(this.extent, child.extent));
-      child.assertValid(maxEntries);
-    }
-  }
-};
+ol.interaction.Draw = function(options) {
 
+  goog.base(this, {
+    handleDownEvent: ol.interaction.Draw.handleDownEvent_,
+    handleEvent: ol.interaction.Draw.handleEvent,
+    handleUpEvent: ol.interaction.Draw.handleUpEvent_
+  });
 
-/**
- * @param {number} start Start.
- * @param {number} stop Stop.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.structs.RBushNode.prototype.getChildrenExtent =
-    function(start, stop, opt_extent) {
-  goog.asserts.assert(!this.isLeaf());
-  var children = this.children;
-  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
-  var i;
-  for (i = start; i < stop; ++i) {
-    ol.extent.extend(extent, children[i].extent);
-  }
-  return extent;
-};
+  /**
+   * @type {ol.Pixel}
+   * @private
+   */
+  this.downPx_ = null;
 
+  /**
+   * Target source for drawn features.
+   * @type {ol.source.Vector}
+   * @private
+   */
+  this.source_ = goog.isDef(options.source) ? options.source : null;
 
-/**
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
- * @param {Array.<ol.structs.RBushNode.<T>>} path Path.
- * @return {boolean} Removed.
- */
-ol.structs.RBushNode.prototype.remove = function(extent, value, path) {
-  var children = this.children;
-  var ii = children.length;
-  var child, i;
-  if (this.height == 1) {
-    for (i = 0; i < ii; ++i) {
-      child = children[i];
-      if (child.value === value) {
-        goog.array.removeAt(children, i);
-        return true;
-      }
-    }
-  } else {
-    goog.asserts.assert(this.height > 1);
-    for (i = 0; i < ii; ++i) {
-      child = children[i];
-      if (ol.extent.containsExtent(child.extent, extent)) {
-        path.push(child);
-        if (child.remove(extent, value, path)) {
-          return true;
-        }
-        path.pop();
-      }
-    }
-  }
-  return false;
-};
+  /**
+   * Target collection for drawn features.
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
+   */
+  this.features_ = goog.isDef(options.features) ? options.features : null;
 
+  /**
+   * Pixel distance for snapping.
+   * @type {number}
+   * @private
+   */
+  this.snapTolerance_ = goog.isDef(options.snapTolerance) ?
+      options.snapTolerance : 12;
 
-/**
- * FIXME empty description for jsdoc
- */
-ol.structs.RBushNode.prototype.updateExtent = function() {
-  goog.asserts.assert(!this.isLeaf());
-  var extent = ol.extent.createOrUpdateEmpty(this.extent);
-  var children = this.children;
-  var i, ii;
-  for (i = 0, ii = children.length; i < ii; ++i) {
-    ol.extent.extend(extent, children[i].extent);
-  }
-};
+  /**
+   * The number of points that must be drawn before a polygon ring can be
+   * finished.  The default is 3.
+   * @type {number}
+   * @private
+   */
+  this.minPointsPerRing_ = goog.isDef(options.minPointsPerRing) ?
+      options.minPointsPerRing : 3;
 
+  /**
+   * Geometry type.
+   * @type {ol.geom.GeometryType}
+   * @private
+   */
+  this.type_ = options.type;
 
-/**
- * @return {boolean} Is leaf.
- */
-ol.structs.RBushNode.prototype.isLeaf = function() {
-  return goog.isNull(this.children);
-};
+  /**
+   * Drawing mode (derived from geometry type.
+   * @type {ol.interaction.DrawMode}
+   * @private
+   */
+  this.mode_ = ol.interaction.Draw.getMode_(this.type_);
 
+  /**
+   * 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;
 
-/**
- * @constructor
- * @param {number=} opt_maxEntries Max entries.
- * @see https://github.com/mourner/rbush
- * @struct
- * @template T
- */
-ol.structs.RBush = function(opt_maxEntries) {
+  /**
+   * Sketch point.
+   * @type {ol.Feature}
+   * @private
+   */
+  this.sketchPoint_ = null;
 
   /**
+   * Sketch line. Used when drawing polygon.
+   * @type {ol.Feature}
    * @private
-   * @type {number}
    */
-  this.maxEntries_ =
-      Math.max(4, goog.isDef(opt_maxEntries) ? opt_maxEntries : 9);
+  this.sketchLine_ = null;
 
   /**
+   * Sketch polygon. Used when drawing polygon.
+   * @type {Array.<Array.<ol.Coordinate>>}
    * @private
-   * @type {number}
    */
-  this.minEntries_ = Math.max(2, Math.ceil(0.4 * this.maxEntries_));
+  this.sketchPolygonCoords_ = null;
 
   /**
+   * Squared tolerance for handling up events.  If the squared distance
+   * between a down and up event is greater than this tolerance, up events
+   * will not be handled.
+   * @type {number}
    * @private
-   * @type {ol.structs.RBushNode.<T>}
    */
-  this.root_ = new ol.structs.RBushNode(ol.extent.createEmpty(), 1, [], null);
+  this.squaredClickTolerance_ = 4;
 
   /**
+   * Draw overlay where our sketch features are drawn.
+   * @type {ol.FeatureOverlay}
    * @private
-   * @type {Object.<string, ol.Extent>}
    */
-  this.valueExtent_ = {};
+  this.overlay_ = new ol.FeatureOverlay({
+    style: goog.isDef(options.style) ?
+        options.style : ol.interaction.Draw.getDefaultStyleFunction()
+  });
 
-  if (goog.DEBUG) {
-    /**
-     * @private
-     * @type {number}
-     */
-    this.readers_ = 0;
-  }
+  /**
+   * Name of the geometry attribute for newly created features.
+   * @type {string|undefined}
+   * @private
+   */
+  this.geometryName_ = options.geometryName;
 
-};
+  /**
+   * @private
+   * @type {ol.events.ConditionType}
+   */
+  this.condition_ = goog.isDef(options.condition) ?
+      options.condition : ol.events.condition.noModifierKeys;
 
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.interaction.InteractionProperty.ACTIVE),
+      this.updateState_, false, this);
 
-/**
- * @param {ol.structs.RBushNode.<T>} node Node.
- * @param {function(ol.structs.RBushNode.<T>, ol.structs.RBushNode.<T>): number}
- *     compare Compare.
- * @private
- * @return {number} All distance margin.
- */
-ol.structs.RBush.prototype.allDistMargin_ = function(node, compare) {
-  var children = node.children;
-  var m = this.minEntries_;
-  var M = children.length;
-  var i;
-  goog.array.sort(children, compare);
-  var leftExtent = node.getChildrenExtent(0, m);
-  var rightExtent = node.getChildrenExtent(M - m, M);
-  var margin =
-      ol.extent.getMargin(leftExtent) + ol.extent.getMargin(rightExtent);
-  for (i = m; i < M - m; ++i) {
-    ol.extent.extend(leftExtent, children[i].extent);
-    margin += ol.extent.getMargin(leftExtent);
-  }
-  for (i = M - m - 1; i >= m; --i) {
-    ol.extent.extend(rightExtent, children[i].extent);
-    margin += ol.extent.getMargin(rightExtent);
-  }
-  return margin;
 };
+goog.inherits(ol.interaction.Draw, ol.interaction.Pointer);
 
 
 /**
- * FIXME empty description for jsdoc
+ * @return {ol.style.StyleFunction} Styles.
  */
-ol.structs.RBush.prototype.assertValid = function() {
-  this.root_.assertValid(this.maxEntries_);
+ol.interaction.Draw.getDefaultStyleFunction = function() {
+  var styles = ol.style.createDefaultEditingStyles();
+  return function(feature, resolution) {
+    return styles[feature.getGeometry().getType()];
+  };
 };
 
 
 /**
- * @param {ol.structs.RBushNode.<T>} node Node.
- * @private
+ * @inheritDoc
  */
-ol.structs.RBush.prototype.chooseSplitAxis_ = function(node) {
-  var xMargin = this.allDistMargin_(node, ol.structs.RBushNode.compareMinX);
-  var yMargin = this.allDistMargin_(node, ol.structs.RBushNode.compareMinY);
-  if (xMargin < yMargin) {
-    goog.array.sort(node.children, ol.structs.RBushNode.compareMinX);
-  }
+ol.interaction.Draw.prototype.setMap = function(map) {
+  goog.base(this, 'setMap', map);
+  this.updateState_();
 };
 
 
 /**
- * @param {ol.structs.RBushNode.<T>} node Node.
- * @private
- * @return {number} Split index.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.Draw}
+ * @api
  */
-ol.structs.RBush.prototype.chooseSplitIndex_ = function(node) {
-  var children = node.children;
-  var m = this.minEntries_;
-  var M = children.length;
-  var minOverlap = Infinity;
-  var minArea = Infinity;
-  var extent1 = ol.extent.createEmpty();
-  var extent2 = ol.extent.createEmpty();
-  var index = 0;
-  var i;
-  for (i = m; i <= M - m; ++i) {
-    extent1 = node.getChildrenExtent(0, i, extent1);
-    extent2 = node.getChildrenExtent(i, M, extent2);
-    var overlap = ol.extent.getIntersectionArea(extent1, extent2);
-    var area = ol.extent.getArea(extent1) + ol.extent.getArea(extent2);
-    if (overlap < minOverlap) {
-      minOverlap = overlap;
-      minArea = Math.min(area, minArea);
-      index = i;
-    } else if (overlap == minOverlap && area < minArea) {
-      minArea = area;
-      index = i;
-    }
+ol.interaction.Draw.handleEvent = function(mapBrowserEvent) {
+  var map = mapBrowserEvent.map;
+  if (!map.isDef()) {
+    return true;
   }
-  return index;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {ol.structs.RBushNode.<T>} node Node.
- * @param {number} level Level.
- * @param {Array.<ol.structs.RBushNode.<T>>} path Path.
- * @private
- * @return {ol.structs.RBushNode.<T>} Node.
- */
-ol.structs.RBush.prototype.chooseSubtree_ =
-    function(extent, node, level, path) {
-  while (!node.isLeaf() && path.length - 1 != level) {
-    var minArea = Infinity;
-    var minEnlargement = Infinity;
-    var children = node.children;
-    var bestChild = null;
-    var i, ii;
-    for (i = 0, ii = children.length; i < ii; ++i) {
-      var child = children[i];
-      var area = ol.extent.getArea(child.extent);
-      var enlargement = ol.extent.getEnlargedArea(child.extent, extent) - area;
-      if (enlargement < minEnlargement) {
-        minEnlargement = enlargement;
-        minArea = Math.min(area, minArea);
-        bestChild = child;
-      } else if (enlargement == minEnlargement && area < minArea) {
-        minArea = area;
-        bestChild = child;
-      }
-    }
-    goog.asserts.assert(!goog.isNull(bestChild));
-    node = bestChild;
-    path.push(node);
+  var pass = true;
+  if (mapBrowserEvent.type === ol.MapBrowserEvent.EventType.POINTERMOVE) {
+    pass = this.handlePointerMove_(mapBrowserEvent);
+  } else if (mapBrowserEvent.type === ol.MapBrowserEvent.EventType.DBLCLICK) {
+    pass = false;
   }
-  return node;
+  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && pass;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Draw}
+ * @private
  */
-ol.structs.RBush.prototype.clear = function() {
-  var node = this.root_;
-  node.extent = ol.extent.createOrUpdateEmpty(this.root_.extent);
-  node.height = 1;
-  node.children.length = 0;
-  node.value = null;
-  goog.object.clear(this.valueExtent_);
+ol.interaction.Draw.handleDownEvent_ = function(event) {
+  if (this.condition_(event)) {
+    this.downPx_ = event.pixel;
+    return true;
+  } else {
+    return false;
+  }
 };
 
 
 /**
- * @param {Array.<ol.structs.RBushNode.<T>>} path Path.
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Draw}
  * @private
  */
-ol.structs.RBush.prototype.condense_ = function(path) {
-  var i;
-  for (i = path.length - 1; i >= 0; --i) {
-    var node = path[i];
-    if (node.children.length === 0) {
-      if (i > 0) {
-        goog.array.remove(path[i - 1].children, node);
-      } else {
-        this.clear();
-      }
+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;
+  if (squaredDistance <= this.squaredClickTolerance_) {
+    this.handlePointerMove_(event);
+    if (goog.isNull(this.finishCoordinate_)) {
+      this.startDrawing_(event);
+    } else if (this.mode_ === ol.interaction.DrawMode.POINT ||
+        this.atFinish_(event)) {
+      this.finishDrawing();
     } else {
-      node.updateExtent();
+      this.addToDrawing_(event);
     }
+    pass = false;
   }
+  return pass;
 };
 
 
 /**
- * Calls a callback function with each node in the tree. Inside the callback,
- * no tree modifications (insert, update, remove) can be made.
- * 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
+ * Handle move events.
+ * @param {ol.MapBrowserEvent} event A move event.
+ * @return {boolean} Pass the event to other interactions.
+ * @private
  */
-ol.structs.RBush.prototype.forEach = function(callback, opt_this) {
-  if (goog.DEBUG) {
-    ++this.readers_;
-    try {
-      return this.forEach_(this.root_, callback, opt_this);
-    } finally {
-      --this.readers_;
-    }
+ol.interaction.Draw.prototype.handlePointerMove_ = function(event) {
+  if (this.mode_ === ol.interaction.DrawMode.POINT &&
+      goog.isNull(this.finishCoordinate_)) {
+    this.startDrawing_(event);
+  } else if (!goog.isNull(this.finishCoordinate_)) {
+    this.modifyDrawing_(event);
   } else {
-    return this.forEach_(this.root_, callback, opt_this);
+    this.createOrUpdateSketchPoint_(event);
   }
+  return true;
 };
 
 
 /**
- * @param {ol.structs.RBushNode.<T>} node Node.
- * @param {function(this: S, T): *} callback Callback.
- * @param {S=} opt_this The object to use as `this` in `callback`.
+ * 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
- * @return {*} Callback return value.
- * @template S
  */
-ol.structs.RBush.prototype.forEach_ = function(node, callback, opt_this) {
-  goog.asserts.assert(!node.isLeaf());
-  /** @type {Array.<ol.structs.RBushNode.<T>>} */
-  var toVisit = [node];
-  var children, i, ii, result;
-  while (toVisit.length > 0) {
-    node = toVisit.pop();
-    children = node.children;
-    if (node.height == 1) {
-      for (i = 0, ii = children.length; i < ii; ++i) {
-        result = callback.call(opt_this, children[i].value);
-        if (result) {
-          return result;
+ol.interaction.Draw.prototype.atFinish_ = function(event) {
+  var at = false;
+  if (!goog.isNull(this.sketchFeature_)) {
+    var geometry = this.sketchFeature_.getGeometry();
+    var potentiallyDone = false;
+    var potentiallyFinishCoordinates = [this.finishCoordinate_];
+    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
+      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+      potentiallyDone = geometry.getCoordinates().length > 2;
+    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
+      potentiallyDone = geometry.getCoordinates()[0].length >
+          this.minPointsPerRing_;
+      potentiallyFinishCoordinates = [this.sketchPolygonCoords_[0][0],
+        this.sketchPolygonCoords_[0][this.sketchPolygonCoords_[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];
+        at = Math.sqrt(dx * dx + dy * dy) <= this.snapTolerance_;
+        if (at) {
+          this.finishCoordinate_ = finishCoordinate;
+          break;
         }
       }
-    } else {
-      toVisit.push.apply(toVisit, children);
     }
   }
+  return at;
 };
 
 
 /**
- * Calls a callback function with each node in the provided extent. Inside the
- * callback, no tree modifications (insert, update, remove) can be made.
- * @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
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-ol.structs.RBush.prototype.forEachInExtent =
-    function(extent, callback, opt_this) {
-  if (goog.DEBUG) {
-    ++this.readers_;
-    try {
-      return this.forEachInExtent_(extent, callback, opt_this);
-    } finally {
-      --this.readers_;
-    }
+ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) {
+  var coordinates = event.coordinate.slice();
+  if (goog.isNull(this.sketchPoint_)) {
+    this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates));
+    this.updateSketchFeatures_();
   } else {
-    return this.forEachInExtent_(extent, callback, opt_this);
+    var sketchPointGeom = this.sketchPoint_.getGeometry();
+    goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point);
+    sketchPointGeom.setCoordinates(coordinates);
   }
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {function(this: S, T): *} callback Callback.
- * @param {S=} opt_this The object to use as `this` in `callback`.
+ * Start the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
  * @private
- * @return {*} Callback return value.
- * @template S
  */
-ol.structs.RBush.prototype.forEachInExtent_ =
-    function(extent, callback, opt_this) {
-  /** @type {Array.<ol.structs.RBushNode.<T>>} */
-  var toVisit = [this.root_];
-  var result;
-  while (toVisit.length > 0) {
-    var node = toVisit.pop();
-    if (ol.extent.intersects(extent, node.extent)) {
-      if (node.isLeaf()) {
-        result = callback.call(opt_this, node.value);
-        if (result) {
-          return result;
-        }
-      } else if (ol.extent.containsExtent(extent, node.extent)) {
-        result = this.forEach_(node, callback, opt_this);
-        if (result) {
-          return result;
-        }
-      } else {
-        toVisit.push.apply(toVisit, node.children);
-      }
+ol.interaction.Draw.prototype.startDrawing_ = function(event) {
+  var start = event.coordinate;
+  this.finishCoordinate_ = start;
+  var geometry;
+  if (this.mode_ === ol.interaction.DrawMode.POINT) {
+    geometry = new ol.geom.Point(start.slice());
+  } else {
+    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
+      geometry = new ol.geom.LineString([start.slice(), start.slice()]);
+    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+      this.sketchLine_ = new ol.Feature(new ol.geom.LineString([start.slice(),
+            start.slice()]));
+      this.sketchPolygonCoords_ = [[start.slice(), start.slice()]];
+      geometry = new ol.geom.Polygon(this.sketchPolygonCoords_);
     }
   }
-  return undefined;
+  goog.asserts.assert(goog.isDef(geometry));
+  this.sketchFeature_ = new ol.Feature();
+  if (goog.isDef(this.geometryName_)) {
+    this.sketchFeature_.setGeometryName(this.geometryName_);
+  }
+  this.sketchFeature_.setGeometry(geometry);
+  this.updateSketchFeatures_();
+  this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWSTART,
+      this.sketchFeature_));
 };
 
 
 /**
- * @param {function(this: S, ol.structs.RBushNode.<T>): *} callback Callback.
- * @param {S=} opt_this The object to use as `this` in `callback`.
- * @return {*} Callback return value.
- * @template S
+ * Modify the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-ol.structs.RBush.prototype.forEachNode = function(callback, opt_this) {
-  /** @type {Array.<ol.structs.RBushNode.<T>>} */
-  var toVisit = [this.root_];
-  while (toVisit.length > 0) {
-    var node = toVisit.pop();
-    var result = callback.call(opt_this, node);
-    if (result) {
-      return result;
+ol.interaction.Draw.prototype.modifyDrawing_ = function(event) {
+  var coordinate = event.coordinate;
+  var geometry = this.sketchFeature_.getGeometry();
+  var coordinates, last;
+  if (this.mode_ === ol.interaction.DrawMode.POINT) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
+    last = geometry.getCoordinates();
+    last[0] = coordinate[0];
+    last[1] = coordinate[1];
+    geometry.setCoordinates(last);
+  } else {
+    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
+      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+      coordinates = geometry.getCoordinates();
+    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
+      coordinates = this.sketchPolygonCoords_[0];
     }
-    if (!node.isLeaf()) {
-      toVisit.push.apply(toVisit, node.children);
+    if (this.atFinish_(event)) {
+      // snap to finish
+      coordinate = this.finishCoordinate_.slice();
     }
-  }
-  return undefined;
-};
-
-
-/**
- * @return {Array.<T>} All.
- */
-ol.structs.RBush.prototype.getAll = function() {
-  var values = [];
-  this.forEach(
-      /**
-       * @param {T} value Value.
-       */
-      function(value) {
-        values.push(value);
-      });
-  return values;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @return {Array.<T>} All in extent.
- */
-ol.structs.RBush.prototype.getInExtent = function(extent) {
-  var values = [];
-  this.forEachInExtent(extent,
-      /**
-       * @param {T} value Value.
-       */
-      function(value) {
-        values.push(value);
-      });
-  return values;
-};
-
-
-/**
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.structs.RBush.prototype.getExtent = function(opt_extent) {
-  return ol.extent.returnOrUpdate(this.root_.extent, opt_extent);
+    var sketchPointGeom = this.sketchPoint_.getGeometry();
+    goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point);
+    sketchPointGeom.setCoordinates(coordinate);
+    last = coordinates[coordinates.length - 1];
+    last[0] = coordinate[0];
+    last[1] = coordinate[1];
+    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
+      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+      geometry.setCoordinates(coordinates);
+    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+      var sketchLineGeom = this.sketchLine_.getGeometry();
+      goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString);
+      sketchLineGeom.setCoordinates(coordinates);
+      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
+      geometry.setCoordinates(this.sketchPolygonCoords_);
+    }
+  }
+  this.updateSketchFeatures_();
 };
 
 
 /**
- * @param {T} value Value.
+ * Add a new coordinate to the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
  * @private
- * @return {string} Key.
  */
-ol.structs.RBush.prototype.getKey_ = function(value) {
-  goog.asserts.assert(goog.isObject(value));
-  return goog.getUid(value).toString();
+ol.interaction.Draw.prototype.addToDrawing_ = function(event) {
+  var coordinate = event.coordinate;
+  var geometry = this.sketchFeature_.getGeometry();
+  var coordinates;
+  if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
+    this.finishCoordinate_ = coordinate.slice();
+    goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+    coordinates = geometry.getCoordinates();
+    coordinates.push(coordinate.slice());
+    geometry.setCoordinates(coordinates);
+  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+    this.sketchPolygonCoords_[0].push(coordinate.slice());
+    goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
+    geometry.setCoordinates(this.sketchPolygonCoords_);
+  }
+  this.updateSketchFeatures_();
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
+ * Stop drawing and add the sketch feature to the target layer.
+ * @api
  */
-ol.structs.RBush.prototype.insert = function(extent, value) {
-  if (goog.DEBUG && this.readers_) {
-    throw new Error('cannot insert value while reading');
+ol.interaction.Draw.prototype.finishDrawing = function() {
+  var sketchFeature = this.abortDrawing_();
+  goog.asserts.assert(!goog.isNull(sketchFeature));
+  var coordinates;
+  var geometry = sketchFeature.getGeometry();
+  if (this.mode_ === ol.interaction.DrawMode.POINT) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
+    coordinates = geometry.getCoordinates();
+  } else if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+    coordinates = geometry.getCoordinates();
+    // remove the redundant last point
+    coordinates.pop();
+    geometry.setCoordinates(coordinates);
+  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
+    goog.asserts.assertInstanceof(geometry, ol.geom.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
+    this.sketchPolygonCoords_[0].pop();
+    this.sketchPolygonCoords_[0].push(this.sketchPolygonCoords_[0][0]);
+    geometry.setCoordinates(this.sketchPolygonCoords_);
+    coordinates = geometry.getCoordinates();
   }
-  var key = this.getKey_(value);
-  goog.asserts.assert(!this.valueExtent_.hasOwnProperty(key));
-  this.insert_(extent, value, this.root_.height - 1);
-  this.valueExtent_[key] = ol.extent.clone(extent);
-};
 
+  // 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]));
+  }
 
-/**
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
- * @param {number} level Level.
- * @private
- * @return {ol.structs.RBushNode.<T>} Node.
- */
-ol.structs.RBush.prototype.insert_ = function(extent, value, level) {
-  /** @type {Array.<ol.structs.RBushNode.<T>>} */
-  var path = [this.root_];
-  var node = this.chooseSubtree_(extent, this.root_, level, path);
-  node.children.push(new ol.structs.RBushNode(extent, 0, null, value));
-  ol.extent.extend(node.extent, extent);
-  var i;
-  for (i = path.length - 1; i >= 0; --i) {
-    if (path[i].children.length > this.maxEntries_) {
-      this.split_(path, i);
-    } else {
-      break;
-    }
+  if (!goog.isNull(this.features_)) {
+    this.features_.push(sketchFeature);
   }
-  for (; i >= 0; --i) {
-    ol.extent.extend(path[i].extent, extent);
+  if (!goog.isNull(this.source_)) {
+    this.source_.addFeature(sketchFeature);
   }
-  return node;
+  this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWEND, sketchFeature));
 };
 
 
 /**
- * @return {boolean} Is empty.
+ * Stop drawing without adding the sketch feature to the target layer.
+ * @return {ol.Feature} The sketch feature (or null if none).
+ * @private
  */
-ol.structs.RBush.prototype.isEmpty = function() {
-  return this.root_.children.length === 0;
+ol.interaction.Draw.prototype.abortDrawing_ = function() {
+  this.finishCoordinate_ = null;
+  var sketchFeature = this.sketchFeature_;
+  if (!goog.isNull(sketchFeature)) {
+    this.sketchFeature_ = null;
+    this.sketchPoint_ = null;
+    this.sketchLine_ = null;
+    this.overlay_.getFeatures().clear();
+  }
+  return sketchFeature;
 };
 
 
 /**
- * @param {T} value Value.
- * @return {boolean} Removed.
+ * @inheritDoc
  */
-ol.structs.RBush.prototype.remove = function(value) {
-  if (goog.DEBUG && this.readers_) {
-    throw new Error('cannot remove value while reading');
-  }
-  var key = this.getKey_(value);
-  goog.asserts.assert(this.valueExtent_.hasOwnProperty(key));
-  var extent = this.valueExtent_[key];
-  delete this.valueExtent_[key];
-  return this.remove_(extent, value);
-};
+ol.interaction.Draw.prototype.shouldStopEvent = goog.functions.FALSE;
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
+ * Redraw the skecth features.
  * @private
- * @return {boolean} Removed.
  */
-ol.structs.RBush.prototype.remove_ = function(extent, value) {
-  var root = this.root_;
-  var path = [root];
-  var removed = root.remove(extent, value, path);
-  if (removed) {
-    this.condense_(path);
-  } else {
-    goog.asserts.assert(path.length == 1);
-    goog.asserts.assert(path[0] === root);
+ol.interaction.Draw.prototype.updateSketchFeatures_ = function() {
+  var sketchFeatures = [];
+  if (!goog.isNull(this.sketchFeature_)) {
+    sketchFeatures.push(this.sketchFeature_);
   }
-  return removed;
+  if (!goog.isNull(this.sketchLine_)) {
+    sketchFeatures.push(this.sketchLine_);
+  }
+  if (!goog.isNull(this.sketchPoint_)) {
+    sketchFeatures.push(this.sketchPoint_);
+  }
+  this.overlay_.setFeatures(new ol.Collection(sketchFeatures));
 };
 
 
 /**
- * @param {Array.<ol.structs.RBushNode.<T>>} path Path.
- * @param {number} level Level.
  * @private
  */
-ol.structs.RBush.prototype.split_ = function(path, level) {
-  var node = path[level];
-  this.chooseSplitAxis_(node);
-  var splitIndex = this.chooseSplitIndex_(node);
-  // FIXME too few arguments to splice here
-  var newChildren = node.children.splice(splitIndex);
-  var newNode = new ol.structs.RBushNode(
-      ol.extent.createEmpty(), node.height, newChildren, null);
-  node.updateExtent();
-  newNode.updateExtent();
-  if (level) {
-    path[level - 1].children.push(newNode);
-  } else {
-    this.splitRoot_(node, newNode);
+ol.interaction.Draw.prototype.updateState_ = function() {
+  var map = this.getMap();
+  var active = this.getActive();
+  if (goog.isNull(map) || !active) {
+    this.abortDrawing_();
   }
+  this.overlay_.setMap(active ? map : null);
 };
 
 
 /**
- * @param {ol.structs.RBushNode.<T>} node1 Node 1.
- * @param {ol.structs.RBushNode.<T>} node2 Node 2.
+ * Get the drawing mode.  The mode for mult-part geometries is the same as for
+ * their single-part cousins.
+ * @param {ol.geom.GeometryType} type Geometry type.
+ * @return {ol.interaction.DrawMode} Drawing mode.
  * @private
  */
-ol.structs.RBush.prototype.splitRoot_ = function(node1, node2) {
-  goog.asserts.assert(node1 === this.root_);
-  var height = node1.height + 1;
-  var extent = ol.extent.extend(node1.extent.slice(), node2.extent);
-  var children = [node1, node2];
-  this.root_ = new ol.structs.RBushNode(extent, height, children, null);
+ol.interaction.Draw.getMode_ = function(type) {
+  var mode;
+  if (type === ol.geom.GeometryType.POINT ||
+      type === ol.geom.GeometryType.MULTI_POINT) {
+    mode = ol.interaction.DrawMode.POINT;
+  } else if (type === ol.geom.GeometryType.LINE_STRING ||
+      type === ol.geom.GeometryType.MULTI_LINE_STRING) {
+    mode = ol.interaction.DrawMode.LINE_STRING;
+  } else if (type === ol.geom.GeometryType.POLYGON ||
+      type === ol.geom.GeometryType.MULTI_POLYGON) {
+    mode = ol.interaction.DrawMode.POLYGON;
+  }
+  goog.asserts.assert(goog.isDef(mode));
+  return mode;
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
+ * Draw mode.  This collapses multi-part geometry types with their single-part
+ * cousins.
+ * @enum {string}
  */
-ol.structs.RBush.prototype.update = function(extent, value) {
-  var key = this.getKey_(value);
-  var currentExtent = this.valueExtent_[key];
-  goog.asserts.assert(goog.isDef(currentExtent));
-  if (!ol.extent.equals(currentExtent, extent)) {
-    if (goog.DEBUG && this.readers_) {
-      throw new Error('cannot update extent while reading');
-    }
-    var removed = this.remove_(currentExtent, value);
-    goog.asserts.assert(removed);
-    this.insert_(extent, value, this.root_.height - 1);
-    this.valueExtent_[key] = ol.extent.clone(extent, currentExtent);
-  }
+ol.interaction.DrawMode = {
+  POINT: 'Point',
+  LINE_STRING: 'LineString',
+  POLYGON: 'Polygon'
 };
 
-// FIXME bulk feature upload - suppress events
-// FIXME put features in an ol.Collection
-// FIXME make change-detection more refined (notably, geometry hint)
-
-goog.provide('ol.source.Vector');
-goog.provide('ol.source.VectorEvent');
-goog.provide('ol.source.VectorEventType');
+goog.provide('ol.interaction.Modify');
 
 goog.require('goog.array');
 goog.require('goog.asserts');
 goog.require('goog.events');
-goog.require('goog.events.Event');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol.ObjectEventType');
-goog.require('ol.proj');
-goog.require('ol.source.Source');
+goog.require('goog.functions');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Feature');
+goog.require('ol.FeatureOverlay');
+goog.require('ol.MapBrowserEvent.EventType');
+goog.require('ol.ViewHint');
+goog.require('ol.coordinate');
+goog.require('ol.events.condition');
+goog.require('ol.extent');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.interaction.Pointer');
 goog.require('ol.structs.RBush');
+goog.require('ol.style.Style');
 
 
 /**
- * @enum {string}
+ * @typedef {{depth: (Array.<number>|undefined),
+ *            feature: ol.Feature,
+ *            geometry: ol.geom.SimpleGeometry,
+ *            index: (number|undefined),
+ *            segment: Array.<ol.Extent>}}
  */
-ol.source.VectorEventType = {
-  /**
-   * Triggered when a feature is added to the source.
-   * @event ol.source.VectorEvent#addfeature
-   * @api stable
-   */
-  ADDFEATURE: 'addfeature',
-  /**
-   * Triggered when a feature is removed from the source.
-   * @event ol.source.VectorEvent#removefeature
-   * @api stable
-   */
-  REMOVEFEATURE: 'removefeature'
-};
+ol.interaction.SegmentDataType;
 
 
 
 /**
  * @classdesc
- * Base class for vector sources.
+ * Interaction for modifying vector data.
  *
  * @constructor
- * @extends {ol.source.Source}
- * @fires ol.source.VectorEvent
- * @param {olx.source.VectorOptions=} opt_options Vector source options.
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.ModifyOptions} options Options.
  * @api stable
  */
-ol.source.Vector = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.interaction.Modify = function(options) {
 
   goog.base(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection,
-    state: goog.isDef(options.state) ?
-        /** @type {ol.source.State} */ (options.state) : undefined
+    handleDownEvent: ol.interaction.Modify.handleDownEvent_,
+    handleDragEvent: ol.interaction.Modify.handleDragEvent_,
+    handleEvent: ol.interaction.Modify.handleEvent,
+    handleUpEvent: ol.interaction.Modify.handleUpEvent_
   });
 
   /**
+   * @type {ol.events.ConditionType}
+   * @private
+   */
+  this.deleteCondition_ = goog.isDef(options.deleteCondition) ?
+      options.deleteCondition :
+      /** @type {ol.events.ConditionType} */ (goog.functions.and(
+          ol.events.condition.noModifierKeys,
+          ol.events.condition.singleClick));
+
+  /**
+   * Editing vertex.
+   * @type {ol.Feature}
+   * @private
+   */
+  this.vertexFeature_ = null;
+
+  /**
+   * Segments intersecting {@link this.vertexFeature_} by segment uid.
+   * @type {Object.<string, boolean>}
+   * @private
+   */
+  this.vertexSegments_ = null;
+
+  /**
+   * @type {ol.Pixel}
+   * @private
+   */
+  this.lastPixel_ = [0, 0];
+
+  /**
+   * Segment RTree for each layer
+   * @type {Object.<*, ol.structs.RBush>}
    * @private
-   * @type {ol.structs.RBush.<ol.Feature>}
    */
   this.rBush_ = new ol.structs.RBush();
 
   /**
+   * @type {number}
    * @private
-   * @type {Object.<string, ol.Feature>}
    */
-  this.nullGeometryFeatures_ = {};
+  this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ?
+      options.pixelTolerance : 10;
 
   /**
-   * A lookup of features by id (the return from feature.getId()).
+   * @type {boolean}
    * @private
-   * @type {Object.<string, ol.Feature>}
    */
-  this.idIndex_ = {};
+  this.snappedToVertex_ = false;
 
   /**
-   * A lookup of features without id (keyed by goog.getUid(feature)).
+   * @type {Array}
    * @private
-   * @type {Object.<string, ol.Feature>}
    */
-  this.undefIdIndex_ = {};
+  this.dragSegments_ = null;
 
   /**
+   * Draw overlay where are sketch features are drawn.
+   * @type {ol.FeatureOverlay}
    * @private
-   * @type {Object.<string, Array.<goog.events.Key>>}
    */
-  this.featureChangeKeys_ = {};
+  this.overlay_ = new ol.FeatureOverlay({
+    style: goog.isDef(options.style) ? options.style :
+        ol.interaction.Modify.getDefaultStyleFunction()
+  });
 
-  if (goog.isDef(options.features)) {
-    this.addFeaturesInternal(options.features);
-  }
+  /**
+  * @const
+  * @private
+  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)> }
+  */
+  this.SEGMENT_WRITERS_ = {
+    'Point': this.writePointGeometry_,
+    'LineString': this.writeLineStringGeometry_,
+    'LinearRing': this.writeLineStringGeometry_,
+    'Polygon': this.writePolygonGeometry_,
+    'MultiPoint': this.writeMultiPointGeometry_,
+    'MultiLineString': this.writeMultiLineStringGeometry_,
+    'MultiPolygon': this.writeMultiPolygonGeometry_,
+    'GeometryCollection': this.writeGeometryCollectionGeometry_
+  };
+
+  /**
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
+   */
+  this.features_ = options.features;
+
+  this.features_.forEach(this.addFeature_, this);
+  goog.events.listen(this.features_, ol.CollectionEventType.ADD,
+      this.handleFeatureAdd_, false, this);
+  goog.events.listen(this.features_, ol.CollectionEventType.REMOVE,
+      this.handleFeatureRemove_, false, this);
 
 };
-goog.inherits(ol.source.Vector, ol.source.Source);
+goog.inherits(ol.interaction.Modify, ol.interaction.Pointer);
 
 
 /**
  * @param {ol.Feature} feature Feature.
- * @api stable
+ * @private
  */
-ol.source.Vector.prototype.addFeature = function(feature) {
-  this.addFeatureInternal(feature);
-  this.dispatchChangeEvent();
+ol.interaction.Modify.prototype.addFeature_ = function(feature) {
+  var geometry = feature.getGeometry();
+  if (goog.isDef(this.SEGMENT_WRITERS_[geometry.getType()])) {
+    this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry);
+  }
+  var map = this.getMap();
+  if (!goog.isNull(map)) {
+    this.handlePointerAtPixel_(this.lastPixel_, map);
+  }
 };
 
 
 /**
- * Add a feature without firing a `change` event.
- * @param {ol.Feature} feature Feature.
- * @protected
+ * @inheritDoc
  */
-ol.source.Vector.prototype.addFeatureInternal = function(feature) {
-  var featureKey = goog.getUid(feature).toString();
-  goog.asserts.assert(!(featureKey in this.featureChangeKeys_));
-  this.featureChangeKeys_[featureKey] = [
-    goog.events.listen(feature,
-        goog.events.EventType.CHANGE,
-        this.handleFeatureChange_, false, this),
-    goog.events.listen(feature,
-        ol.ObjectEventType.PROPERTYCHANGE,
-        this.handleFeatureChange_, false, this)
-  ];
-  var geometry = feature.getGeometry();
-  if (goog.isDefAndNotNull(geometry)) {
-    var extent = geometry.getExtent();
-    this.rBush_.insert(extent, feature);
-  } else {
-    this.nullGeometryFeatures_[featureKey] = feature;
+ol.interaction.Modify.prototype.setMap = function(map) {
+  this.overlay_.setMap(map);
+  goog.base(this, 'setMap', map);
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) {
+  var feature = evt.element;
+  goog.asserts.assertInstanceof(feature, ol.Feature);
+  this.addFeature_(feature);
+};
+
+
+/**
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
+ */
+ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
+  var feature = evt.element;
+  var rBush = this.rBush_;
+  var i, nodesToRemove = [];
+  rBush.forEachInExtent(feature.getGeometry().getExtent(), function(node) {
+    if (feature === node.feature) {
+      nodesToRemove.push(node);
+    }
+  });
+  for (i = nodesToRemove.length - 1; i >= 0; --i) {
+    rBush.remove(nodesToRemove[i]);
   }
-  var id = feature.getId();
-  if (goog.isDef(id)) {
-    this.idIndex_[id.toString()] = feature;
-  } else {
-    goog.asserts.assert(!(featureKey in this.undefIdIndex_),
-        'Feature already added to the source');
-    this.undefIdIndex_[featureKey] = feature;
+  // There remains only vertexFeature…
+  if (!goog.isNull(this.vertexFeature_) &&
+      this.features_.getLength() === 0) {
+    this.overlay_.removeFeature(this.vertexFeature_);
+    this.vertexFeature_ = null;
   }
-  this.dispatchEvent(
-      new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature));
 };
 
 
 /**
- * @param {Array.<ol.Feature>} features Features.
- * @api stable
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Point} geometry Geometry.
+ * @private
  */
-ol.source.Vector.prototype.addFeatures = function(features) {
-  this.addFeaturesInternal(features);
-  this.dispatchChangeEvent();
+ol.interaction.Modify.prototype.writePointGeometry_ =
+    function(feature, geometry) {
+  var coordinates = geometry.getCoordinates();
+  var segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+    feature: feature,
+    geometry: geometry,
+    segment: [coordinates, coordinates]
+  });
+  this.rBush_.insert(geometry.getExtent(), segmentData);
 };
 
 
 /**
- * Add features without firing a `change` event.
- * @param {Array.<ol.Feature>} features Features.
- * @protected
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPoint} geometry Geometry.
+ * @private
  */
-ol.source.Vector.prototype.addFeaturesInternal = function(features) {
-  // FIXME use R-Bush bulk load when available
-  var i, ii;
-  for (i = 0, ii = features.length; i < ii; ++i) {
-    this.addFeatureInternal(features[i]);
+ol.interaction.Modify.prototype.writeMultiPointGeometry_ =
+    function(feature, geometry) {
+  var points = geometry.getCoordinates();
+  var coordinates, i, ii, segmentData;
+  for (i = 0, ii = points.length; i < ii; ++i) {
+    coordinates = points[i];
+    segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+      feature: feature,
+      geometry: geometry,
+      depth: [i],
+      index: i,
+      segment: [coordinates, coordinates]
+    });
+    this.rBush_.insert(geometry.getExtent(), segmentData);
   }
 };
 
 
 /**
- * Remove all features.
- * @api stable
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.LineString} geometry Geometry.
+ * @private
  */
-ol.source.Vector.prototype.clear = function() {
-  this.rBush_.forEach(this.removeFeatureInternal, this);
-  this.rBush_.clear();
-  goog.object.forEach(
-      this.nullGeometryFeatures_, this.removeFeatureInternal, this);
-  goog.object.clear(this.nullGeometryFeatures_);
-  goog.asserts.assert(goog.object.isEmpty(this.featureChangeKeys_));
-  this.dispatchChangeEvent();
+ol.interaction.Modify.prototype.writeLineStringGeometry_ =
+    function(feature, geometry) {
+  var coordinates = geometry.getCoordinates();
+  var i, ii, segment, segmentData;
+  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
+    segment = coordinates.slice(i, i + 2);
+    segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+      feature: feature,
+      geometry: geometry,
+      index: i,
+      segment: segment
+    });
+    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+  }
 };
 
 
 /**
- * @param {function(this: T, ol.Feature): S} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
- * @return {S|undefined}
- * @template T,S
- * @api stable
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiLineString} geometry Geometry.
+ * @private
  */
-ol.source.Vector.prototype.forEachFeature = function(f, opt_this) {
-  return this.rBush_.forEach(f, opt_this);
+ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ =
+    function(feature, geometry) {
+  var lines = geometry.getCoordinates();
+  var coordinates, i, ii, j, jj, segment, segmentData;
+  for (j = 0, jj = lines.length; j < jj; ++j) {
+    coordinates = lines[j];
+    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
+      segment = coordinates.slice(i, i + 2);
+      segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+        feature: feature,
+        geometry: geometry,
+        depth: [j],
+        index: i,
+        segment: segment
+      });
+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+    }
+  }
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {function(this: T, ol.Feature): S} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
- * @return {S|undefined}
- * @template T,S
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Polygon} geometry Geometry.
+ * @private
  */
-ol.source.Vector.prototype.forEachFeatureAtCoordinate =
-    function(coordinate, f, opt_this) {
-  var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]];
-  return this.forEachFeatureInExtent(extent, function(feature) {
-    var geometry = feature.getGeometry();
-    goog.asserts.assert(goog.isDefAndNotNull(geometry));
-    if (geometry.containsCoordinate(coordinate)) {
-      return f.call(opt_this, feature);
-    } else {
-      return undefined;
+ol.interaction.Modify.prototype.writePolygonGeometry_ =
+    function(feature, geometry) {
+  var rings = geometry.getCoordinates();
+  var coordinates, i, ii, j, jj, segment, segmentData;
+  for (j = 0, jj = rings.length; j < jj; ++j) {
+    coordinates = rings[j];
+    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
+      segment = coordinates.slice(i, i + 2);
+      segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+        feature: feature,
+        geometry: geometry,
+        depth: [j],
+        index: i,
+        segment: segment
+      });
+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
     }
-  });
+  }
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {function(this: T, ol.Feature): S} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
- * @return {S|undefined}
- * @template T,S
- * @api
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @private
  */
-ol.source.Vector.prototype.forEachFeatureInExtent =
-    function(extent, f, opt_this) {
-  return this.rBush_.forEachInExtent(extent, f, opt_this);
+ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ =
+    function(feature, geometry) {
+  var polygons = geometry.getCoordinates();
+  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
+  for (k = 0, kk = polygons.length; k < kk; ++k) {
+    rings = polygons[k];
+    for (j = 0, jj = rings.length; j < jj; ++j) {
+      coordinates = rings[j];
+      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
+        segment = coordinates.slice(i, i + 2);
+        segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+          feature: feature,
+          geometry: geometry,
+          depth: [j, k],
+          index: i,
+          segment: segment
+        });
+        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+      }
+    }
+  }
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {function(this: T, ol.Feature): S} f Callback.
- * @param {T=} opt_this The object to use as `this` in `f`.
- * @return {S|undefined}
- * @template T,S
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * @private
  */
-ol.source.Vector.prototype.forEachFeatureInExtentAtResolution =
-    function(extent, resolution, f, opt_this) {
-  return this.forEachFeatureInExtent(extent, f, opt_this);
+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]);
+  }
 };
 
 
 /**
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @return {ol.Feature} Vertex feature.
+ * @private
  */
-ol.source.Vector.prototype.getFeatures = function() {
-  var features = this.rBush_.getAll();
-  if (!goog.object.isEmpty(this.nullGeometryFeatures_)) {
-    goog.array.extend(
-        features, goog.object.getValues(this.nullGeometryFeatures_));
+ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ =
+    function(coordinates) {
+  var vertexFeature = this.vertexFeature_;
+  if (goog.isNull(vertexFeature)) {
+    vertexFeature = new ol.Feature(new ol.geom.Point(coordinates));
+    this.vertexFeature_ = vertexFeature;
+    this.overlay_.addFeature(vertexFeature);
+  } else {
+    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
+    geometry.setCoordinates(coordinates);
   }
-  return features;
+  return vertexFeature;
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Modify}
+ * @private
  */
-ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
-  var features = [];
-  this.forEachFeatureAtCoordinate(coordinate, function(feature) {
-    features.push(feature);
-  });
-  return features;
+ol.interaction.Modify.handleDownEvent_ = function(evt) {
+  this.handlePointerAtPixel_(evt.pixel, evt.map);
+  this.dragSegments_ = [];
+  var vertexFeature = this.vertexFeature_;
+  if (!goog.isNull(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);
+    for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) {
+      var segmentDataMatch = segmentDataMatches[i];
+      var segment = segmentDataMatch.segment;
+      if (ol.coordinate.equals(segment[0], vertex)) {
+        this.dragSegments_.push([segmentDataMatch, 0]);
+      } else if (ol.coordinate.equals(segment[1], vertex)) {
+        this.dragSegments_.push([segmentDataMatch, 1]);
+      } else if (goog.getUid(segment) in this.vertexSegments_) {
+        insertVertices.push([segmentDataMatch, vertex]);
+      }
+    }
+    for (i = insertVertices.length - 1; i >= 0; --i) {
+      this.insertVertex_.apply(this, insertVertices[i]);
+    }
+  }
+  return !goog.isNull(this.vertexFeature_);
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @return {Array.<ol.Feature>} Features.
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @this {ol.interaction.Modify}
+ * @private
  */
-ol.source.Vector.prototype.getFeaturesInExtent = function(extent) {
-  return this.rBush_.getInExtent(extent);
+ol.interaction.Modify.handleDragEvent_ = function(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;
+    }
+
+    geometry.setCoordinates(coordinates);
+    this.createOrUpdateVertexFeature_(vertex);
+  }
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {ol.Feature} Closest feature.
- * @api stable
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Modify}
+ * @private
  */
-ol.source.Vector.prototype.getClosestFeatureToCoordinate =
-    function(coordinate) {
-  // Find the closest feature using branch and bound.  We start searching an
-  // infinite extent, and find the distance from the first feature found.  This
-  // becomes the closest feature.  We then compute a smaller extent which any
-  // closer feature must intersect.  We continue searching with this smaller
-  // extent, trying to find a closer feature.  Every time we find a closer
-  // feature, we update the extent being searched so that any even closer
-  // feature must intersect it.  We continue until we run out of features.
-  var x = coordinate[0];
-  var y = coordinate[1];
-  var closestFeature = null;
-  var closestPoint = [NaN, NaN];
-  var minSquaredDistance = Infinity;
-  var extent = [-Infinity, -Infinity, Infinity, Infinity];
-  this.rBush_.forEachInExtent(extent,
-      /**
-       * @param {ol.Feature} feature Feature.
-       */
-      function(feature) {
-        var geometry = feature.getGeometry();
-        goog.asserts.assert(goog.isDefAndNotNull(geometry));
-        var previousMinSquaredDistance = minSquaredDistance;
-        minSquaredDistance = geometry.closestPointXY(
-            x, y, closestPoint, minSquaredDistance);
-        if (minSquaredDistance < previousMinSquaredDistance) {
-          closestFeature = feature;
-          // This is sneaky.  Reduce the extent that it is currently being
-          // searched while the R-Tree traversal using this same extent object
-          // is still in progress.  This is safe because the new extent is
-          // strictly contained by the old extent.
-          var minDistance = Math.sqrt(minSquaredDistance);
-          extent[0] = x - minDistance;
-          extent[1] = y - minDistance;
-          extent[2] = x + minDistance;
-          extent[3] = y + minDistance;
-        }
-      });
-  return closestFeature;
+ol.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);
+  }
+  return false;
 };
 
 
 /**
- * Get the extent of the features currently in the source.
- * @return {ol.Extent} Extent.
- * @api stable
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.Modify}
+ * @api
  */
-ol.source.Vector.prototype.getExtent = function() {
-  return this.rBush_.getExtent();
+ol.interaction.Modify.handleEvent = function(mapBrowserEvent) {
+  var handled;
+  if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
+      mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) {
+    this.handlePointerMove_(mapBrowserEvent);
+  }
+  if (!goog.isNull(this.vertexFeature_) && this.snappedToVertex_ &&
+      this.deleteCondition_(mapBrowserEvent)) {
+    var geometry = this.vertexFeature_.getGeometry();
+    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
+    handled = this.removeVertex_();
+  }
+  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
+      !handled;
 };
 
 
 /**
- * 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
+ * @param {ol.MapBrowserEvent} evt Event.
+ * @private
  */
-ol.source.Vector.prototype.getFeatureById = function(id) {
-  var feature = this.idIndex_[id.toString()];
-  return goog.isDef(feature) ? feature : null;
+ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
+  this.lastPixel_ = evt.pixel;
+  this.handlePointerAtPixel_(evt.pixel, evt.map);
 };
 
 
 /**
- * @param {goog.events.Event} event Event.
+ * @param {ol.Pixel} pixel Pixel
+ * @param {ol.Map} map Map.
  * @private
  */
-ol.source.Vector.prototype.handleFeatureChange_ = function(event) {
-  var feature = /** @type {ol.Feature} */ (event.target);
-  var featureKey = goog.getUid(feature).toString();
-  var geometry = feature.getGeometry();
-  if (!goog.isDefAndNotNull(geometry)) {
-    if (!(featureKey in this.nullGeometryFeatures_)) {
-      this.rBush_.remove(feature);
-      this.nullGeometryFeatures_[featureKey] = feature;
-    }
-  } else {
-    var extent = geometry.getExtent();
-    if (featureKey in this.nullGeometryFeatures_) {
-      delete this.nullGeometryFeatures_[featureKey];
-      this.rBush_.insert(extent, feature);
-    } else {
-      this.rBush_.update(extent, feature);
-    }
-  }
-  var id = feature.getId();
-  var removed;
-  if (goog.isDef(id)) {
-    var sid = id.toString();
-    if (featureKey in this.undefIdIndex_) {
-      delete this.undefIdIndex_[featureKey];
-      this.idIndex_[sid] = feature;
-    } else {
-      if (this.idIndex_[sid] !== feature) {
-        removed = this.removeFromIdIndex_(feature);
-        goog.asserts.assert(removed,
-            'Expected feature to be removed from index');
-        this.idIndex_[sid] = feature;
+ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) {
+  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
+  var sortByDistance = function(a, b) {
+    return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) -
+        ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment);
+  };
+
+  var lowerLeft = map.getCoordinateFromPixel(
+      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
+  var upperRight = map.getCoordinateFromPixel(
+      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
+  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
+
+  var rBush = this.rBush_;
+  var nodes = rBush.getInExtent(box);
+  if (nodes.length > 0) {
+    nodes.sort(sortByDistance);
+    var node = nodes[0];
+    var closestSegment = node.segment;
+    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
+        closestSegment));
+    var vertexPixel = map.getPixelFromCoordinate(vertex);
+    if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
+        this.pixelTolerance_) {
+      var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
+      var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
+      var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
+      var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
+      var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
+      this.snappedToVertex_ = dist <= this.pixelTolerance_;
+      if (this.snappedToVertex_) {
+        vertex = squaredDist1 > squaredDist2 ?
+            closestSegment[1] : closestSegment[0];
       }
+      this.createOrUpdateVertexFeature_(vertex);
+      var vertexSegments = {};
+      vertexSegments[goog.getUid(closestSegment)] = true;
+      var segment;
+      for (var i = 1, ii = nodes.length; i < ii; ++i) {
+        segment = nodes[i].segment;
+        if ((ol.coordinate.equals(closestSegment[0], segment[0]) &&
+            ol.coordinate.equals(closestSegment[1], segment[1]) ||
+            (ol.coordinate.equals(closestSegment[0], segment[1]) &&
+            ol.coordinate.equals(closestSegment[1], segment[0])))) {
+          vertexSegments[goog.getUid(segment)] = true;
+        } else {
+          break;
+        }
+      }
+      this.vertexSegments_ = vertexSegments;
+      return;
     }
-  } else {
-    if (!(featureKey in this.undefIdIndex_)) {
-      removed = this.removeFromIdIndex_(feature);
-      goog.asserts.assert(removed,
-          'Expected feature to be removed from index');
-      this.undefIdIndex_[featureKey] = feature;
-    } else {
-      goog.asserts.assert(this.undefIdIndex_[featureKey] === feature);
-    }
   }
-  this.dispatchChangeEvent();
+  if (!goog.isNull(this.vertexFeature_)) {
+    this.overlay_.removeFeature(this.vertexFeature_);
+    this.vertexFeature_ = null;
+  }
 };
 
 
 /**
- * @return {boolean} Is empty.
+ * @param {ol.interaction.SegmentDataType} segmentData Segment data.
+ * @param {ol.Coordinate} vertex Vertex.
+ * @private
  */
-ol.source.Vector.prototype.isEmpty = function() {
-  return this.rBush_.isEmpty() &&
-      goog.object.isEmpty(this.nullGeometryFeatures_);
-};
+ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) {
+  var segment = segmentData.segment;
+  var feature = segmentData.feature;
+  var geometry = segmentData.geometry;
+  var depth = segmentData.depth;
+  var index = segmentData.index;
+  var coordinates;
 
+  while (vertex.length < geometry.getStride()) {
+    vertex.push(0);
+  }
 
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {ol.proj.Projection} projection Projection.
- */
-ol.source.Vector.prototype.loadFeatures = goog.nullFunction;
+  switch (geometry.getType()) {
+    case ol.geom.GeometryType.MULTI_LINE_STRING:
+      goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
+      coordinates = geometry.getCoordinates();
+      coordinates[depth[0]].splice(index + 1, 0, vertex);
+      break;
+    case ol.geom.GeometryType.POLYGON:
+      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
+      coordinates = geometry.getCoordinates();
+      coordinates[depth[0]].splice(index + 1, 0, vertex);
+      break;
+    case ol.geom.GeometryType.MULTI_POLYGON:
+      goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon);
+      coordinates = geometry.getCoordinates();
+      coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex);
+      break;
+    case ol.geom.GeometryType.LINE_STRING:
+      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
+      coordinates = geometry.getCoordinates();
+      coordinates.splice(index + 1, 0, vertex);
+      break;
+    default:
+      return;
+  }
 
+  geometry.setCoordinates(coordinates);
+  var rTree = this.rBush_;
+  goog.asserts.assert(goog.isDef(segment));
+  rTree.remove(segmentData);
+  goog.asserts.assert(goog.isDef(index));
+  this.updateSegmentIndices_(geometry, index, depth, 1);
+  var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+    segment: [segment[0], vertex],
+    feature: feature,
+    geometry: geometry,
+    depth: depth,
+    index: index
+  });
+  rTree.insert(ol.extent.boundingExtent(newSegmentData.segment),
+      newSegmentData);
+  this.dragSegments_.push([newSegmentData, 1]);
 
-/**
- * @param {ol.Feature} feature Feature.
- * @api stable
- */
-ol.source.Vector.prototype.removeFeature = function(feature) {
-  var featureKey = goog.getUid(feature).toString();
-  if (featureKey in this.nullGeometryFeatures_) {
-    delete this.nullGeometryFeatures_[featureKey];
-  } else {
-    this.rBush_.remove(feature);
-  }
-  this.removeFeatureInternal(feature);
-  this.dispatchChangeEvent();
+  var newSegmentData2 = /** @type {ol.interaction.SegmentDataType} */ ({
+    segment: [vertex, segment[1]],
+    feature: feature,
+    geometry: geometry,
+    depth: depth,
+    index: index + 1
+  });
+  rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment),
+      newSegmentData2);
+  this.dragSegments_.push([newSegmentData2, 0]);
 };
 
 
 /**
- * Remove feature without firing a `change` event.
- * @param {ol.Feature} feature Feature.
- * @protected
+ * Removes a vertex from all matching features.
+ * @return {boolean} True when a vertex was removed.
+ * @private
  */
-ol.source.Vector.prototype.removeFeatureInternal = function(feature) {
-  var featureKey = goog.getUid(feature).toString();
-  goog.asserts.assert(featureKey in this.featureChangeKeys_);
-  goog.array.forEach(this.featureChangeKeys_[featureKey],
-      goog.events.unlistenByKey);
-  delete this.featureChangeKeys_[featureKey];
-  var id = feature.getId();
-  if (goog.isDef(id)) {
-    delete this.idIndex_[id.toString()];
-  } else {
-    delete this.undefIdIndex_[featureKey];
+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, newSegment, right, segmentData, uid;
+  for (i = dragSegments.length - 1; i >= 0; --i) {
+    dragSegment = dragSegments[i];
+    segmentData = dragSegment[0];
+    geometry = segmentData.geometry;
+    coordinates = geometry.getCoordinates();
+    uid = goog.getUid(segmentData.feature);
+    left = right = index = undefined;
+    if (dragSegment[1] === 0) {
+      right = segmentData;
+      index = segmentData.index;
+    } else if (dragSegment[1] == 1) {
+      left = segmentData;
+      index = segmentData.index + 1;
+    }
+    if (!(uid in segmentsByFeature)) {
+      segmentsByFeature[uid] = [left, right, index];
+    }
+    newSegment = segmentsByFeature[uid];
+    if (goog.isDef(left)) {
+      newSegment[0] = left;
+    }
+    if (goog.isDef(right)) {
+      newSegment[1] = right;
+    }
+    if (goog.isDef(newSegment[0]) && goog.isDef(newSegment[1])) {
+      component = coordinates;
+      deleted = false;
+      newIndex = index - 1;
+      switch (geometry.getType()) {
+        case ol.geom.GeometryType.MULTI_LINE_STRING:
+          coordinates[segmentData.depth[0]].splice(index, 1);
+          deleted = true;
+          break;
+        case ol.geom.GeometryType.LINE_STRING:
+          coordinates.splice(index, 1);
+          deleted = true;
+          break;
+        case ol.geom.GeometryType.MULTI_POLYGON:
+          component = component[segmentData.depth[1]];
+          /* falls through */
+        case ol.geom.GeometryType.POLYGON:
+          component = component[segmentData.depth[0]];
+          if (component.length > 4) {
+            if (index == component.length - 1) {
+              index = 0;
+            }
+            component.splice(index, 1);
+            deleted = true;
+            if (index === 0) {
+              // close the ring again
+              component.pop();
+              component.push(component[0]);
+              newIndex = component.length - 1;
+            }
+          }
+          break;
+      }
+
+      if (deleted) {
+        this.rBush_.remove(newSegment[0]);
+        this.rBush_.remove(newSegment[1]);
+        geometry.setCoordinates(coordinates);
+        goog.asserts.assert(newIndex >= 0);
+        var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({
+          depth: segmentData.depth,
+          feature: segmentData.feature,
+          geometry: segmentData.geometry,
+          index: newIndex,
+          segment: [newSegment[0].segment[0], newSegment[1].segment[1]]
+        });
+        this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment),
+            newSegmentData);
+        this.updateSegmentIndices_(geometry, index, segmentData.depth, -1);
+
+        this.overlay_.removeFeature(this.vertexFeature_);
+        this.vertexFeature_ = null;
+      }
+    }
   }
-  this.dispatchEvent(new ol.source.VectorEvent(
-      ol.source.VectorEventType.REMOVEFEATURE, feature));
+  return deleted;
 };
 
 
 /**
- * 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.
+ * @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.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;
+ol.interaction.Modify.prototype.updateSegmentIndices_ = function(
+    geometry, index, depth, delta) {
+  this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) {
+    if (segmentDataMatch.geometry === geometry &&
+        (!goog.isDef(depth) ||
+        goog.array.equals(segmentDataMatch.depth, depth)) &&
+        segmentDataMatch.index > index) {
+      segmentDataMatch.index += delta;
     }
-  }
-  return removed;
+  });
 };
 
 
-
 /**
- * @classdesc
- * Events emitted by {@link ol.source.Vector} instances are instances of this
- * type.
- *
- * @constructor
- * @extends {goog.events.Event}
- * @implements {oli.source.VectorEvent}
- * @param {string} type Type.
- * @param {ol.Feature=} opt_feature Feature.
+ * @return {ol.style.StyleFunction} Styles.
  */
-ol.source.VectorEvent = function(type, opt_feature) {
-
-  goog.base(this, type);
-
-  /**
-   * The feature being added or removed.
-   * @type {ol.Feature|undefined}
-   * @api stable
-   */
-  this.feature = opt_feature;
-
+ol.interaction.Modify.getDefaultStyleFunction = function() {
+  var style = ol.style.createDefaultEditingStyles();
+  return function(feature, resolution) {
+    return style[ol.geom.GeometryType.POINT];
+  };
 };
-goog.inherits(ol.source.VectorEvent, goog.events.Event);
 
-goog.provide('ol.DrawEvent');
-goog.provide('ol.interaction.Draw');
+goog.provide('ol.interaction.Select');
 
+goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.events.Event');
-goog.require('ol.Collection');
-goog.require('ol.Coordinate');
+goog.require('goog.events');
+goog.require('goog.functions');
+goog.require('ol.CollectionEventType');
 goog.require('ol.Feature');
 goog.require('ol.FeatureOverlay');
-goog.require('ol.Map');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEvent.EventType');
 goog.require('ol.events.condition');
 goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.source.Vector');
+goog.require('ol.interaction.Interaction');
 goog.require('ol.style.Style');
 
 
-/**
- * @enum {string}
- */
-ol.DrawEventType = {
-  /**
-   * Triggered upon feature draw start
-   * @event ol.DrawEvent#drawstart
-   * @api stable
-   */
-  DRAWSTART: 'drawstart',
-  /**
-   * Triggered upon feature draw end
-   * @event ol.DrawEvent#drawend
-   * @api stable
-   */
-  DRAWEND: 'drawend'
-};
-
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.Draw} instances are instances of
- * this type.
- *
- * @constructor
- * @extends {goog.events.Event}
- * @implements {oli.DrawEvent}
- * @param {ol.DrawEventType} type Type.
- * @param {ol.Feature} feature The feature drawn.
- */
-ol.DrawEvent = function(type, feature) {
-
-  goog.base(this, type);
-
-  /**
-   * The feature being drawn.
-   * @type {ol.Feature}
-   * @api stable
-   */
-  this.feature = feature;
-
-};
-goog.inherits(ol.DrawEvent, goog.events.Event);
-
-
 
 /**
  * @classdesc
- * Interaction that allows drawing geometries.
+ * Handles selection of vector data. A {@link ol.FeatureOverlay} is maintained
+ * internally to store the selected feature(s). Which features are selected is
+ * determined by the `condition` option, and optionally the `toggle` or
+ * `add`/`remove` options.
  *
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.DrawEvent
- * @param {olx.interaction.DrawOptions} options Options.
+ * @extends {ol.interaction.Interaction}
+ * @param {olx.interaction.SelectOptions=} opt_options Options.
  * @api stable
  */
-ol.interaction.Draw = function(options) {
-
-  goog.base(this);
+ol.interaction.Select = function(opt_options) {
 
-  /**
-   * @type {ol.Pixel}
-   * @private
-   */
-  this.downPx_ = null;
+  goog.base(this, {
+    handleEvent: ol.interaction.Select.handleEvent
+  });
 
-  /**
-   * Target source for drawn features.
-   * @type {ol.source.Vector}
-   * @private
-   */
-  this.source_ = goog.isDef(options.source) ? options.source : null;
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
   /**
-   * Target collection for drawn features.
-   * @type {ol.Collection.<ol.Feature>}
    * @private
+   * @type {ol.events.ConditionType}
    */
-  this.features_ = goog.isDef(options.features) ? options.features : null;
+  this.condition_ = goog.isDef(options.condition) ?
+      options.condition : ol.events.condition.singleClick;
 
   /**
-   * Pixel distance for snapping.
-   * @type {number}
    * @private
+   * @type {ol.events.ConditionType}
    */
-  this.snapTolerance_ = goog.isDef(options.snapTolerance) ?
-      options.snapTolerance : 12;
+  this.addCondition_ = goog.isDef(options.addCondition) ?
+      options.addCondition : ol.events.condition.never;
 
   /**
-   * The number of points that must be drawn before a polygon ring can be
-   * finished.  The default is 3.
-   * @type {number}
    * @private
+   * @type {ol.events.ConditionType}
    */
-  this.minPointsPerRing_ = goog.isDef(options.minPointsPerRing) ?
-      options.minPointsPerRing : 3;
+  this.removeCondition_ = goog.isDef(options.removeCondition) ?
+      options.removeCondition : ol.events.condition.never;
 
   /**
-   * Geometry type.
-   * @type {ol.geom.GeometryType}
    * @private
+   * @type {ol.events.ConditionType}
    */
-  this.type_ = options.type;
+  this.toggleCondition_ = goog.isDef(options.toggleCondition) ?
+      options.toggleCondition : ol.events.condition.shiftKeyOnly;
 
-  /**
-   * Drawing mode (derived from geometry type.
-   * @type {ol.interaction.DrawMode}
-   * @private
-   */
-  this.mode_ = ol.interaction.Draw.getMode_(this.type_);
+  var layerFilter;
+  if (goog.isDef(options.layers)) {
+    if (goog.isFunction(options.layers)) {
+      layerFilter = options.layers;
+    } else {
+      var layers = options.layers;
+      layerFilter =
+          /**
+           * @param {ol.layer.Layer} layer Layer.
+           * @return {boolean} Include.
+           */
+          function(layer) {
+        return goog.array.contains(layers, layer);
+      };
+    }
+  } else {
+    layerFilter = goog.functions.TRUE;
+  }
 
   /**
-   * Finish coordinate for the feature (first point for polygons, last point for
-   * linestrings).
-   * @type {ol.Coordinate}
    * @private
+   * @type {function(ol.layer.Layer): boolean}
    */
-  this.finishCoordinate_ = null;
+  this.layerFilter_ = layerFilter;
 
   /**
-   * Sketch feature.
-   * @type {ol.Feature}
    * @private
+   * @type {ol.FeatureOverlay}
    */
-  this.sketchFeature_ = null;
+  this.featureOverlay_ = new ol.FeatureOverlay({
+    style: goog.isDef(options.style) ? options.style :
+        ol.interaction.Select.getDefaultStyleFunction()
+  });
 
-  /**
-   * Sketch point.
-   * @type {ol.Feature}
-   * @private
-   */
-  this.sketchPoint_ = null;
+  var features = this.featureOverlay_.getFeatures();
+  goog.events.listen(features, ol.CollectionEventType.ADD,
+      this.addFeature_, false, this);
+  goog.events.listen(features, ol.CollectionEventType.REMOVE,
+      this.removeFeature_, false, this);
 
-  /**
-   * Sketch line. Used when drawing polygon.
-   * @type {ol.Feature}
-   * @private
-   */
-  this.sketchLine_ = null;
+};
+goog.inherits(ol.interaction.Select, ol.interaction.Interaction);
 
-  /**
-   * Sketch polygon. Used when drawing polygon.
-   * @type {Array.<Array.<ol.Coordinate>>}
-   * @private
-   */
-  this.sketchPolygonCoords_ = 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_ = 4;
+/**
+ * Get the selected features.
+ * @return {ol.Collection.<ol.Feature>} Features collection.
+ * @api stable
+ */
+ol.interaction.Select.prototype.getFeatures = function() {
+  return this.featureOverlay_.getFeatures();
+};
 
-  /**
-   * Draw overlay where our sketch features are drawn.
-   * @type {ol.FeatureOverlay}
-   * @private
-   */
-  this.overlay_ = new ol.FeatureOverlay({
-    style: goog.isDef(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;
+/**
+ * @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_.getFeatures();
+  if (set) {
+    // Replace the currently selected feature(s) with the feature at the pixel,
+    // or clear the selected feature(s) if there is no feature at the pixel.
+    /** @type {ol.Feature|undefined} */
+    var feature = map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @param {ol.layer.Layer} layer Layer.
+         */
+        function(feature, layer) {
+          return feature;
+        }, undefined, this.layerFilter_);
+    if (goog.isDef(feature) &&
+        features.getLength() == 1 &&
+        features.item(0) == feature) {
+      // No change
+    } else {
+      if (features.getLength() !== 0) {
+        features.clear();
+      }
+      if (goog.isDef(feature)) {
+        features.push(feature);
+      }
+    }
+  } else {
+    // Modify the currently selected feature(s).
+    var /** @type {Array.<ol.Feature>} */ deselected = [];
+    var /** @type {Array.<ol.Feature>} */ selected = [];
+    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @param {ol.layer.Layer} layer Layer.
+         */
+        function(feature, layer) {
+          var index = goog.array.indexOf(features.getArray(), feature);
+          if (index == -1) {
+            if (add || toggle) {
+              selected.push(feature);
+            }
+          } else {
+            if (remove || toggle) {
+              deselected.push(feature);
+            }
+          }
+        }, undefined, this.layerFilter_);
+    var i;
+    for (i = deselected.length - 1; i >= 0; --i) {
+      features.remove(deselected[i]);
+    }
+    features.extend(selected);
+  }
+  return ol.events.condition.mouseMove(mapBrowserEvent);
+};
 
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.condition_ = goog.isDef(options.condition) ?
-      options.condition : ol.events.condition.noModifierKeys;
 
+/**
+ * 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_.getFeatures();
+  if (!goog.isNull(currentMap)) {
+    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
+  }
+  goog.base(this, 'setMap', map);
+  this.featureOverlay_.setMap(map);
+  if (!goog.isNull(map)) {
+    selectedFeatures.forEach(map.skipFeature, map);
+  }
 };
-goog.inherits(ol.interaction.Draw, ol.interaction.Pointer);
 
 
 /**
  * @return {ol.style.StyleFunction} Styles.
  */
-ol.interaction.Draw.getDefaultStyleFunction = function() {
+ol.interaction.Select.getDefaultStyleFunction = function() {
   var styles = ol.style.createDefaultEditingStyles();
+  goog.array.extend(styles[ol.geom.GeometryType.POLYGON],
+      styles[ol.geom.GeometryType.LINE_STRING]);
+  goog.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION],
+      styles[ol.geom.GeometryType.LINE_STRING]);
+
   return function(feature, resolution) {
     return styles[feature.getGeometry().getType()];
   };
@@ -89705,2154 +103177,2262 @@ ol.interaction.Draw.getDefaultStyleFunction = function() {
 
 
 /**
- * @inheritDoc
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
  */
-ol.interaction.Draw.prototype.setMap = function(map) {
-  if (goog.isNull(map)) {
-    // removing from a map, clean up
-    this.abortDrawing_();
+ol.interaction.Select.prototype.addFeature_ = function(evt) {
+  var feature = evt.element;
+  var map = this.getMap();
+  goog.asserts.assertInstanceof(feature, ol.Feature);
+  if (!goog.isNull(map)) {
+    map.skipFeature(feature);
   }
-  this.overlay_.setMap(map);
-  goog.base(this, 'setMap', map);
 };
 
 
 /**
- * @inheritDoc
+ * @param {ol.CollectionEvent} evt Event.
+ * @private
  */
-ol.interaction.Draw.prototype.handleMapBrowserEvent = function(event) {
-  var map = event.map;
-  if (!map.isDef()) {
-    return true;
-  }
-  var pass = true;
-  if (event.type === ol.MapBrowserEvent.EventType.POINTERMOVE) {
-    pass = this.handlePointerMove_(event);
-  } else if (event.type === ol.MapBrowserEvent.EventType.DBLCLICK) {
-    pass = false;
+ol.interaction.Select.prototype.removeFeature_ = function(evt) {
+  var feature = evt.element;
+  var map = this.getMap();
+  goog.asserts.assertInstanceof(feature, ol.Feature);
+  if (!goog.isNull(map)) {
+    map.unskipFeature(feature);
   }
-  return (goog.base(this, 'handleMapBrowserEvent', event) && pass);
 };
 
+goog.provide('ol.layer.Heatmap');
+
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.math');
+goog.require('ol.Object');
+goog.require('ol.dom');
+goog.require('ol.layer.Vector');
+goog.require('ol.render.EventType');
+goog.require('ol.style.Icon');
+goog.require('ol.style.Style');
+
 
 /**
- * Handle down events.
- * @param {ol.MapBrowserEvent} event A down event.
- * @return {boolean} Pass the event to other interactions.
+ * @enum {string}
  */
-ol.interaction.Draw.prototype.handlePointerDown = function(event) {
-  if (this.condition_(event)) {
-    this.downPx_ = event.pixel;
-    return true;
-  } else {
-    return false;
-  }
+ol.layer.HeatmapLayerProperty = {
+  GRADIENT: 'gradient'
 };
 
 
+
 /**
- * Handle up events.
- * @param {ol.MapBrowserEvent} event An up event.
- * @return {boolean} Pass the event to other interactions.
+ * @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.interaction.Draw.prototype.handlePointerUp = 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;
-  if (squaredDistance <= this.squaredClickTolerance_) {
-    this.handlePointerMove_(event);
-    if (goog.isNull(this.finishCoordinate_)) {
-      this.startDrawing_(event);
-    } else if (this.mode_ === ol.interaction.DrawMode.POINT ||
-        this.atFinish_(event)) {
-      this.finishDrawing_(event);
-    } else {
-      this.addToDrawing_(event);
-    }
-    pass = false;
+ol.layer.Heatmap = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this, /** @type {olx.layer.VectorOptions} */ (options));
+
+  /**
+   * @private
+   * @type {Uint8ClampedArray}
+   */
+  this.gradient_ = null;
+
+  goog.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.GRADIENT),
+      this.handleGradientChanged_, false, this);
+
+  this.setGradient(goog.isDef(options.gradient) ?
+      options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
+
+  var circle = ol.layer.Heatmap.createCircle_(
+      goog.isDef(options.radius) ? options.radius : 8,
+      goog.isDef(options.blur) ? options.blur : 15,
+      goog.isDef(options.shadow) ? options.shadow : 250);
+
+  /**
+   * @type {Array.<Array.<ol.style.Style>>}
+   */
+  var styleCache = new Array(256);
+
+  var weight = goog.isDef(options.weight) ? options.weight : 'weight';
+  var weightFunction;
+  if (goog.isString(weight)) {
+    weightFunction = function(feature) {
+      return feature.get(weight);
+    };
+  } else {
+    weightFunction = weight;
   }
-  return pass;
+  goog.asserts.assert(goog.isFunction(weightFunction));
+
+  this.setStyle(function(feature, resolution) {
+    var weight = weightFunction(feature);
+    var opacity = goog.isDef(weight) ? goog.math.clamp(weight, 0, 1) : 1;
+    // cast to 8 bits
+    var index = (255 * opacity) | 0;
+    var style = styleCache[index];
+    if (!goog.isDef(style)) {
+      style = [
+        new ol.style.Style({
+          image: new ol.style.Icon({
+            opacity: opacity,
+            src: circle
+          })
+        })
+      ];
+      styleCache[index] = style;
+    }
+    return style;
+  });
+
+  // For performance reasons, don't sort the features before rendering.
+  // The render order is not relevant for a heatmap representation.
+  this.setRenderOrder(null);
+
+  goog.events.listen(this, ol.render.EventType.RENDER,
+      this.handleRender_, false, this);
+
 };
+goog.inherits(ol.layer.Heatmap, ol.layer.Vector);
 
 
 /**
- * Handle move events.
- * @param {ol.MapBrowserEvent} event A move event.
- * @return {boolean} Pass the event to other interactions.
- * @private
+ * @const
+ * @type {Array.<string>}
  */
-ol.interaction.Draw.prototype.handlePointerMove_ = function(event) {
-  if (this.mode_ === ol.interaction.DrawMode.POINT &&
-      goog.isNull(this.finishCoordinate_)) {
-    this.startDrawing_(event);
-  } else if (!goog.isNull(this.finishCoordinate_)) {
-    this.modifyDrawing_(event);
-  } else {
-    this.createOrUpdateSketchPoint_(event);
-  }
-  return true;
-};
+ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
 
 
 /**
- * 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.
+ * @param {Array.<string>} colors
+ * @return {Uint8ClampedArray}
  * @private
  */
-ol.interaction.Draw.prototype.atFinish_ = function(event) {
-  var at = false;
-  if (!goog.isNull(this.sketchFeature_)) {
-    var geometry = this.sketchFeature_.getGeometry();
-    var potentiallyDone = false;
-    var potentiallyFinishCoordinates = [this.finishCoordinate_];
-    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
-      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-      potentiallyDone = geometry.getCoordinates().length > 2;
-    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
-      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
-      potentiallyDone = geometry.getCoordinates()[0].length >
-          this.minPointsPerRing_;
-      potentiallyFinishCoordinates = [this.sketchPolygonCoords_[0][0],
-        this.sketchPolygonCoords_[0][this.sketchPolygonCoords_[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];
-        at = Math.sqrt(dx * dx + dy * dy) <= this.snapTolerance_;
-        if (at) {
-          this.finishCoordinate_ = finishCoordinate;
-          break;
-        }
-      }
-    }
+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]);
   }
-  return at;
+
+  context.fillStyle = gradient;
+  context.fillRect(0, 0, width, height);
+
+  return context.getImageData(0, 0, width, height).data;
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} event Event.
+ * @param {number} radius Radius size in pixel.
+ * @param {number} blur Blur size in pixel.
+ * @param {number} shadow Shadow offset size in pixel.
+ * @return {string}
  * @private
  */
-ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) {
-  var coordinates = event.coordinate.slice();
-  if (goog.isNull(this.sketchPoint_)) {
-    this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates));
-    this.updateSketchFeatures_();
-  } else {
-    var sketchPointGeom = this.sketchPoint_.getGeometry();
-    goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point);
-    sketchPointGeom.setCoordinates(coordinates);
-  }
+ol.layer.Heatmap.createCircle_ = function(radius, blur, shadow) {
+  var halfSize = radius + blur + 1;
+  var size = 2 * halfSize;
+  var context = ol.dom.createCanvasContext2D(size, size);
+  context.shadowOffsetX = context.shadowOffsetY = shadow;
+  context.shadowBlur = blur;
+  context.shadowColor = '#000';
+  context.beginPath();
+  var center = halfSize - shadow;
+  context.arc(center, center, radius, 0, Math.PI * 2, true);
+  context.fill();
+  return context.canvas.toDataURL();
 };
 
 
 /**
- * Start the drawing.
- * @param {ol.MapBrowserEvent} event Event.
- * @private
+ * @return {Array.<string>} Colors.
+ * @api
+ * @observable
  */
-ol.interaction.Draw.prototype.startDrawing_ = function(event) {
-  var start = event.coordinate;
-  this.finishCoordinate_ = start;
-  var geometry;
-  if (this.mode_ === ol.interaction.DrawMode.POINT) {
-    geometry = new ol.geom.Point(start.slice());
-  } else {
-    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
-      geometry = new ol.geom.LineString([start.slice(), start.slice()]);
-    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
-      this.sketchLine_ = new ol.Feature(new ol.geom.LineString([start.slice(),
-            start.slice()]));
-      this.sketchPolygonCoords_ = [[start.slice(), start.slice()]];
-      geometry = new ol.geom.Polygon(this.sketchPolygonCoords_);
-    }
-  }
-  goog.asserts.assert(goog.isDef(geometry));
-  this.sketchFeature_ = new ol.Feature();
-  if (goog.isDef(this.geometryName_)) {
-    this.sketchFeature_.setGeometryName(this.geometryName_);
-  }
-  this.sketchFeature_.setGeometry(geometry);
-  this.updateSketchFeatures_();
-  this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWSTART,
-      this.sketchFeature_));
+ol.layer.Heatmap.prototype.getGradient = function() {
+  return /** @type {Array.<string>} */ (
+      this.get(ol.layer.HeatmapLayerProperty.GRADIENT));
 };
+goog.exportProperty(
+    ol.layer.Heatmap.prototype,
+    'getGradient',
+    ol.layer.Heatmap.prototype.getGradient);
 
 
 /**
- * Modify the drawing.
- * @param {ol.MapBrowserEvent} event Event.
  * @private
  */
-ol.interaction.Draw.prototype.modifyDrawing_ = function(event) {
-  var coordinate = event.coordinate;
-  var geometry = this.sketchFeature_.getGeometry();
-  var coordinates, last;
-  if (this.mode_ === ol.interaction.DrawMode.POINT) {
-    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
-    last = geometry.getCoordinates();
-    last[0] = coordinate[0];
-    last[1] = coordinate[1];
-    geometry.setCoordinates(last);
-  } else {
-    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
-      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-      coordinates = geometry.getCoordinates();
-    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
-      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
-      coordinates = this.sketchPolygonCoords_[0];
-    }
-    if (this.atFinish_(event)) {
-      // snap to finish
-      coordinate = this.finishCoordinate_.slice();
-    }
-    var sketchPointGeom = this.sketchPoint_.getGeometry();
-    goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point);
-    sketchPointGeom.setCoordinates(coordinate);
-    last = coordinates[coordinates.length - 1];
-    last[0] = coordinate[0];
-    last[1] = coordinate[1];
-    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
-      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-      geometry.setCoordinates(coordinates);
-    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
-      var sketchLineGeom = this.sketchLine_.getGeometry();
-      goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString);
-      sketchLineGeom.setCoordinates(coordinates);
-      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
-      geometry.setCoordinates(this.sketchPolygonCoords_);
-    }
-  }
-  this.updateSketchFeatures_();
+ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
+  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
 };
 
 
 /**
- * Add a new coordinate to the drawing.
- * @param {ol.MapBrowserEvent} event Event.
+ * @param {ol.render.Event} event Post compose event
  * @private
  */
-ol.interaction.Draw.prototype.addToDrawing_ = function(event) {
-  var coordinate = event.coordinate;
-  var geometry = this.sketchFeature_.getGeometry();
-  var coordinates;
-  if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
-    this.finishCoordinate_ = coordinate.slice();
-    goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-    coordinates = geometry.getCoordinates();
-    coordinates.push(coordinate.slice());
-    geometry.setCoordinates(coordinates);
-  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
-    this.sketchPolygonCoords_[0].push(coordinate.slice());
-    goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
-    geometry.setCoordinates(this.sketchPolygonCoords_);
+ol.layer.Heatmap.prototype.handleRender_ = function(event) {
+  goog.asserts.assert(event.type == ol.render.EventType.RENDER);
+  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, offset;
+  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];
+    }
   }
-  this.updateSketchFeatures_();
+  context.putImageData(image, 0, 0);
 };
 
 
 /**
- * Stop drawing and add the sketch feature to the target layer.
- * @param {ol.MapBrowserEvent} event Event.
- * @private
+ * @param {Array.<string>} colors Gradient.
+ * @api
+ * @observable
  */
-ol.interaction.Draw.prototype.finishDrawing_ = function(event) {
-  var sketchFeature = this.abortDrawing_();
-  goog.asserts.assert(!goog.isNull(sketchFeature));
-  var coordinates;
-  var geometry = sketchFeature.getGeometry();
-  if (this.mode_ === ol.interaction.DrawMode.POINT) {
-    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
-    coordinates = geometry.getCoordinates();
-  } else if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
-    goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-    coordinates = geometry.getCoordinates();
-    // remove the redundant last point
-    coordinates.pop();
-    geometry.setCoordinates(coordinates);
-  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
-    goog.asserts.assertInstanceof(geometry, ol.geom.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
-    this.sketchPolygonCoords_[0].pop();
-    this.sketchPolygonCoords_[0].push(this.sketchPolygonCoords_[0][0]);
-    geometry.setCoordinates(this.sketchPolygonCoords_);
-    coordinates = geometry.getCoordinates();
-  }
+ol.layer.Heatmap.prototype.setGradient = function(colors) {
+  this.set(ol.layer.HeatmapLayerProperty.GRADIENT, colors);
+};
+goog.exportProperty(
+    ol.layer.Heatmap.prototype,
+    'setGradient',
+    ol.layer.Heatmap.prototype.setGradient);
 
-  // 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]));
-  }
+goog.provide('ol.loadingstrategy');
 
-  if (!goog.isNull(this.features_)) {
-    this.features_.push(sketchFeature);
-  }
-  if (!goog.isNull(this.source_)) {
-    this.source_.addFeature(sketchFeature);
-  }
-  this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWEND, sketchFeature));
-};
+goog.require('ol.TileCoord');
 
 
 /**
- * Stop drawing without adding the sketch feature to the target layer.
- * @return {ol.Feature} The sketch feature (or null if none).
- * @private
+ * 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.interaction.Draw.prototype.abortDrawing_ = function() {
-  this.finishCoordinate_ = null;
-  var sketchFeature = this.sketchFeature_;
-  if (!goog.isNull(sketchFeature)) {
-    this.sketchFeature_ = null;
-    this.sketchPoint_ = null;
-    this.sketchLine_ = null;
-    this.overlay_.getFeatures().clear();
-  }
-  return sketchFeature;
+ol.loadingstrategy.all = function(extent, resolution) {
+  return [[-Infinity, -Infinity, Infinity, Infinity]];
 };
 
 
 /**
- * Redraw the skecth features.
- * @private
+ * 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.interaction.Draw.prototype.updateSketchFeatures_ = function() {
-  var sketchFeatures = [];
-  if (!goog.isNull(this.sketchFeature_)) {
-    sketchFeatures.push(this.sketchFeature_);
-  }
-  if (!goog.isNull(this.sketchLine_)) {
-    sketchFeatures.push(this.sketchLine_);
-  }
-  if (!goog.isNull(this.sketchPoint_)) {
-    sketchFeatures.push(this.sketchPoint_);
-  }
-  this.overlay_.setFeatures(new ol.Collection(sketchFeatures));
+ol.loadingstrategy.bbox = function(extent, resolution) {
+  return [extent];
 };
 
 
 /**
- * Get the drawing mode.  The mode for mult-part geometries is the same as for
- * their single-part cousins.
- * @param {ol.geom.GeometryType} type Geometry type.
- * @return {ol.interaction.DrawMode} Drawing mode.
- * @private
+ * 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.interaction.Draw.getMode_ = function(type) {
-  var mode;
-  if (type === ol.geom.GeometryType.POINT ||
-      type === ol.geom.GeometryType.MULTI_POINT) {
-    mode = ol.interaction.DrawMode.POINT;
-  } else if (type === ol.geom.GeometryType.LINE_STRING ||
-      type === ol.geom.GeometryType.MULTI_LINE_STRING) {
-    mode = ol.interaction.DrawMode.LINE_STRING;
-  } else if (type === ol.geom.GeometryType.POLYGON ||
-      type === ol.geom.GeometryType.MULTI_POLYGON) {
-    mode = ol.interaction.DrawMode.POLYGON;
-  }
-  goog.asserts.assert(goog.isDef(mode));
-  return mode;
+ol.loadingstrategy.createTile = 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;
+      });
 };
 
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Draw mode.  This collapses multi-part geometry types with their single-part
- * cousins.
- * @enum {string}
+ * @fileoverview A utility to load JavaScript files via DOM script tags.
+ * Refactored from goog.net.Jsonp. Works cross-domain.
+ *
  */
-ol.interaction.DrawMode = {
-  POINT: 'Point',
-  LINE_STRING: 'LineString',
-  POLYGON: 'Polygon'
-};
 
-goog.provide('ol.interaction.Modify');
+goog.provide('goog.net.jsloader');
+goog.provide('goog.net.jsloader.Error');
+goog.provide('goog.net.jsloader.ErrorCode');
+goog.provide('goog.net.jsloader.Options');
 
 goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.functions');
-goog.require('ol.Collection');
-goog.require('ol.CollectionEventType');
-goog.require('ol.Feature');
-goog.require('ol.FeatureOverlay');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.ViewHint');
-goog.require('ol.coordinate');
-goog.require('ol.events.condition');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.structs.RBush');
-goog.require('ol.style.Style');
+goog.require('goog.async.Deferred');
+goog.require('goog.debug.Error');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
 
 
 /**
- * @typedef {{depth: (Array.<number>|undefined),
- *            feature: ol.Feature,
- *            geometry: ol.geom.SimpleGeometry,
- *            index: (number|undefined),
- *            segment: Array.<ol.Extent>}}
+ * The name of the property of goog.global under which the JavaScript
+ * verification object is stored by the loaded script.
+ * @type {string}
+ * @private
  */
-ol.interaction.SegmentDataType;
+goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
+
 
+/**
+ * The default length of time, in milliseconds, we are prepared to wait for a
+ * load request to complete.
+ * @type {number}
+ */
+goog.net.jsloader.DEFAULT_TIMEOUT = 5000;
 
 
 /**
- * @classdesc
- * Interaction for modifying vector data.
+ * Optional parameters for goog.net.jsloader.send.
+ * timeout: The length of time, in milliseconds, we are prepared to wait
+ *     for a load request to complete. Default it 5 seconds.
+ * document: The HTML document under which to load the JavaScript. Default is
+ *     the current document.
+ * cleanupWhenDone: If true clean up the script tag after script completes to
+ *     load. This is important if you just want to read data from the JavaScript
+ *     and then throw it away. Default is false.
  *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.ModifyOptions} options Options.
- * @api stable
+ * @typedef {{
+ *   timeout: (number|undefined),
+ *   document: (HTMLDocument|undefined),
+ *   cleanupWhenDone: (boolean|undefined)
+ * }}
  */
-ol.interaction.Modify = function(options) {
-
-  goog.base(this);
+goog.net.jsloader.Options;
 
 
-  /**
-   * @type {ol.events.ConditionType}
-   * @private
-   */
-  this.deleteCondition_ = goog.isDef(options.deleteCondition) ?
-      options.deleteCondition :
-      /** @type {ol.events.ConditionType} */ (goog.functions.and(
-          ol.events.condition.noModifierKeys,
-          ol.events.condition.singleClick));
+/**
+ * Scripts (URIs) waiting to be loaded.
+ * @type {Array<string>}
+ * @private
+ */
+goog.net.jsloader.scriptsToLoad_ = [];
 
-  /**
-   * 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;
+/**
+ * Loads and evaluates the JavaScript files at the specified URIs, guaranteeing
+ * the order of script loads.
+ *
+ * Because we have to load the scripts in serial (load script 1, exec script 1,
+ * load script 2, exec script 2, and so on), this will be slower than doing
+ * the network fetches in parallel.
+ *
+ * If you need to load a large number of scripts but dependency order doesn't
+ * matter, you should just call goog.net.jsloader.load N times.
+ *
+ * If you need to load a large number of scripts on the same domain,
+ * you may want to use goog.module.ModuleLoader.
+ *
+ * @param {Array<string>} uris The URIs to load.
+ * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
+ *     goog.net.jsloader.options documentation for details.
+ */
+goog.net.jsloader.loadMany = function(uris, opt_options) {
+  // Loading the scripts in serial introduces asynchronosity into the flow.
+  // Therefore, there are race conditions where client A can kick off the load
+  // sequence for client B, even though client A's scripts haven't all been
+  // loaded yet.
+  //
+  // To work around this issue, all module loads share a queue.
+  if (!uris.length) {
+    return;
+  }
 
-  /**
-   * @type {ol.Pixel}
-   * @private
-   */
-  this.lastPixel_ = [0, 0];
+  var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length;
+  goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris);
+  if (isAnotherModuleLoading) {
+    // jsloader is still loading some other scripts.
+    // In order to prevent the race condition noted above, we just add
+    // these URIs to the end of the scripts' queue and return.
+    return;
+  }
 
-  /**
-   * Segment RTree for each layer
-   * @type {Object.<*, ol.structs.RBush>}
-   * @private
-   */
-  this.rBush_ = new ol.structs.RBush();
+  uris = goog.net.jsloader.scriptsToLoad_;
+  var popAndLoadNextScript = function() {
+    var uri = uris.shift();
+    var deferred = goog.net.jsloader.load(uri, opt_options);
+    if (uris.length) {
+      deferred.addBoth(popAndLoadNextScript);
+    }
+  };
+  popAndLoadNextScript();
+};
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ?
-      options.pixelTolerance : 10;
 
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.snappedToVertex_ = false;
+/**
+ * Loads and evaluates a JavaScript file.
+ * When the script loads, a user callback is called.
+ * It is the client's responsibility to verify that the script ran successfully.
+ *
+ * @param {string} uri The URI of the JavaScript.
+ * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
+ *     goog.net.jsloader.Options documentation for details.
+ * @return {!goog.async.Deferred} The deferred result, that may be used to add
+ *     callbacks and/or cancel the transmission.
+ *     The error callback will be called with a single goog.net.jsloader.Error
+ *     parameter.
+ */
+goog.net.jsloader.load = function(uri, opt_options) {
+  var options = opt_options || {};
+  var doc = options.document || document;
 
-  /**
-   * @type {Array}
-   * @private
-   */
-  this.dragSegments_ = null;
+  var script = goog.dom.createElement(goog.dom.TagName.SCRIPT);
+  var request = {script_: script, timeout_: undefined};
+  var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request);
 
-  /**
-   * Draw overlay where are sketch features are drawn.
-   * @type {ol.FeatureOverlay}
-   * @private
-   */
-  this.overlay_ = new ol.FeatureOverlay({
-    style: (goog.isDef(options.style)) ? options.style :
-        ol.interaction.Modify.getDefaultStyleFunction()
-  });
+  // Set a timeout.
+  var timeout = null;
+  var timeoutDuration = goog.isDefAndNotNull(options.timeout) ?
+      options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT;
+  if (timeoutDuration > 0) {
+    timeout = window.setTimeout(function() {
+      goog.net.jsloader.cleanup_(script, true);
+      deferred.errback(new goog.net.jsloader.Error(
+          goog.net.jsloader.ErrorCode.TIMEOUT,
+          'Timeout reached for loading script ' + uri));
+    }, timeoutDuration);
+    request.timeout_ = timeout;
+  }
 
-  /**
-  * @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_
+  // Hang the user callback to be called when the script completes to load.
+  // NOTE(user): This callback will be called in IE even upon error. In any
+  // case it is the client's responsibility to verify that the script ran
+  // successfully.
+  script.onload = script.onreadystatechange = function() {
+    if (!script.readyState || script.readyState == 'loaded' ||
+        script.readyState == 'complete') {
+      var removeScriptNode = options.cleanupWhenDone || false;
+      goog.net.jsloader.cleanup_(script, removeScriptNode, timeout);
+      deferred.callback(null);
+    }
   };
 
-  /**
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
-   */
-  this.features_ = options.features;
+  // Add an error callback.
+  // NOTE(user): Not supported in IE.
+  script.onerror = function() {
+    goog.net.jsloader.cleanup_(script, true, timeout);
+    deferred.errback(new goog.net.jsloader.Error(
+        goog.net.jsloader.ErrorCode.LOAD_ERROR,
+        'Error while loading script ' + uri));
+  };
 
-  this.features_.forEach(this.addFeature_, this);
-  goog.events.listen(this.features_, ol.CollectionEventType.ADD,
-      this.handleFeatureAdd_, false, this);
-  goog.events.listen(this.features_, ol.CollectionEventType.REMOVE,
-      this.handleFeatureRemove_, false, this);
+  // Add the script element to the document.
+  goog.dom.setProperties(script, {
+    'type': 'text/javascript',
+    'charset': 'UTF-8',
+    // NOTE(user): Safari never loads the script if we don't set
+    // the src attribute before appending.
+    'src': uri
+  });
+  var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);
+  scriptParent.appendChild(script);
 
+  return deferred;
 };
-goog.inherits(ol.interaction.Modify, ol.interaction.Pointer);
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @private
+ * Loads a JavaScript file and verifies it was evaluated successfully, using a
+ * verification object.
+ * The verification object is set by the loaded JavaScript at the end of the
+ * script.
+ * We verify this object was set and return its value in the success callback.
+ * If the object is not defined we trigger an error callback.
+ *
+ * @param {string} uri The URI of the JavaScript.
+ * @param {string} verificationObjName The name of the verification object that
+ *     the loaded script should set.
+ * @param {goog.net.jsloader.Options} options Optional parameters. See
+ *     goog.net.jsloader.Options documentation for details.
+ * @return {!goog.async.Deferred} The deferred result, that may be used to add
+ *     callbacks and/or cancel the transmission.
+ *     The success callback will be called with a single parameter containing
+ *     the value of the verification object.
+ *     The error callback will be called with a single goog.net.jsloader.Error
+ *     parameter.
  */
-ol.interaction.Modify.prototype.addFeature_ = function(feature) {
-  var geometry = feature.getGeometry();
-  if (goog.isDef(this.SEGMENT_WRITERS_[geometry.getType()])) {
-    this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry);
+goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) {
+  // Define the global objects variable.
+  if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) {
+    goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {};
   }
-  var map = this.getMap();
-  if (!goog.isNull(map)) {
-    this.handlePointerAtPixel_(this.lastPixel_, map);
+  var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_];
+
+  // Verify that the expected object does not exist yet.
+  if (goog.isDef(verifyObjs[verificationObjName])) {
+    // TODO(user): Error or reset variable?
+    return goog.async.Deferred.fail(new goog.net.jsloader.Error(
+        goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS,
+        'Verification object ' + verificationObjName + ' already defined.'));
   }
-};
 
+  // Send request to load the JavaScript.
+  var sendDeferred = goog.net.jsloader.load(uri, options);
 
-/**
- * @inheritDoc
- */
-ol.interaction.Modify.prototype.setMap = function(map) {
-  this.overlay_.setMap(map);
-  goog.base(this, 'setMap', map);
+  // Create a deferred object wrapping the send result.
+  var deferred = new goog.async.Deferred(
+      goog.bind(sendDeferred.cancel, sendDeferred));
+
+  // Call user back with object that was set by the script.
+  sendDeferred.addCallback(function() {
+    var result = verifyObjs[verificationObjName];
+    if (goog.isDef(result)) {
+      deferred.callback(result);
+      delete verifyObjs[verificationObjName];
+    } else {
+      // Error: script was not loaded properly.
+      deferred.errback(new goog.net.jsloader.Error(
+          goog.net.jsloader.ErrorCode.VERIFY_ERROR,
+          'Script ' + uri + ' loaded, but verification object ' +
+          verificationObjName + ' was not defined.'));
+    }
+  });
+
+  // Pass error to new deferred object.
+  sendDeferred.addErrback(function(error) {
+    if (goog.isDef(verifyObjs[verificationObjName])) {
+      delete verifyObjs[verificationObjName];
+    }
+    deferred.errback(error);
+  });
+
+  return deferred;
 };
 
 
 /**
- * @param {ol.CollectionEvent} evt Event.
+ * Gets the DOM element under which we should add new script elements.
+ * How? Take the first head element, and if not found take doc.documentElement,
+ * which always exists.
+ *
+ * @param {!HTMLDocument} doc The relevant document.
+ * @return {!Element} The script parent element.
  * @private
  */
-ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) {
-  var feature = evt.element;
-  goog.asserts.assertInstanceof(feature, ol.Feature);
-  this.addFeature_(feature);
+goog.net.jsloader.getScriptParentElement_ = function(doc) {
+  var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD);
+  if (!headElements || goog.array.isEmpty(headElements)) {
+    return doc.documentElement;
+  } else {
+    return headElements[0];
+  }
 };
 
 
 /**
- * @param {ol.CollectionEvent} evt Event.
+ * Cancels a given request.
+ * @this {{script_: Element, timeout_: number}} The request context.
  * @private
  */
-ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
-  var feature = evt.element;
-  var rBush = this.rBush_;
-  var i, nodesToRemove = [];
-  rBush.forEachInExtent(feature.getGeometry().getExtent(), function(node) {
-    if (feature === node.feature) {
-      nodesToRemove.push(node);
+goog.net.jsloader.cancel_ = function() {
+  var request = this;
+  if (request && request.script_) {
+    var scriptNode = request.script_;
+    if (scriptNode && scriptNode.tagName == 'SCRIPT') {
+      goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);
     }
-  });
-  for (i = nodesToRemove.length - 1; i >= 0; --i) {
-    rBush.remove(nodesToRemove[i]);
-  }
-  // There remains only vertexFeature…
-  if (!goog.isNull(this.vertexFeature_) &&
-      this.features_.getLength() === 0) {
-    this.overlay_.removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
   }
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Point} geometry Geometry.
+ * Removes the script node and the timeout.
+ *
+ * @param {Node} scriptNode The node to be cleaned up.
+ * @param {boolean} removeScriptNode If true completely remove the script node.
+ * @param {?number=} opt_timeout The timeout handler to cleanup.
  * @private
  */
-ol.interaction.Modify.prototype.writePointGeometry_ =
-    function(feature, geometry) {
-  var coordinates = geometry.getCoordinates();
-  var segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-    feature: feature,
-    geometry: geometry,
-    segment: [coordinates, coordinates]
-  });
-  this.rBush_.insert(geometry.getExtent(), segmentData);
-};
+goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode,
+                                      opt_timeout) {
+  if (goog.isDefAndNotNull(opt_timeout)) {
+    goog.global.clearTimeout(opt_timeout);
+  }
 
+  scriptNode.onload = goog.nullFunction;
+  scriptNode.onerror = goog.nullFunction;
+  scriptNode.onreadystatechange = goog.nullFunction;
 
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPoint} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writeMultiPointGeometry_ =
-    function(feature, geometry) {
-  var points = geometry.getCoordinates();
-  var coordinates, i, ii, segmentData;
-  for (i = 0, ii = points.length; i < ii; ++i) {
-    coordinates = points[i];
-    segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-      feature: feature,
-      geometry: geometry,
-      depth: [i],
-      index: i,
-      segment: [coordinates, coordinates]
-    });
-    this.rBush_.insert(geometry.getExtent(), segmentData);
+  // Do this after a delay (removing the script node of a running script can
+  // confuse older IEs).
+  if (removeScriptNode) {
+    window.setTimeout(function() {
+      goog.dom.removeNode(scriptNode);
+    }, 0);
   }
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.LineString} geometry Geometry.
- * @private
+ * Possible error codes for jsloader.
+ * @enum {number}
  */
-ol.interaction.Modify.prototype.writeLineStringGeometry_ =
-    function(feature, geometry) {
-  var coordinates = geometry.getCoordinates();
-  var i, ii, segment, segmentData;
-  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-    segment = coordinates.slice(i, i + 2);
-    segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-      feature: feature,
-      geometry: geometry,
-      index: i,
-      segment: segment
-    });
-    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-  }
+goog.net.jsloader.ErrorCode = {
+  LOAD_ERROR: 0,
+  TIMEOUT: 1,
+  VERIFY_ERROR: 2,
+  VERIFY_OBJECT_ALREADY_EXISTS: 3
 };
 
 
+
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiLineString} geometry Geometry.
- * @private
+ * A jsloader error.
+ *
+ * @param {goog.net.jsloader.ErrorCode} code The error code.
+ * @param {string=} opt_message Additional message.
+ * @constructor
+ * @extends {goog.debug.Error}
+ * @final
  */
-ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ =
-    function(feature, geometry) {
-  var lines = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, segment, segmentData;
-  for (j = 0, jj = lines.length; j < jj; ++j) {
-    coordinates = lines[j];
-    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-      segment = coordinates.slice(i, i + 2);
-      segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-        feature: feature,
-        geometry: geometry,
-        depth: [j],
-        index: i,
-        segment: segment
-      });
-      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-    }
+goog.net.jsloader.Error = function(code, opt_message) {
+  var msg = 'Jsloader error (code #' + code + ')';
+  if (opt_message) {
+    msg += ': ' + opt_message;
   }
+  goog.net.jsloader.Error.base(this, 'constructor', msg);
+
+  /**
+   * The code for this error.
+   *
+   * @type {goog.net.jsloader.ErrorCode}
+   */
+  this.code = code;
 };
+goog.inherits(goog.net.jsloader.Error, goog.debug.Error);
+
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
+// The original file lives here: http://go/cross_domain_channel.js
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Polygon} geometry Geometry.
- * @private
+ * @fileoverview Implements a cross-domain communication channel. A
+ * typical web page is prevented by browser security from sending
+ * request, such as a XMLHttpRequest, to other servers than the ones
+ * from which it came. The Jsonp class provides a workaround by
+ * using dynamically generated script tags. Typical usage:.
+ *
+ * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
+ * var payload = { 'foo': 1, 'bar': true };
+ * jsonp.send(payload, function(reply) { alert(reply) });
+ *
+ * This script works in all browsers that are currently supported by
+ * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
+ * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
+ *
  */
-ol.interaction.Modify.prototype.writePolygonGeometry_ =
-    function(feature, geometry) {
-  var rings = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, segment, segmentData;
-  for (j = 0, jj = rings.length; j < jj; ++j) {
-    coordinates = rings[j];
-    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-      segment = coordinates.slice(i, i + 2);
-      segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-        feature: feature,
-        geometry: geometry,
-        depth: [j],
-        index: i,
-        segment: segment
-      });
-      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-    }
-  }
-};
+
+goog.provide('goog.net.Jsonp');
+
+goog.require('goog.Uri');
+goog.require('goog.net.jsloader');
+
+// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+//
+// This class allows us (Google) to send data from non-Google and thus
+// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
+// anything sensitive, such as session or cookie specific data. Return
+// only data that you want parties external to Google to have. Also
+// NEVER use this method to send data from web pages to untrusted
+// servers, or redirects to unknown servers (www.google.com/cache,
+// /q=xx&btnl, /url, www.googlepages.com, etc.)
+//
+// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @private
+ * Creates a new cross domain channel that sends data to the specified
+ * host URL. By default, if no reply arrives within 5s, the channel
+ * assumes the call failed to complete successfully.
+ *
+ * @param {goog.Uri|string} uri The Uri of the server side code that receives
+ *     data posted through this channel (e.g.,
+ *     "http://maps.google.com/maps/geo").
+ *
+ * @param {string=} opt_callbackParamName The parameter name that is used to
+ *     specify the callback. Defaults to "callback".
+ *
+ * @constructor
+ * @final
  */
-ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ =
-    function(feature, geometry) {
-  var polygons = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
-  for (k = 0, kk = polygons.length; k < kk; ++k) {
-    rings = polygons[k];
-    for (j = 0, jj = rings.length; j < jj; ++j) {
-      coordinates = rings[j];
-      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-        segment = coordinates.slice(i, i + 2);
-        segmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-          feature: feature,
-          geometry: geometry,
-          depth: [j, k],
-          index: i,
-          segment: segment
-        });
-        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-      }
-    }
-  }
+goog.net.Jsonp = function(uri, opt_callbackParamName) {
+  /**
+   * The uri_ object will be used to encode the payload that is sent to the
+   * server.
+   * @type {goog.Uri}
+   * @private
+   */
+  this.uri_ = new goog.Uri(uri);
+
+  /**
+   * This is the callback parameter name that is added to the uri.
+   * @type {string}
+   * @private
+   */
+  this.callbackParamName_ = opt_callbackParamName ?
+      opt_callbackParamName : 'callback';
+
+  /**
+   * The length of time, in milliseconds, this channel is prepared
+   * to wait for for a request to complete. The default value is 5 seconds.
+   * @type {number}
+   * @private
+   */
+  this.timeout_ = 5000;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * The name of the property of goog.global under which the callback is
+ * stored.
+ */
+goog.net.Jsonp.CALLBACKS = '_callbacks_';
+
+
+/**
+ * Used to generate unique callback IDs. The counter must be global because
+ * all channels share a common callback object.
  * @private
  */
-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]);
-  }
-};
+goog.net.Jsonp.scriptCounter_ = 0;
 
 
 /**
- * @param {ol.Coordinate} coordinates Coordinates.
- * @return {ol.Feature} Vertex feature.
- * @private
+ * Sets the length of time, in milliseconds, this channel is prepared
+ * to wait for for a request to complete. If the call is not competed
+ * within the set time span, it is assumed to have failed. To wait
+ * indefinitely for a request to complete set the timout to a negative
+ * number.
+ *
+ * @param {number} timeout The length of time before calls are
+ * interrupted.
  */
-ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ =
-    function(coordinates) {
-  var vertexFeature = this.vertexFeature_;
-  if (goog.isNull(vertexFeature)) {
-    vertexFeature = new ol.Feature(new ol.geom.Point(coordinates));
-    this.vertexFeature_ = vertexFeature;
-    this.overlay_.addFeature(vertexFeature);
-  } else {
-    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
-    geometry.setCoordinates(coordinates);
-  }
-  return vertexFeature;
+goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
+  this.timeout_ = timeout;
 };
 
 
 /**
- * @inheritDoc
+ * Returns the current timeout value, in milliseconds.
+ *
+ * @return {number} The timeout value.
  */
-ol.interaction.Modify.prototype.handlePointerDown = function(evt) {
-  this.handlePointerAtPixel_(evt.pixel, evt.map);
-  this.dragSegments_ = [];
-  var vertexFeature = this.vertexFeature_;
-  if (!goog.isNull(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 distinctFeatures = {};
-    for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) {
-      var segmentDataMatch = segmentDataMatches[i];
-      var segment = segmentDataMatch.segment;
-      if (!(goog.getUid(segmentDataMatch.feature) in distinctFeatures)) {
-        var feature = segmentDataMatch.feature;
-        distinctFeatures[goog.getUid(feature)] = true;
-      }
-      if (ol.coordinate.equals(segment[0], vertex)) {
-        this.dragSegments_.push([segmentDataMatch, 0]);
-      } else if (ol.coordinate.equals(segment[1], vertex)) {
-        this.dragSegments_.push([segmentDataMatch, 1]);
-      } else if (goog.getUid(segment) in this.vertexSegments_) {
-        insertVertices.push([segmentDataMatch, vertex]);
-      }
-    }
-    for (i = insertVertices.length - 1; i >= 0; --i) {
-      this.insertVertex_.apply(this, insertVertices[i]);
-    }
-  }
-  return !goog.isNull(this.vertexFeature_);
+goog.net.Jsonp.prototype.getRequestTimeout = function() {
+  return this.timeout_;
 };
 
 
 /**
- * @inheritDoc
+ * Sends the given payload to the URL specified at the construction
+ * time. The reply is delivered to the given replyCallback. If the
+ * errorCallback is specified and the reply does not arrive within the
+ * timeout period set on this channel, the errorCallback is invoked
+ * with the original payload.
+ *
+ * If no reply callback is specified, then the response is expected to
+ * consist of calls to globally registered functions. No &callback=
+ * URL parameter will be sent in the request, and the script element
+ * will be cleaned up after the timeout.
+ *
+ * @param {Object=} opt_payload Name-value pairs.  If given, these will be
+ *     added as parameters to the supplied URI as GET parameters to the
+ *     given server URI.
+ *
+ * @param {Function=} opt_replyCallback A function expecting one
+ *     argument, called when the reply arrives, with the response data.
+ *
+ * @param {Function=} opt_errorCallback A function expecting one
+ *     argument, called on timeout, with the payload (if given), otherwise
+ *     null.
+ *
+ * @param {string=} opt_callbackParamValue Value to be used as the
+ *     parameter value for the callback parameter (callbackParamName).
+ *     To be used when the value needs to be fixed by the client for a
+ *     particular request, to make use of the cached responses for the request.
+ *     NOTE: If multiple requests are made with the same
+ *     opt_callbackParamValue, only the last call will work whenever the
+ *     response comes back.
+ *
+ * @return {!Object} A request descriptor that may be used to cancel this
+ *     transmission, or null, if the message may not be cancelled.
  */
-ol.interaction.Modify.prototype.handlePointerDrag = function(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];
+goog.net.Jsonp.prototype.send = function(opt_payload,
+                                         opt_replyCallback,
+                                         opt_errorCallback,
+                                         opt_callbackParamValue) {
 
-    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;
-    }
+  var payload = opt_payload || null;
 
-    geometry.setCoordinates(coordinates);
-    this.createOrUpdateVertexFeature_(vertex);
+  var id = opt_callbackParamValue ||
+      '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
+      goog.now().toString(36);
+
+  if (!goog.global[goog.net.Jsonp.CALLBACKS]) {
+    goog.global[goog.net.Jsonp.CALLBACKS] = {};
   }
-};
 
+  // Create a new Uri object onto which this payload will be added
+  var uri = this.uri_.clone();
+  if (payload) {
+    goog.net.Jsonp.addPayloadToUri_(payload, uri);
+  }
 
-/**
- * @inheritDoc
- */
-ol.interaction.Modify.prototype.handlePointerUp = 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 (opt_replyCallback) {
+    var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback);
+    goog.global[goog.net.Jsonp.CALLBACKS][id] = reply;
+
+    uri.setParameterValues(this.callbackParamName_,
+                           goog.net.Jsonp.CALLBACKS + '.' + id);
   }
-  return false;
+
+  var deferred = goog.net.jsloader.load(uri.toString(),
+      {timeout: this.timeout_, cleanupWhenDone: true});
+  var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback);
+  deferred.addErrback(error);
+
+  return {id_: id, deferred_: deferred};
 };
 
 
 /**
- * @inheritDoc
+ * Cancels a given request. The request must be exactly the object returned by
+ * the send method.
+ *
+ * @param {Object} request The request object returned by the send method.
  */
-ol.interaction.Modify.prototype.handleMapBrowserEvent =
-    function(mapBrowserEvent) {
-  var handled;
-  if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
-      mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) {
-    this.handlePointerMove_(mapBrowserEvent);
-  }
-  if (!goog.isNull(this.vertexFeature_) && this.snappedToVertex_ &&
-      this.deleteCondition_(mapBrowserEvent)) {
-    var geometry = this.vertexFeature_.getGeometry();
-    goog.asserts.assertInstanceof(geometry, ol.geom.Point);
-    handled = this.removeVertex_();
+goog.net.Jsonp.prototype.cancel = function(request) {
+  if (request) {
+    if (request.deferred_) {
+      request.deferred_.cancel();
+    }
+    if (request.id_) {
+      goog.net.Jsonp.cleanup_(request.id_, false);
+    }
   }
-  return goog.base(this, 'handleMapBrowserEvent', mapBrowserEvent) && !handled;
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} evt Event.
+ * Creates a timeout callback that calls the given timeoutCallback with the
+ * original payload.
+ *
+ * @param {string} id The id of the script node.
+ * @param {Object} payload The payload that was sent to the server.
+ * @param {Function=} opt_errorCallback The function called on timeout.
+ * @return {!Function} A zero argument function that handles callback duties.
  * @private
  */
-ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
-  this.lastPixel_ = evt.pixel;
-  this.handlePointerAtPixel_(evt.pixel, evt.map);
+goog.net.Jsonp.newErrorHandler_ = function(id,
+                                           payload,
+                                           opt_errorCallback) {
+  /**
+   * When we call across domains with a request, this function is the
+   * timeout handler. Once it's done executing the user-specified
+   * error-handler, it removes the script node and original function.
+   */
+  return function() {
+    goog.net.Jsonp.cleanup_(id, false);
+    if (opt_errorCallback) {
+      opt_errorCallback(payload);
+    }
+  };
 };
 
 
 /**
- * @param {ol.Pixel} pixel Pixel
- * @param {ol.Map} map Map.
+ * Creates a reply callback that calls the given replyCallback with data
+ * returned by the server.
+ *
+ * @param {string} id The id of the script node.
+ * @param {Function} replyCallback The function called on reply.
+ * @return {!Function} A reply callback function.
  * @private
  */
-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);
+goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) {
+  /**
+   * This function is the handler for the all-is-well response. It
+   * clears the error timeout handler, calls the user's handler, then
+   * removes the script node and itself.
+   *
+   * @param {...Object} var_args The response data sent from the server.
+   */
+  var handler = function(var_args) {
+    goog.net.Jsonp.cleanup_(id, true);
+    replyCallback.apply(undefined, arguments);
   };
+  return handler;
+};
 
-  var lowerLeft = map.getCoordinateFromPixel(
-      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
-  var upperRight = map.getCoordinateFromPixel(
-      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
-  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
 
-  var rBush = this.rBush_;
-  var nodes = rBush.getInExtent(box);
-  if (nodes.length > 0) {
-    nodes.sort(sortByDistance);
-    var node = nodes[0];
-    var closestSegment = node.segment;
-    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
-        closestSegment));
-    var vertexPixel = map.getPixelFromCoordinate(vertex);
-    if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
-        this.pixelTolerance_) {
-      var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
-      var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
-      var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
-      var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
-      var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
-      this.snappedToVertex_ = dist <= this.pixelTolerance_;
-      if (this.snappedToVertex_) {
-        vertex = squaredDist1 > squaredDist2 ?
-            closestSegment[1] : closestSegment[0];
-      }
-      this.createOrUpdateVertexFeature_(vertex);
-      var vertexSegments = {};
-      vertexSegments[goog.getUid(closestSegment)] = true;
-      var segment;
-      for (var i = 1, ii = nodes.length; i < ii; ++i) {
-        segment = nodes[i].segment;
-        if ((ol.coordinate.equals(closestSegment[0], segment[0]) &&
-            ol.coordinate.equals(closestSegment[1], segment[1]) ||
-            (ol.coordinate.equals(closestSegment[0], segment[1]) &&
-            ol.coordinate.equals(closestSegment[1], segment[0])))) {
-          vertexSegments[goog.getUid(segment)] = true;
-        } else {
-          break;
-        }
-      }
-      this.vertexSegments_ = vertexSegments;
-      return;
+/**
+ * Removes the script node and reply handler with the given id.
+ *
+ * @param {string} id The id of the script node to be removed.
+ * @param {boolean} deleteReplyHandler If true, delete the reply handler
+ *     instead of setting it to nullFunction (if we know the callback could
+ *     never be called again).
+ * @private
+ */
+goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) {
+  if (goog.global[goog.net.Jsonp.CALLBACKS][id]) {
+    if (deleteReplyHandler) {
+      delete goog.global[goog.net.Jsonp.CALLBACKS][id];
+    } else {
+      // Removing the script tag doesn't necessarily prevent the script
+      // from firing, so we make the callback a noop.
+      goog.global[goog.net.Jsonp.CALLBACKS][id] = goog.nullFunction;
     }
   }
-  if (!goog.isNull(this.vertexFeature_)) {
-    this.overlay_.removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
-  }
 };
 
 
 /**
- * @param {ol.interaction.SegmentDataType} segmentData Segment data.
- * @param {ol.Coordinate} vertex Vertex.
+ * Returns URL encoded payload. The payload should be a map of name-value
+ * pairs, in the form {"foo": 1, "bar": true, ...}.  If the map is empty,
+ * the URI will be unchanged.
+ *
+ * <p>The method uses hasOwnProperty() to assure the properties are on the
+ * object, not on its prototype.
+ *
+ * @param {!Object} payload A map of value name pairs to be encoded.
+ *     A value may be specified as an array, in which case a query parameter
+ *     will be created for each value, e.g.:
+ *     {"foo": [1,2]} will encode to "foo=1&foo=2".
+ *
+ * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
+ *     will be encoded.
+ *
+ * @return {!goog.Uri} A reference to the Uri sent as a parameter.
  * @private
  */
-ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) {
-  var segment = segmentData.segment;
-  var feature = segmentData.feature;
-  var geometry = segmentData.geometry;
-  var depth = segmentData.depth;
-  var index = segmentData.index;
-  var coordinates;
-
-  switch (geometry.getType()) {
-    case ol.geom.GeometryType.MULTI_LINE_STRING:
-      goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
-      coordinates = geometry.getCoordinates();
-      coordinates[depth[0]].splice(index + 1, 0, vertex);
-      break;
-    case ol.geom.GeometryType.POLYGON:
-      goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
-      coordinates = geometry.getCoordinates();
-      coordinates[depth[0]].splice(index + 1, 0, vertex);
-      break;
-    case ol.geom.GeometryType.MULTI_POLYGON:
-      goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon);
-      coordinates = geometry.getCoordinates();
-      coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex);
-      break;
-    case ol.geom.GeometryType.LINE_STRING:
-      goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
-      coordinates = geometry.getCoordinates();
-      coordinates.splice(index + 1, 0, vertex);
-      break;
-    default:
-      return;
+goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
+  for (var name in payload) {
+    // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
+    // case, we iterate over all properties as a very lame workaround.
+    if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
+      uri.setParameterValues(name, payload[name]);
+    }
   }
+  return uri;
+};
 
-  geometry.setCoordinates(coordinates);
-  var rTree = this.rBush_;
-  goog.asserts.assert(goog.isDef(segment));
-  rTree.remove(segmentData);
-  goog.asserts.assert(goog.isDef(index));
-  this.updateSegmentIndices_(geometry, index, depth, 1);
-  var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-    segment: [segment[0], vertex],
-    feature: feature,
-    geometry: geometry,
-    depth: depth,
-    index: index
-  });
-  rTree.insert(ol.extent.boundingExtent(newSegmentData.segment),
-      newSegmentData);
-  this.dragSegments_.push([newSegmentData, 1]);
 
-  var newSegmentData2 = /** @type {ol.interaction.SegmentDataType} */ ({
-    segment: [vertex, segment[1]],
-    feature: feature,
-    geometry: geometry,
-    depth: depth,
-    index: index + 1
-  });
-  rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment),
-      newSegmentData2);
-  this.dragSegments_.push([newSegmentData2, 0]);
-};
+// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+//
+// This class allows us (Google) to send data from non-Google and thus
+// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
+// anything sensitive, such as session or cookie specific data. Return
+// only data that you want parties external to Google to have. Also
+// NEVER use this method to send data from web pages to untrusted
+// servers, or redirects to unknown servers (www.google.com/cache,
+// /q=xx&btnl, /url, www.googlepages.com, etc.)
+//
+// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
 
+goog.provide('ol.TileUrlFunction');
+goog.provide('ol.TileUrlFunctionType');
 
-/**
- * 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, newSegment, right, segmentData, uid;
-  for (i = dragSegments.length - 1; i >= 0; --i) {
-    dragSegment = dragSegments[i];
-    segmentData = dragSegment[0];
-    geometry = segmentData.geometry;
-    coordinates = geometry.getCoordinates();
-    uid = goog.getUid(segmentData.feature);
-    left = right = index = undefined;
-    if (dragSegment[1] === 0) {
-      right = segmentData;
-      index = segmentData.index;
-    } else if (dragSegment[1] == 1) {
-      left = segmentData;
-      index = segmentData.index + 1;
-    }
-    if (!(uid in segmentsByFeature)) {
-      segmentsByFeature[uid] = [left, right, index];
-    }
-    newSegment = segmentsByFeature[uid];
-    if (goog.isDef(left)) {
-      newSegment[0] = left;
-    }
-    if (goog.isDef(right)) {
-      newSegment[1] = right;
-    }
-    if (goog.isDef(newSegment[0]) && goog.isDef(newSegment[1])) {
-      component = coordinates;
-      deleted = false;
-      newIndex = index - 1;
-      switch (geometry.getType()) {
-        case ol.geom.GeometryType.MULTI_LINE_STRING:
-          coordinates[segmentData.depth[0]].splice(index, 1);
-          deleted = true;
-          break;
-        case ol.geom.GeometryType.LINE_STRING:
-          coordinates.splice(index, 1);
-          deleted = true;
-          break;
-        case ol.geom.GeometryType.MULTI_POLYGON:
-          component = component[segmentData.depth[1]];
-          /* falls through */
-        case ol.geom.GeometryType.POLYGON:
-          component = component[segmentData.depth[0]];
-          if (component.length > 4) {
-            if (index == component.length - 1) {
-              index = 0;
-            }
-            component.splice(index, 1);
-            deleted = true;
-            if (index === 0) {
-              // close the ring again
-              component.pop();
-              component.push(component[0]);
-              newIndex = component.length - 1;
-            }
-          }
-          break;
-      }
+goog.require('goog.array');
+goog.require('goog.math');
+goog.require('ol.TileCoord');
+goog.require('ol.tilecoord');
 
-      if (deleted) {
-        this.rBush_.remove(newSegment[0]);
-        this.rBush_.remove(newSegment[1]);
-        geometry.setCoordinates(coordinates);
-        goog.asserts.assert(newIndex >= 0);
-        var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({
-          depth: segmentData.depth,
-          feature: segmentData.feature,
-          geometry: segmentData.geometry,
-          index: newIndex,
-          segment: [newSegment[0].segment[0], newSegment[1].segment[1]]
-        });
-        this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment),
-            newSegmentData);
-        this.updateSegmentIndices_(geometry, index, segmentData.depth, -1);
 
-        this.overlay_.removeFeature(this.vertexFeature_);
-        this.vertexFeature_ = null;
-      }
-    }
-  }
-  return deleted;
-};
+/**
+ * A function that takes an {@link ol.TileCoord} for the tile coordinate,
+ * a `{number}` representing the pixel ratio and an {@link ol.proj.Projection}
+ * for the projection  as arguments and returns a `{string}` or
+ * undefined representing the tile URL.
+ *
+ * @typedef {function(ol.TileCoord, number,
+ *           ol.proj.Projection): (string|undefined)}
+ * @api
+ */
+ol.TileUrlFunctionType;
 
 
 /**
- * @inheritDoc
+ * @typedef {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=):
+ *     ol.TileCoord}
  */
-ol.interaction.Modify.prototype.shouldStopEvent = goog.functions.identity;
+ol.TileCoordTransformType;
 
 
 /**
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {number} index Index.
- * @param {Array.<number>|undefined} depth Depth.
- * @param {number} delta Delta (1 or -1).
- * @private
+ * @param {string} template Template.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
  */
-ol.interaction.Modify.prototype.updateSegmentIndices_ = function(
-    geometry, index, depth, delta) {
-  this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) {
-    if (segmentDataMatch.geometry === geometry &&
-        (!goog.isDef(depth) ||
-        goog.array.equals(segmentDataMatch.depth, depth)) &&
-        segmentDataMatch.index > index) {
-      segmentDataMatch.index += delta;
-    }
-  });
+ol.TileUrlFunction.createFromTemplate = function(template) {
+  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 (goog.isNull(tileCoord)) {
+          return undefined;
+        } else {
+          return template.replace(zRegEx, tileCoord[0].toString())
+                         .replace(xRegEx, tileCoord[1].toString())
+                         .replace(yRegEx, tileCoord[2].toString())
+                         .replace(dashYRegEx, function() {
+                           var y = (1 << tileCoord[0]) - tileCoord[2] - 1;
+                           return y.toString();
+                         });
+        }
+      });
 };
 
 
 /**
- * @return {ol.style.StyleFunction} Styles.
+ * @param {Array.<string>} templates Templates.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
  */
-ol.interaction.Modify.getDefaultStyleFunction = function() {
-  var style = ol.style.createDefaultEditingStyles();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POINT];
-  };
+ol.TileUrlFunction.createFromTemplates = function(templates) {
+  return ol.TileUrlFunction.createFromTileUrlFunctions(
+      goog.array.map(templates, ol.TileUrlFunction.createFromTemplate));
 };
 
-goog.provide('ol.interaction.Select');
-
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.functions');
-goog.require('ol.CollectionEventType');
-goog.require('ol.Feature');
-goog.require('ol.FeatureOverlay');
-goog.require('ol.events.condition');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.style.Style');
-
-
 
 /**
- * @classdesc
- * Handles selection of vector data. A {@link ol.FeatureOverlay} is maintained
- * internally to store the selected feature(s). Which features are selected is
- * determined by the `condition` option, and optionally the `toggle` or
- * `add`/`remove` options.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.SelectOptions=} opt_options Options.
- * @api stable
+ * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
  */
-ol.interaction.Select = function(opt_options) {
-
-  goog.base(this);
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.condition_ = goog.isDef(options.condition) ?
-      options.condition : ol.events.condition.singleClick;
-
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.addCondition_ = goog.isDef(options.addCondition) ?
-      options.addCondition : ol.events.condition.never;
-
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.removeCondition_ = goog.isDef(options.removeCondition) ?
-      options.removeCondition : ol.events.condition.never;
-
-  /**
-   * @private
-   * @type {ol.events.ConditionType}
-   */
-  this.toggleCondition_ = goog.isDef(options.toggleCondition) ?
-      options.toggleCondition : ol.events.condition.shiftKeyOnly;
-
-  var layerFilter;
-  if (goog.isDef(options.layers)) {
-    if (goog.isFunction(options.layers)) {
-      layerFilter = options.layers;
-    } else {
-      var layers = options.layers;
-      layerFilter =
-          /**
-           * @param {ol.layer.Layer} layer Layer.
-           * @return {boolean} Include.
-           */
-          function(layer) {
-        return goog.array.contains(layers, layer);
-      };
-    }
-  } else {
-    layerFilter = goog.functions.TRUE;
+ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
+  if (tileUrlFunctions.length === 1) {
+    return tileUrlFunctions[0];
   }
+  return (
+      /**
+       * @param {ol.TileCoord} tileCoord Tile Coordinate.
+       * @param {number} pixelRatio Pixel ratio.
+       * @param {ol.proj.Projection} projection Projection.
+       * @return {string|undefined} Tile URL.
+       */
+      function(tileCoord, pixelRatio, projection) {
+        if (goog.isNull(tileCoord)) {
+          return undefined;
+        } else {
+          var h = ol.tilecoord.hash(tileCoord);
+          var index = goog.math.modulo(h, tileUrlFunctions.length);
+          return tileUrlFunctions[index](tileCoord, pixelRatio, projection);
+        }
+      });
+};
 
-  /**
-   * @private
-   * @type {function(ol.layer.Layer): boolean}
-   */
-  this.layerFilter_ = layerFilter;
-
-  /**
-   * @private
-   * @type {ol.FeatureOverlay}
-   */
-  this.featureOverlay_ = new ol.FeatureOverlay({
-    style: (goog.isDef(options.style)) ? options.style :
-        ol.interaction.Select.getDefaultStyleFunction()
-  });
-
-  var features = this.featureOverlay_.getFeatures();
-  goog.events.listen(features, ol.CollectionEventType.ADD,
-      this.addFeature_, false, this);
-  goog.events.listen(features, ol.CollectionEventType.REMOVE,
-      this.removeFeature_, false, this);
 
+/**
+ * @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;
 };
-goog.inherits(ol.interaction.Select, ol.interaction.Interaction);
 
 
 /**
- * Get the selected features.
- * @return {ol.Collection.<ol.Feature>} Features collection.
- * @api stable
+ * @param {ol.TileCoordTransformType} transformFn Transform function.
+ * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
  */
-ol.interaction.Select.prototype.getFeatures = function() {
-  return this.featureOverlay_.getFeatures();
+ol.TileUrlFunction.withTileCoordTransform =
+    function(transformFn, tileUrlFunction) {
+  var tmpTileCoord = [0, 0, 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 (goog.isNull(tileCoord)) {
+          return undefined;
+        } else {
+          return tileUrlFunction(
+              transformFn(tileCoord, projection, tmpTileCoord),
+              pixelRatio,
+              projection);
+        }
+      });
 };
 
 
 /**
- * @inheritDoc
+ * @param {string} url URL.
+ * @return {Array.<string>} Array of urls.
  */
-ol.interaction.Select.prototype.handleMapBrowserEvent =
-    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_.getFeatures();
-  if (set) {
-    // Replace the currently selected feature(s) with the feature at the pixel,
-    // or clear the selected feature(s) if there is no feature at the pixel.
-    /** @type {ol.Feature|undefined} */
-    var feature = map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
-        /**
-         * @param {ol.Feature} feature Feature.
-         * @param {ol.layer.Layer} layer Layer.
-         */
-        function(feature, layer) {
-          return feature;
-        }, undefined, this.layerFilter_);
-    if (goog.isDef(feature) &&
-        features.getLength() == 1 &&
-        features.item(0) == feature) {
-      // No change
-    } else {
-      if (features.getLength() !== 0) {
-        features.clear();
-      }
-      if (goog.isDef(feature)) {
-        features.push(feature);
-      }
+ol.TileUrlFunction.expandUrl = function(url) {
+  var urls = [];
+  var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url);
+  if (match) {
+    var startCharCode = match[1].charCodeAt(0);
+    var stopCharCode = match[2].charCodeAt(0);
+    var charCode;
+    for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) {
+      urls.push(url.replace(match[0], String.fromCharCode(charCode)));
     }
   } else {
-    // Modify the currently selected feature(s).
-    var /** @type {Array.<number>} */ deselected = [];
-    var /** @type {Array.<ol.Feature>} */ selected = [];
-    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
-        /**
-         * @param {ol.Feature} feature Feature.
-         * @param {ol.layer.Layer} layer Layer.
-         */
-        function(feature, layer) {
-          var index = goog.array.indexOf(features.getArray(), feature);
-          if (index == -1) {
-            if (add || toggle) {
-              selected.push(feature);
-            }
-          } else {
-            if (remove || toggle) {
-              deselected.push(index);
-            }
-          }
-        }, undefined, this.layerFilter_);
-    var i;
-    for (i = deselected.length - 1; i >= 0; --i) {
-      features.removeAt(deselected[i]);
-    }
-    features.extend(selected);
+    urls.push(url);
   }
-  return false;
+  return urls;
 };
 
+goog.provide('ol.TileCache');
+
+goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.TileRange');
+goog.require('ol.structs.LRUCache');
+goog.require('ol.tilecoord');
+
+
 
 /**
- * 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
+ * @constructor
+ * @extends {ol.structs.LRUCache.<ol.Tile>}
+ * @param {number=} opt_highWaterMark High water mark.
+ * @struct
  */
-ol.interaction.Select.prototype.setMap = function(map) {
-  var currentMap = this.getMap();
-  var selectedFeatures = this.featureOverlay_.getFeatures();
-  if (!goog.isNull(currentMap)) {
-    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
-  }
-  goog.base(this, 'setMap', map);
-  this.featureOverlay_.setMap(map);
-  if (!goog.isNull(map)) {
-    selectedFeatures.forEach(map.skipFeature, map);
-  }
+ol.TileCache = function(opt_highWaterMark) {
+
+  goog.base(this);
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.highWaterMark_ = goog.isDef(opt_highWaterMark) ?
+      opt_highWaterMark : ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK;
+
 };
+goog.inherits(ol.TileCache, ol.structs.LRUCache);
 
 
 /**
- * @return {ol.style.StyleFunction} Styles.
+ * @return {boolean} Can expire cache.
  */
-ol.interaction.Select.getDefaultStyleFunction = function() {
-  var styles = ol.style.createDefaultEditingStyles();
-  goog.array.extend(styles[ol.geom.GeometryType.POLYGON],
-      styles[ol.geom.GeometryType.LINE_STRING]);
-  goog.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION],
-      styles[ol.geom.GeometryType.LINE_STRING]);
-
-  return function(feature, resolution) {
-    return styles[feature.getGeometry().getType()];
-  };
+ol.TileCache.prototype.canExpireCache = function() {
+  return this.getCount() > this.highWaterMark_;
 };
 
 
 /**
- * @param {ol.CollectionEvent} evt Event.
- * @private
+ * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
  */
-ol.interaction.Select.prototype.addFeature_ = function(evt) {
-  var feature = evt.element;
-  var map = this.getMap();
-  goog.asserts.assertInstanceof(feature, ol.Feature);
-  if (!goog.isNull(map)) {
-    map.skipFeature(feature);
+ol.TileCache.prototype.expireCache = function(usedTiles) {
+  var tile, zKey;
+  while (this.canExpireCache()) {
+    tile = /** @type {ol.Tile} */ (this.peekLast());
+    zKey = tile.tileCoord[0].toString();
+    if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) {
+      break;
+    } else {
+      this.pop().dispose();
+    }
   }
 };
 
 
 /**
- * @param {ol.CollectionEvent} evt Event.
- * @private
+ * Remove a tile range from the cache, e.g. to invalidate tiles.
+ * @param {ol.TileRange} tileRange The tile range to prune.
  */
-ol.interaction.Select.prototype.removeFeature_ = function(evt) {
-  var feature = evt.element;
-  var map = this.getMap();
-  goog.asserts.assertInstanceof(feature, ol.Feature);
-  if (!goog.isNull(map)) {
-    map.unskipFeature(feature);
+ol.TileCache.prototype.pruneTileRange = function(tileRange) {
+  var i = this.getCount(),
+      key;
+  while (i--) {
+    key = this.peekLastKey();
+    if (tileRange.contains(ol.tilecoord.createFromString(key))) {
+      this.pop().dispose();
+    } else {
+      this.get(key);
+    }
   }
 };
 
-goog.provide('ol.layer.Heatmap');
+goog.provide('ol.source.TileImage');
 
 goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.math');
-goog.require('ol.Object');
-goog.require('ol.dom');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.EventType');
-goog.require('ol.style.Icon');
-goog.require('ol.style.Style');
-
-
-/**
- * @enum {string}
- */
-ol.layer.HeatmapLayerProperty = {
-  GRADIENT: 'gradient'
-};
+goog.require('ol.ImageTile');
+goog.require('ol.TileCache');
+goog.require('ol.TileCoord');
+goog.require('ol.TileLoadFunctionType');
+goog.require('ol.TileState');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.TileUrlFunctionType');
+goog.require('ol.source.Tile');
 
 
 
 /**
  * @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.
+ * Base class for sources providing images divided into a tile grid.
  *
  * @constructor
- * @extends {ol.layer.Vector}
- * @fires ol.render.Event
- * @param {olx.layer.HeatmapOptions=} opt_options Options.
+ * @extends {ol.source.Tile}
+ * @param {olx.source.TileImageOptions} options Image tile options.
  * @api
  */
-ol.layer.Heatmap = function(opt_options) {
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.source.TileImage = function(options) {
 
-  goog.base(this, /** @type {olx.layer.VectorOptions} */ (options));
+  goog.base(this, {
+    attributions: options.attributions,
+    extent: options.extent,
+    logo: options.logo,
+    opaque: options.opaque,
+    projection: options.projection,
+    state: goog.isDef(options.state) ?
+        /** @type {ol.source.State} */ (options.state) : undefined,
+    tileGrid: options.tileGrid,
+    tilePixelRatio: options.tilePixelRatio
+  });
 
   /**
-   * @private
-   * @type {Uint8ClampedArray}
+   * @protected
+   * @type {ol.TileUrlFunctionType}
    */
-  this.gradient_ = null;
-
-  goog.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.GRADIENT),
-      this.handleGradientChanged_, false, this);
-
-  this.setGradient(goog.isDef(options.gradient) ?
-      options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
-
-  var circle = ol.layer.Heatmap.createCircle_(
-      goog.isDef(options.radius) ? options.radius : 8,
-      goog.isDef(options.blur) ? options.blur : 15,
-      goog.isDef(options.shadow) ? options.shadow : 250);
+  this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ?
+      options.tileUrlFunction :
+      ol.TileUrlFunction.nullTileUrlFunction;
 
   /**
-   * @type {Array.<Array.<ol.style.Style>>}
+   * @protected
+   * @type {?string}
    */
-  var styleCache = new Array(256);
+  this.crossOrigin =
+      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
 
-  var weight = goog.isDef(options.weight) ? options.weight : 'weight';
-  var weightFunction;
-  if (goog.isString(weight)) {
-    weightFunction = function(feature) {
-      return feature.get(weight);
-    };
-  } else {
-    weightFunction = weight;
-  }
-  goog.asserts.assert(goog.isFunction(weightFunction));
+  /**
+   * @protected
+   * @type {ol.TileCache}
+   */
+  this.tileCache = new ol.TileCache();
 
-  this.setStyle(function(feature, resolution) {
-    var weight = weightFunction(feature);
-    var opacity = goog.isDef(weight) ? goog.math.clamp(weight, 0, 1) : 1;
-    // cast to 8 bits
-    var index = (255 * opacity) | 0;
-    var style = styleCache[index];
-    if (!goog.isDef(style)) {
-      style = [
-        new ol.style.Style({
-          image: new ol.style.Icon({
-            opacity: opacity,
-            src: circle
-          })
-        })
-      ];
-      styleCache[index] = style;
-    }
-    return style;
-  });
+  /**
+   * @protected
+   * @type {ol.TileLoadFunctionType}
+   */
+  this.tileLoadFunction = goog.isDef(options.tileLoadFunction) ?
+      options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction;
 
-  // For performance reasons, don't sort the features before rendering.
-  // The render order is not relevant for a heatmap representation.
-  this.setRenderOrder(null);
+  /**
+   * @protected
+   * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string,
+   *        ?string, ol.TileLoadFunctionType)}
+   */
+  this.tileClass = goog.isDef(options.tileClass) ?
+      options.tileClass : ol.ImageTile;
 
-  goog.events.listen(this, ol.render.EventType.RENDER,
-      this.handleRender_, false, this);
+};
+goog.inherits(ol.source.TileImage, ol.source.Tile);
 
+
+/**
+ * @param {ol.ImageTile} imageTile Image tile.
+ * @param {string} src Source.
+ */
+ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
+  imageTile.getImage().src = src;
 };
-goog.inherits(ol.layer.Heatmap, ol.layer.Vector);
 
 
 /**
- * @const
- * @type {Array.<string>}
+ * @inheritDoc
  */
-ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
+ol.source.TileImage.prototype.canExpireCache = function() {
+  return this.tileCache.canExpireCache();
+};
 
 
 /**
- * @param {Array.<string>} colors
- * @return {Uint8ClampedArray}
- * @private
+ * @inheritDoc
  */
-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]);
-  }
+ol.source.TileImage.prototype.expireCache = function(usedTiles) {
+  this.tileCache.expireCache(usedTiles);
+};
 
-  context.fillStyle = gradient;
-  context.fillRect(0, 0, width, height);
 
-  return context.getImageData(0, 0, width, height).data;
+/**
+ * @inheritDoc
+ */
+ol.source.TileImage.prototype.getTile =
+    function(z, x, y, pixelRatio, projection) {
+  var tileCoordKey = this.getKeyZXY(z, x, y);
+  if (this.tileCache.containsKey(tileCoordKey)) {
+    return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
+  } else {
+    goog.asserts.assert(projection);
+    var tileCoord = [z, x, y];
+    var tileUrl = this.tileUrlFunction(tileCoord, pixelRatio, projection);
+    var tile = new this.tileClass(
+        tileCoord,
+        goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY,
+        goog.isDef(tileUrl) ? tileUrl : '',
+        this.crossOrigin,
+        this.tileLoadFunction);
+    this.tileCache.set(tileCoordKey, tile);
+    return tile;
+  }
 };
 
 
 /**
- * @param {number} radius Radius size in pixel.
- * @param {number} blur Blur size in pixel.
- * @param {number} shadow Shadow offset size in pixel.
- * @return {string}
- * @private
+ * @return {ol.TileLoadFunctionType} TileLoadFunction
+ * @api
  */
-ol.layer.Heatmap.createCircle_ = function(radius, blur, shadow) {
-  var halfSize = radius + blur + 1;
-  var size = 2 * halfSize;
-  var context = ol.dom.createCanvasContext2D(size, size);
-  context.shadowOffsetX = context.shadowOffsetY = shadow;
-  context.shadowBlur = blur;
-  context.shadowColor = '#000';
-  context.beginPath();
-  var center = halfSize - shadow;
-  context.arc(center, center, radius, 0, Math.PI * 2, true);
-  context.fill();
-  return context.canvas.toDataURL();
+ol.source.TileImage.prototype.getTileLoadFunction = function() {
+  return this.tileLoadFunction;
 };
 
 
 /**
- * @return {Array.<string>} Colors.
+ * @return {ol.TileUrlFunctionType} TileUrlFunction
  * @api
- * @observable
  */
-ol.layer.Heatmap.prototype.getGradient = function() {
-  return /** @type {Array.<string>} */ (
-      this.get(ol.layer.HeatmapLayerProperty.GRADIENT));
+ol.source.TileImage.prototype.getTileUrlFunction = function() {
+  return this.tileUrlFunction;
 };
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getGradient',
-    ol.layer.Heatmap.prototype.getGradient);
 
 
 /**
- * @private
+ * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
+ * @api
  */
-ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
-  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
+ol.source.TileImage.prototype.setTileLoadFunction = function(tileLoadFunction) {
+  this.tileCache.clear();
+  this.tileLoadFunction = tileLoadFunction;
+  this.changed();
 };
 
 
 /**
- * @param {ol.render.Event} event Post compose event
- * @private
+ * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
+ * @api
  */
-ol.layer.Heatmap.prototype.handleRender_ = function(event) {
-  goog.asserts.assert(event.type == ol.render.EventType.RENDER);
-  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, offset;
-  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);
+ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) {
+  // FIXME It should be possible to be more intelligent and avoid clearing the
+  // FIXME cache.  The tile URL function would need to be incorporated into the
+  // FIXME cache key somehow.
+  this.tileCache.clear();
+  this.tileUrlFunction = tileUrlFunction;
+  this.changed();
 };
 
 
 /**
- * @param {Array.<string>} colors Gradient.
- * @api
- * @observable
+ * @inheritDoc
  */
-ol.layer.Heatmap.prototype.setGradient = function(colors) {
-  this.set(ol.layer.HeatmapLayerProperty.GRADIENT, colors);
+ol.source.TileImage.prototype.useTile = function(z, x, y) {
+  var tileCoordKey = this.getKeyZXY(z, x, y);
+  if (this.tileCache.containsKey(tileCoordKey)) {
+    this.tileCache.get(tileCoordKey);
+  }
 };
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setGradient',
-    ol.layer.Heatmap.prototype.setGradient);
 
-goog.provide('ol.loadingstrategy');
+goog.provide('ol.tilegrid.XYZ');
 
+goog.require('goog.math');
+goog.require('ol');
 goog.require('ol.TileCoord');
+goog.require('ol.TileRange');
+goog.require('ol.extent');
+goog.require('ol.extent.Corner');
+goog.require('ol.proj');
+goog.require('ol.proj.EPSG3857');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.TileGrid');
+
 
 
 /**
- * Strategy function for loading all features with a single request.
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @return {Array.<ol.Extent>} Extents.
+ * @classdesc
+ * Set the grid pattern for sources accessing XYZ tiled-image servers.
+ *
+ * @constructor
+ * @extends {ol.tilegrid.TileGrid}
+ * @param {olx.tilegrid.XYZOptions} options XYZ options.
+ * @struct
  * @api
  */
-ol.loadingstrategy.all = function(extent, resolution) {
-  return [[-Infinity, -Infinity, Infinity, Infinity]];
-};
+ol.tilegrid.XYZ = function(options) {
+  var extent = goog.isDef(options.extent) ?
+      options.extent : ol.proj.EPSG3857.EXTENT;
+  var resolutions = ol.tilegrid.resolutionsFromExtent(
+      extent, options.maxZoom, options.tileSize);
 
+  goog.base(this, {
+    minZoom: options.minZoom,
+    origin: ol.extent.getCorner(extent, ol.extent.Corner.TOP_LEFT),
+    resolutions: resolutions,
+    tileSize: options.tileSize
+  });
 
-/**
- * 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];
 };
+goog.inherits(ol.tilegrid.XYZ, ol.tilegrid.TileGrid);
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.loadingstrategy.createTile = function(tileGrid) {
+ol.tilegrid.XYZ.prototype.createTileCoordTransform = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
+  var minZ = this.minZoom;
+  var maxZ = this.maxZoom;
+  var wrapX = goog.isDef(options.wrapX) ? options.wrapX : true;
+  /** @type {Array.<ol.TileRange>} */
+  var tileRangeByZ = null;
+  if (goog.isDef(options.extent)) {
+    tileRangeByZ = new Array(maxZ + 1);
+    var z;
+    for (z = 0; z <= maxZ; ++z) {
+      if (z < minZ) {
+        tileRangeByZ[z] = null;
+      } else {
+        tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z);
+      }
+    }
+  }
   return (
       /**
-       * @param {ol.Extent} extent Extent.
-       * @param {number} resolution Resolution.
-       * @return {Array.<ol.Extent>} Extents.
+       * @param {ol.TileCoord} tileCoord Tile coordinate.
+       * @param {ol.proj.Projection} projection Projection.
+       * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate.
+       * @return {ol.TileCoord} Tile coordinate.
        */
-      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));
+      function(tileCoord, projection, opt_tileCoord) {
+        var z = tileCoord[0];
+        if (z < minZ || maxZ < z) {
+          return null;
+        }
+        var n = Math.pow(2, z);
+        var x = tileCoord[1];
+        if (wrapX) {
+          x = goog.math.modulo(x, n);
+        } else if (x < 0 || n <= x) {
+          return null;
+        }
+        var y = tileCoord[2];
+        if (y < -n || -1 < y) {
+          return null;
+        }
+        if (!goog.isNull(tileRangeByZ)) {
+          if (!tileRangeByZ[z].containsXY(x, y)) {
+            return null;
           }
         }
-        return extents;
+        return ol.tilecoord.createOrUpdate(z, x, -y - 1, opt_tileCoord);
       });
 };
 
-goog.provide('ol.Overlay');
-goog.provide('ol.OverlayPositioning');
-goog.provide('ol.OverlayProperty');
-
-goog.require('goog.asserts');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.style');
-goog.require('ol.Coordinate');
-goog.require('ol.Map');
-goog.require('ol.MapEventType');
-goog.require('ol.Object');
-
 
 /**
- * @enum {string}
+ * @inheritDoc
  */
-ol.OverlayProperty = {
-  ELEMENT: 'element',
-  MAP: 'map',
-  OFFSET: 'offset',
-  POSITION: 'position',
-  POSITIONING: 'positioning'
+ol.tilegrid.XYZ.prototype.getTileCoordChildTileRange =
+    function(tileCoord, opt_tileRange) {
+  if (tileCoord[0] < this.maxZoom) {
+    var doubleX = 2 * tileCoord[1];
+    var doubleY = 2 * tileCoord[2];
+    return ol.TileRange.createOrUpdate(
+        doubleX, doubleX + 1,
+        doubleY, doubleY + 1,
+        opt_tileRange);
+  } else {
+    return null;
+  }
 };
 
 
 /**
- * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
- * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
- * `'top-center'`, `'top-right'`
- * @enum {string}
- * @api stable
+ * @inheritDoc
  */
-ol.OverlayPositioning = {
-  BOTTOM_LEFT: 'bottom-left',
-  BOTTOM_CENTER: 'bottom-center',
-  BOTTOM_RIGHT: 'bottom-right',
-  CENTER_LEFT: 'center-left',
-  CENTER_CENTER: 'center-center',
-  CENTER_RIGHT: 'center-right',
-  TOP_LEFT: 'top-left',
-  TOP_CENTER: 'top-center',
-  TOP_RIGHT: 'top-right'
+ol.tilegrid.XYZ.prototype.forEachTileCoordParentTileRange =
+    function(tileCoord, callback, opt_this, opt_tileRange) {
+  var tileRange = ol.TileRange.createOrUpdate(
+      0, tileCoord[1], 0, tileCoord[2], opt_tileRange);
+  var z;
+  for (z = tileCoord[0] - 1; z >= this.minZoom; --z) {
+    tileRange.minX = tileRange.maxX >>= 1;
+    tileRange.minY = tileRange.maxY >>= 1;
+    if (callback.call(opt_this, z, tileRange)) {
+      return true;
+    }
+  }
+  return false;
 };
 
+goog.provide('ol.source.BingMaps');
+
+goog.require('goog.Uri');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.net.Jsonp');
+goog.require('ol');
+goog.require('ol.Attribution');
+goog.require('ol.TileRange');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.State');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.XYZ');
+
 
 
 /**
  * @classdesc
- * 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);
+ * Layer source for Bing Maps tile data.
  *
  * @constructor
- * @extends {ol.Object}
- * @param {olx.OverlayOptions} options Overlay options.
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.BingMapsOptions} options Bing Maps options.
  * @api stable
  */
-ol.Overlay = function(options) {
-
-  goog.base(this);
+ol.source.BingMaps = function(options) {
+
+  goog.base(this, {
+    crossOrigin: 'anonymous',
+    opaque: true,
+    projection: ol.proj.get('EPSG:3857'),
+    state: ol.source.State.LOADING,
+    tileLoadFunction: options.tileLoadFunction
+  });
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.culture_ = goog.isDef(options.culture) ? options.culture : 'en-us';
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxZoom_ = goog.isDef(options.maxZoom) ? options.maxZoom : -1;
+
+  var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
+  var uri = new goog.Uri(
+      protocol + '//dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
+      options.imagerySet);
+
+  var jsonp = new goog.net.Jsonp(uri, 'jsonp');
+  jsonp.send({
+    'include': 'ImageryProviders',
+    'uriScheme': ol.IS_HTTPS ? 'https' : 'http',
+    'key': options.key
+  }, goog.bind(this.handleImageryMetadataResponse, this));
+
+};
+goog.inherits(ol.source.BingMaps, ol.source.TileImage);
+
+
+/**
+ * @const
+ * @type {ol.Attribution}
+ * @api
+ */
+ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({
+  html: '<a class="ol-attribution-bing-tos" ' +
+      'href="http://www.microsoft.com/maps/product/terms.html">' +
+      'Terms of Use</a>'
+});
+
+
+/**
+ * @param {BingMapsImageryMetadataResponse} response Response.
+ */
+ol.source.BingMaps.prototype.handleImageryMetadataResponse =
+    function(response) {
+
+  if (response.statusCode != 200 ||
+      response.statusDescription != 'OK' ||
+      response.authenticationResultCode != 'ValidCredentials' ||
+      response.resourceSets.length != 1 ||
+      response.resourceSets[0].resources.length != 1) {
+    this.setState(ol.source.State.ERROR);
+    return;
+  }
+
+  var brandLogoUri = response.brandLogoUri;
+  //var copyright = response.copyright;  // FIXME do we need to display this?
+  var resource = response.resourceSets[0].resources[0];
+  goog.asserts.assert(resource.imageWidth == resource.imageHeight);
+  var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_;
+
+  var sourceProjection = this.getProjection();
+  var tileGrid = new ol.tilegrid.XYZ({
+    extent: ol.tilegrid.extentFromProjection(sourceProjection),
+    minZoom: resource.zoomMin,
+    maxZoom: maxZoom,
+    tileSize: resource.imageWidth
+  });
+  this.tileGrid = tileGrid;
+
+  var culture = this.culture_;
+  this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
+      tileGrid.createTileCoordTransform(),
+      ol.TileUrlFunction.createFromTileUrlFunctions(
+          goog.array.map(
+              resource.imageUrlSubdomains,
+              function(subdomain) {
+                var imageUrl = resource.imageUrl
+                    .replace('{subdomain}', subdomain)
+                    .replace('{culture}', culture);
+                return (
+                    /**
+                     * @param {ol.TileCoord} tileCoord Tile coordinate.
+                     * @param {number} pixelRatio Pixel ratio.
+                     * @param {ol.proj.Projection} projection Projection.
+                     * @return {string|undefined} Tile URL.
+                     */
+                    function(tileCoord, pixelRatio, projection) {
+                      goog.asserts.assert(ol.proj.equivalent(
+                          projection, sourceProjection));
+                      if (goog.isNull(tileCoord)) {
+                        return undefined;
+                      } else {
+                        return imageUrl.replace(
+                            '{quadkey}', ol.tilecoord.quadKey(tileCoord));
+                      }
+                    });
+              })));
+
+  if (resource.imageryProviders) {
+    var transform = ol.proj.getTransformFromProjections(
+        ol.proj.get('EPSG:4326'), this.getProjection());
+
+    var attributions = goog.array.map(
+        resource.imageryProviders,
+        function(imageryProvider) {
+          var html = imageryProvider.attribution;
+          /** @type {Object.<string, Array.<ol.TileRange>>} */
+          var tileRanges = {};
+          goog.array.forEach(
+              imageryProvider.coverageAreas,
+              function(coverageArea) {
+                var minZ = coverageArea.zoomMin;
+                var maxZ = Math.min(coverageArea.zoomMax, maxZoom);
+                var bbox = coverageArea.bbox;
+                var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]];
+                var extent = ol.extent.applyTransform(
+                    epsg4326Extent, transform);
+                var tileRange, z, zKey;
+                for (z = minZ; z <= maxZ; ++z) {
+                  zKey = z.toString();
+                  tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
+                  if (zKey in tileRanges) {
+                    tileRanges[zKey].push(tileRange);
+                  } else {
+                    tileRanges[zKey] = [tileRange];
+                  }
+                }
+              });
+          return new ol.Attribution({html: html, tileRanges: tileRanges});
+        });
+    attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION);
+    this.setAttributions(attributions);
+  }
+
+  this.setLogo(brandLogoUri);
+
+  this.setState(ol.source.State.READY);
+
+};
+
+// FIXME keep cluster cache by resolution ?
+// FIXME distance not respected because of the centroid
+
+goog.provide('ol.source.Cluster');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('ol.Feature');
+goog.require('ol.coordinate');
+goog.require('ol.extent');
+goog.require('ol.geom.Point');
+goog.require('ol.source.Vector');
+
+
+
+/**
+ * @constructor
+ * @param {olx.source.ClusterOptions} options
+ * @extends {ol.source.Vector}
+ * @api
+ */
+ol.source.Cluster = function(options) {
+  goog.base(this, {
+    attributions: options.attributions,
+    extent: options.extent,
+    logo: options.logo,
+    projection: options.projection
+  });
 
   /**
+   * @type {number|undefined}
    * @private
-   * @type {boolean}
    */
-  this.insertFirst_ = goog.isDef(options.insertFirst) ?
-      options.insertFirst : true;
+  this.resolution_ = undefined;
 
   /**
+   * @type {number}
    * @private
-   * @type {boolean}
    */
-  this.stopEvent_ = goog.isDef(options.stopEvent) ? options.stopEvent : true;
+  this.distance_ = goog.isDef(options.distance) ? options.distance : 20;
 
   /**
+   * @type {Array.<ol.Feature>}
    * @private
-   * @type {Element}
    */
-  this.element_ = goog.dom.createElement(goog.dom.TagName.DIV);
-  this.element_.style.position = 'absolute';
+  this.features_ = [];
 
   /**
+   * @type {ol.source.Vector}
    * @private
-   * @type {{bottom_: string,
-   *         left_: string,
-   *         right_: string,
-   *         top_: string,
-   *         visible: boolean}}
    */
-  this.rendered_ = {
-    bottom_: '',
-    left_: '',
-    right_: '',
-    top_: '',
-    visible: true
-  };
+  this.source_ = options.source;
 
-  /**
-   * @private
-   * @type {goog.events.Key}
-   */
-  this.mapPostrenderListenerKey_ = null;
+  this.source_.on(goog.events.EventType.CHANGE,
+      ol.source.Cluster.prototype.onSourceChange_, this);
+};
+goog.inherits(ol.source.Cluster, ol.source.Vector);
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.OverlayProperty.ELEMENT),
-      this.handleElementChanged, false, this);
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.OverlayProperty.MAP),
-      this.handleMapChanged, false, this);
+/**
+ * @inheritDoc
+ */
+ol.source.Cluster.prototype.loadFeatures = function(extent, resolution,
+    projection) {
+  if (resolution !== this.resolution_) {
+    this.clear();
+    this.resolution_ = resolution;
+    this.source_.loadFeatures(extent, resolution, projection);
+    this.cluster_();
+    this.addFeatures(this.features_);
+  }
+};
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.OverlayProperty.OFFSET),
-      this.handleOffsetChanged, false, this);
 
-  goog.events.listen(
-      this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITION),
-      this.handlePositionChanged, false, this);
+/**
+ * handle the source changing
+ * @private
+ */
+ol.source.Cluster.prototype.onSourceChange_ = function() {
+  this.clear();
+  this.cluster_();
+  this.addFeatures(this.features_);
+  this.changed();
+};
 
-  goog.events.listen(
-      this,
-      ol.Object.getChangeEventType(ol.OverlayProperty.POSITIONING),
-      this.handlePositioningChanged, false, this);
 
-  if (goog.isDef(options.element)) {
-    this.setElement(options.element);
+/**
+ * @private
+ */
+ol.source.Cluster.prototype.cluster_ = function() {
+  if (!goog.isDef(this.resolution_)) {
+    return;
   }
+  goog.array.clear(this.features_);
+  var extent = ol.extent.createEmpty();
+  var mapDistance = this.distance_ * this.resolution_;
+  var features = this.source_.getFeatures();
 
-  this.setOffset(goog.isDef(options.offset) ? options.offset : [0, 0]);
+  /**
+   * @type {Object.<string, boolean>}
+   */
+  var clustered = {};
 
-  this.setPositioning(goog.isDef(options.positioning) ?
-      /** @type {ol.OverlayPositioning} */ (options.positioning) :
-      ol.OverlayPositioning.TOP_LEFT);
+  for (var i = 0, ii = features.length; i < ii; i++) {
+    var feature = features[i];
+    if (!goog.object.containsKey(clustered, goog.getUid(feature).toString())) {
+      var geometry = feature.getGeometry();
+      goog.asserts.assert(geometry instanceof ol.geom.Point);
+      var coordinates = geometry.getCoordinates();
+      ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
+      ol.extent.buffer(extent, mapDistance, extent);
 
-  if (goog.isDef(options.position)) {
-    this.setPosition(options.position);
+      var neighbors = this.source_.getFeaturesInExtent(extent);
+      goog.asserts.assert(neighbors.length >= 1);
+      neighbors = goog.array.filter(neighbors, function(neighbor) {
+        var uid = goog.getUid(neighbor).toString();
+        if (!goog.object.containsKey(clustered, uid)) {
+          goog.object.set(clustered, uid, true);
+          return true;
+        } else {
+          return false;
+        }
+      });
+      this.features_.push(this.createCluster_(neighbors));
+    }
   }
-
+  goog.asserts.assert(
+      goog.object.getCount(clustered) == this.source_.getFeatures().length);
 };
-goog.inherits(ol.Overlay, ol.Object);
 
 
 /**
- * Get the DOM element of this overlay.
- * @return {Element|undefined} The Element containing the overlay.
- * @observable
- * @api stable
+ * @param {Array.<ol.Feature>} features Features
+ * @return {ol.Feature}
+ * @private
  */
-ol.Overlay.prototype.getElement = function() {
-  return /** @type {Element|undefined} */ (
-      this.get(ol.OverlayProperty.ELEMENT));
+ol.source.Cluster.prototype.createCluster_ = function(features) {
+  var length = features.length;
+  var centroid = [0, 0];
+  for (var i = 0; i < length; i++) {
+    var geometry = features[i].getGeometry();
+    goog.asserts.assert(geometry instanceof ol.geom.Point);
+    var coordinates = geometry.getCoordinates();
+    ol.coordinate.add(centroid, coordinates);
+  }
+  ol.coordinate.scale(centroid, 1 / length);
+
+  var cluster = new ol.Feature(new ol.geom.Point(centroid));
+  cluster.set('features', features);
+  return cluster;
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getElement',
-    ol.Overlay.prototype.getElement);
 
+// 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.
 
 /**
- * Get the map associated with this overlay.
- * @return {ol.Map|undefined} The map that the overlay is part of.
- * @observable
- * @api stable
+ * @fileoverview Common events for the network classes.
  */
-ol.Overlay.prototype.getMap = function() {
-  return /** @type {ol.Map|undefined} */ (
-      this.get(ol.OverlayProperty.MAP));
-};
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getMap',
-    ol.Overlay.prototype.getMap);
+
+
+goog.provide('goog.net.EventType');
 
 
 /**
- * Get the offset of this overlay.
- * @return {Array.<number>} The offset.
- * @observable
- * @api stable
+ * Event names for network events
+ * @enum {string}
  */
-ol.Overlay.prototype.getOffset = function() {
-  return /** @type {Array.<number>} */ (
-      this.get(ol.OverlayProperty.OFFSET));
+goog.net.EventType = {
+  COMPLETE: 'complete',
+  SUCCESS: 'success',
+  ERROR: 'error',
+  ABORT: 'abort',
+  READY: 'ready',
+  READY_STATE_CHANGE: 'readystatechange',
+  TIMEOUT: 'timeout',
+  INCREMENTAL_DATA: 'incrementaldata',
+  PROGRESS: 'progress'
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getOffset',
-    ol.Overlay.prototype.getOffset);
 
+// 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.
 
 /**
- * Get the current position of this overlay.
- * @return {ol.Coordinate|undefined} The spatial point that the overlay is
- *     anchored at.
- * @observable
- * @api stable
+ * @fileoverview A timer class to which other classes and objects can
+ * listen on.  This is only an abstraction above setInterval.
+ *
+ * @see ../demos/timers.html
  */
-ol.Overlay.prototype.getPosition = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.OverlayProperty.POSITION));
-};
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getPosition',
-    ol.Overlay.prototype.getPosition);
+
+goog.provide('goog.Timer');
+
+goog.require('goog.Promise');
+goog.require('goog.events.EventTarget');
+
 
 
 /**
- * Get the current positioning of this overlay.
- * @return {ol.OverlayPositioning} How the overlay is positioned
- *     relative to its point on the map.
- * @observable
- * @api stable
+ * Class for handling timing events.
+ *
+ * @param {number=} opt_interval Number of ms between ticks (Default: 1ms).
+ * @param {Object=} opt_timerObject  An object that has setTimeout, setInterval,
+ *     clearTimeout and clearInterval (eg Window).
+ * @constructor
+ * @extends {goog.events.EventTarget}
  */
-ol.Overlay.prototype.getPositioning = function() {
-  return /** @type {ol.OverlayPositioning} */ (
-      this.get(ol.OverlayProperty.POSITIONING));
+goog.Timer = function(opt_interval, opt_timerObject) {
+  goog.events.EventTarget.call(this);
+
+  /**
+   * Number of ms between ticks
+   * @type {number}
+   * @private
+   */
+  this.interval_ = opt_interval || 1;
+
+  /**
+   * An object that implements setTimeout, setInterval, clearTimeout and
+   * clearInterval. We default to the window object. Changing this on
+   * goog.Timer.prototype changes the object for all timer instances which can
+   * be useful if your environment has some other implementation of timers than
+   * the window object.
+   * @type {Object}
+   * @private
+   */
+  this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject;
+
+  /**
+   * Cached tick_ bound to the object for later use in the timer.
+   * @type {Function}
+   * @private
+   */
+  this.boundTick_ = goog.bind(this.tick_, this);
+
+  /**
+   * Firefox browser often fires the timer event sooner
+   * (sometimes MUCH sooner) than the requested timeout. So we
+   * compare the time to when the event was last fired, and
+   * reschedule if appropriate. See also goog.Timer.intervalScale
+   * @type {number}
+   * @private
+   */
+  this.last_ = goog.now();
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getPositioning',
-    ol.Overlay.prototype.getPositioning);
+goog.inherits(goog.Timer, goog.events.EventTarget);
 
 
 /**
- * @protected
+ * Maximum timeout value.
+ *
+ * Timeout values too big to fit into a signed 32-bit integer may cause
+ * overflow in FF, Safari, and Chrome, resulting in the timeout being
+ * scheduled immediately.  It makes more sense simply not to schedule these
+ * timeouts, since 24.8 days is beyond a reasonable expectation for the
+ * browser to stay open.
+ *
+ * @type {number}
+ * @private
  */
-ol.Overlay.prototype.handleElementChanged = function() {
-  goog.dom.removeChildren(this.element_);
-  var element = this.getElement();
-  if (goog.isDefAndNotNull(element)) {
-    goog.dom.append(/** @type {!Node} */ (this.element_), element);
-  }
-};
+goog.Timer.MAX_TIMEOUT_ = 2147483647;
 
 
 /**
- * @protected
+ * A timer ID that cannot be returned by any known implmentation of
+ * Window.setTimeout.  Passing this value to window.clearTimeout should
+ * therefore be a no-op.
+ *
+ * @const {number}
+ * @private
  */
-ol.Overlay.prototype.handleMapChanged = function() {
-  if (!goog.isNull(this.mapPostrenderListenerKey_)) {
-    goog.dom.removeNode(this.element_);
-    goog.events.unlistenByKey(this.mapPostrenderListenerKey_);
-    this.mapPostrenderListenerKey_ = null;
-  }
-  var map = this.getMap();
-  if (goog.isDefAndNotNull(map)) {
-    this.mapPostrenderListenerKey_ = goog.events.listen(map,
-        ol.MapEventType.POSTRENDER, this.handleMapPostrender, false, this);
-    this.updatePixelPosition_();
-    var container = this.stopEvent_ ?
-        map.getOverlayContainerStopEvent() : map.getOverlayContainer();
-    if (this.insertFirst_) {
-      goog.dom.insertChildAt(/** @type {!Element} */ (
-          container), this.element_, 0);
-    } else {
-      goog.dom.append(/** @type {!Node} */ (container), this.element_);
-    }
-  }
-};
+goog.Timer.INVALID_TIMEOUT_ID_ = -1;
 
 
 /**
- * @protected
+ * Whether this timer is enabled
+ * @type {boolean}
  */
-ol.Overlay.prototype.handleMapPostrender = function() {
-  this.updatePixelPosition_();
-};
+goog.Timer.prototype.enabled = false;
 
 
 /**
- * @protected
+ * An object that implements setTimout, setInterval, clearTimeout and
+ * clearInterval. We default to the global object. Changing
+ * goog.Timer.defaultTimerObject changes the object for all timer instances
+ * which can be useful if your environment has some other implementation of
+ * timers you'd like to use.
+ * @type {Object}
  */
-ol.Overlay.prototype.handleOffsetChanged = function() {
-  this.updatePixelPosition_();
-};
+goog.Timer.defaultTimerObject = goog.global;
 
 
 /**
- * @protected
+ * A variable that controls the timer error correction. If the
+ * timer is called before the requested interval times
+ * intervalScale, which often happens on mozilla, the timer is
+ * rescheduled. See also this.last_
+ * @type {number}
  */
-ol.Overlay.prototype.handlePositionChanged = function() {
-  this.updatePixelPosition_();
-};
+goog.Timer.intervalScale = 0.8;
 
 
 /**
- * @protected
+ * Variable for storing the result of setInterval
+ * @type {?number}
+ * @private
  */
-ol.Overlay.prototype.handlePositioningChanged = function() {
-  this.updatePixelPosition_();
+goog.Timer.prototype.timer_ = null;
+
+
+/**
+ * Gets the interval of the timer.
+ * @return {number} interval Number of ms between ticks.
+ */
+goog.Timer.prototype.getInterval = function() {
+  return this.interval_;
 };
 
 
 /**
- * Set the DOM element to be associated with this overlay.
- * @param {Element|undefined} element The Element containing the overlay.
- * @observable
- * @api stable
+ * Sets the interval of the timer.
+ * @param {number} interval Number of ms between ticks.
  */
-ol.Overlay.prototype.setElement = function(element) {
-  this.set(ol.OverlayProperty.ELEMENT, element);
+goog.Timer.prototype.setInterval = function(interval) {
+  this.interval_ = interval;
+  if (this.timer_ && this.enabled) {
+    // Stop and then start the timer to reset the interval.
+    this.stop();
+    this.start();
+  } else if (this.timer_) {
+    this.stop();
+  }
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setElement',
-    ol.Overlay.prototype.setElement);
 
 
 /**
- * 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
+ * Callback for the setTimeout used by the timer
+ * @private
  */
-ol.Overlay.prototype.setMap = function(map) {
-  this.set(ol.OverlayProperty.MAP, map);
+goog.Timer.prototype.tick_ = function() {
+  if (this.enabled) {
+    var elapsed = goog.now() - this.last_;
+    if (elapsed > 0 &&
+        elapsed < this.interval_ * goog.Timer.intervalScale) {
+      this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
+          this.interval_ - elapsed);
+      return;
+    }
+
+    // Prevents setInterval from registering a duplicate timeout when called
+    // in the timer event handler.
+    if (this.timer_) {
+      this.timerObject_.clearTimeout(this.timer_);
+      this.timer_ = null;
+    }
+
+    this.dispatchTick();
+    // The timer could be stopped in the timer event handler.
+    if (this.enabled) {
+      this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
+          this.interval_);
+      this.last_ = goog.now();
+    }
+  }
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setMap',
-    ol.Overlay.prototype.setMap);
 
 
 /**
- * Set the offset for this overlay.
- * @param {Array.<number>} offset Offset.
- * @observable
- * @api stable
+ * Dispatches the TICK event. This is its own method so subclasses can override.
  */
-ol.Overlay.prototype.setOffset = function(offset) {
-  this.set(ol.OverlayProperty.OFFSET, offset);
+goog.Timer.prototype.dispatchTick = function() {
+  this.dispatchEvent(goog.Timer.TICK);
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setOffset',
-    ol.Overlay.prototype.setOffset);
 
 
 /**
- * Set the position for this overlay.
- * @param {ol.Coordinate|undefined} position The spatial point that the overlay
- *     is anchored at.
- * @observable
- * @api stable
+ * Starts the timer.
  */
-ol.Overlay.prototype.setPosition = function(position) {
-  this.set(ol.OverlayProperty.POSITION, position);
+goog.Timer.prototype.start = function() {
+  this.enabled = true;
+
+  // If there is no interval already registered, start it now
+  if (!this.timer_) {
+    // IMPORTANT!
+    // window.setInterval in FireFox has a bug - it fires based on
+    // absolute time, rather than on relative time. What this means
+    // is that if a computer is sleeping/hibernating for 24 hours
+    // and the timer interval was configured to fire every 1000ms,
+    // then after the PC wakes up the timer will fire, in rapid
+    // succession, 3600*24 times.
+    // This bug is described here and is already fixed, but it will
+    // take time to propagate, so for now I am switching this over
+    // to setTimeout logic.
+    //     https://bugzilla.mozilla.org/show_bug.cgi?id=376643
+    //
+    this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
+        this.interval_);
+    this.last_ = goog.now();
+  }
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setPosition',
-    ol.Overlay.prototype.setPosition);
 
 
 /**
- * Set the positioning for this overlay.
- * @param {ol.OverlayPositioning} positioning how the overlay is
- *     positioned relative to its point on the map.
- * @observable
- * @api stable
+ * Stops the timer.
  */
-ol.Overlay.prototype.setPositioning = function(positioning) {
-  this.set(ol.OverlayProperty.POSITIONING, positioning);
+goog.Timer.prototype.stop = function() {
+  this.enabled = false;
+  if (this.timer_) {
+    this.timerObject_.clearTimeout(this.timer_);
+    this.timer_ = null;
+  }
+};
+
+
+/** @override */
+goog.Timer.prototype.disposeInternal = function() {
+  goog.Timer.superClass_.disposeInternal.call(this);
+  this.stop();
+  delete this.timerObject_;
 };
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setPositioning',
-    ol.Overlay.prototype.setPositioning);
 
 
 /**
- * @private
+ * Constant for the timer's event type
+ * @type {string}
  */
-ol.Overlay.prototype.updatePixelPosition_ = function() {
-
-  var map = this.getMap();
-  var position = this.getPosition();
-  if (!goog.isDef(map) || !map.isRendered() || !goog.isDef(position)) {
-    if (this.rendered_.visible) {
-      goog.style.setElementShown(this.element_, false);
-      this.rendered_.visible = false;
-    }
-    return;
-  }
+goog.Timer.TICK = 'tick';
 
-  var pixel = map.getPixelFromCoordinate(position);
-  goog.asserts.assert(!goog.isNull(pixel));
-  var mapSize = map.getSize();
-  goog.asserts.assert(goog.isDef(mapSize));
-  var style = this.element_.style;
-  var offset = this.getOffset();
-  goog.asserts.assert(goog.isArray(offset));
-  var positioning = this.getPositioning();
-  goog.asserts.assert(goog.isDef(positioning));
 
-  var offsetX = offset[0];
-  var offsetY = offset[1];
-  if (positioning == ol.OverlayPositioning.BOTTOM_RIGHT ||
-      positioning == ol.OverlayPositioning.CENTER_RIGHT ||
-      positioning == ol.OverlayPositioning.TOP_RIGHT) {
-    if (this.rendered_.left_ !== '') {
-      this.rendered_.left_ = style.left = '';
-    }
-    var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px';
-    if (this.rendered_.right_ != right) {
-      this.rendered_.right_ = style.right = right;
+/**
+ * Calls the given function once, after the optional pause.
+ *
+ * The function is always called asynchronously, even if the delay is 0. This
+ * is a common trick to schedule a function to run after a batch of browser
+ * event processing.
+ *
+ * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function
+ *     or object that has a handleEvent method.
+ * @param {number=} opt_delay Milliseconds to wait; default is 0.
+ * @param {SCOPE=} opt_handler Object in whose scope to call the listener.
+ * @return {number} A handle to the timer ID.
+ * @template SCOPE
+ */
+goog.Timer.callOnce = function(listener, opt_delay, opt_handler) {
+  if (goog.isFunction(listener)) {
+    if (opt_handler) {
+      listener = goog.bind(listener, opt_handler);
     }
+  } else if (listener && typeof listener.handleEvent == 'function') {
+    // using typeof to prevent strict js warning
+    listener = goog.bind(listener.handleEvent, listener);
   } else {
-    if (this.rendered_.right_ !== '') {
-      this.rendered_.right_ = style.right = '';
-    }
-    if (positioning == ol.OverlayPositioning.BOTTOM_CENTER ||
-        positioning == ol.OverlayPositioning.CENTER_CENTER ||
-        positioning == ol.OverlayPositioning.TOP_CENTER) {
-      offsetX -= goog.style.getSize(this.element_).width / 2;
-    }
-    var left = Math.round(pixel[0] + offsetX) + 'px';
-    if (this.rendered_.left_ != left) {
-      this.rendered_.left_ = style.left = left;
-    }
+    throw Error('Invalid listener argument');
   }
-  if (positioning == ol.OverlayPositioning.BOTTOM_LEFT ||
-      positioning == ol.OverlayPositioning.BOTTOM_CENTER ||
-      positioning == ol.OverlayPositioning.BOTTOM_RIGHT) {
-    if (this.rendered_.top_ !== '') {
-      this.rendered_.top_ = style.top = '';
-    }
-    var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px';
-    if (this.rendered_.bottom_ != bottom) {
-      this.rendered_.bottom_ = style.bottom = bottom;
-    }
+
+  if (opt_delay > goog.Timer.MAX_TIMEOUT_) {
+    // Timeouts greater than MAX_INT return immediately due to integer
+    // overflow in many browsers.  Since MAX_INT is 24.8 days, just don't
+    // schedule anything at all.
+    return goog.Timer.INVALID_TIMEOUT_ID_;
   } else {
-    if (this.rendered_.bottom_ !== '') {
-      this.rendered_.bottom_ = style.bottom = '';
-    }
-    if (positioning == ol.OverlayPositioning.CENTER_LEFT ||
-        positioning == ol.OverlayPositioning.CENTER_CENTER ||
-        positioning == ol.OverlayPositioning.CENTER_RIGHT) {
-      offsetY -= goog.style.getSize(this.element_).height / 2;
-    }
-    var top = Math.round(pixel[1] + offsetY) + 'px';
-    if (this.rendered_.top_ != top) {
-      this.rendered_.top_ = style.top = top;
-    }
+    return goog.Timer.defaultTimerObject.setTimeout(
+        listener, opt_delay || 0);
   }
+};
+
+
+/**
+ * Clears a timeout initiated by callOnce
+ * @param {?number} timerId a timer ID.
+ */
+goog.Timer.clear = function(timerId) {
+  goog.Timer.defaultTimerObject.clearTimeout(timerId);
+};
 
-  if (!this.rendered_.visible) {
-    goog.style.setElementShown(this.element_, true);
-    this.rendered_.visible = true;
-  }
 
+/**
+ * @param {number} delay Milliseconds to wait.
+ * @param {(RESULT|goog.Thenable<RESULT>|Thenable)=} opt_result The value
+ *     with which the promise will be resolved.
+ * @return {!goog.Promise<RESULT>} A promise that will be resolved after
+ *     the specified delay, unless it is canceled first.
+ * @template RESULT
+ */
+goog.Timer.promise = function(delay, opt_result) {
+  var timerKey = null;
+  return new goog.Promise(function(resolve, reject) {
+    timerKey = goog.Timer.callOnce(function() {
+      resolve(opt_result);
+    }, delay);
+    if (timerKey == goog.Timer.INVALID_TIMEOUT_ID_) {
+      reject(new Error('Failed to schedule timer.'));
+    }
+  }).thenCatch(function(error) {
+    // Clear the timer. The most likely reason is "cancel" signal.
+    goog.Timer.clear(timerKey);
+    throw error;
+  });
 };
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -91867,360 +105447,240 @@ ol.Overlay.prototype.updatePixelPosition_ = function() {
 // limitations under the License.
 
 /**
- * @fileoverview A utility to load JavaScript files via DOM script tags.
- * Refactored from goog.net.Jsonp. Works cross-domain.
- *
+ * @fileoverview Error codes shared between goog.net.IframeIo and
+ * goog.net.XhrIo.
  */
 
-goog.provide('goog.net.jsloader');
-goog.provide('goog.net.jsloader.Error');
-goog.provide('goog.net.jsloader.ErrorCode');
-goog.provide('goog.net.jsloader.Options');
-
-goog.require('goog.array');
-goog.require('goog.async.Deferred');
-goog.require('goog.debug.Error');
-goog.require('goog.dom');
-goog.require('goog.dom.TagName');
+goog.provide('goog.net.ErrorCode');
 
 
 /**
- * The name of the property of goog.global under which the JavaScript
- * verification object is stored by the loaded script.
- * @type {string}
- * @private
+ * Error codes
+ * @enum {number}
  */
-goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
+goog.net.ErrorCode = {
 
+  /**
+   * There is no error condition.
+   */
+  NO_ERROR: 0,
 
-/**
- * The default length of time, in milliseconds, we are prepared to wait for a
- * load request to complete.
- * @type {number}
- */
-goog.net.jsloader.DEFAULT_TIMEOUT = 5000;
+  /**
+   * The most common error from iframeio, unfortunately, is that the browser
+   * responded with an error page that is classed as a different domain. The
+   * situations, are when a browser error page  is shown -- 404, access denied,
+   * DNS failure, connection reset etc.)
+   *
+   */
+  ACCESS_DENIED: 1,
 
+  /**
+   * Currently the only case where file not found will be caused is when the
+   * code is running on the local file system and a non-IE browser makes a
+   * request to a file that doesn't exist.
+   */
+  FILE_NOT_FOUND: 2,
 
-/**
- * Optional parameters for goog.net.jsloader.send.
- * timeout: The length of time, in milliseconds, we are prepared to wait
- *     for a load request to complete. Default it 5 seconds.
- * document: The HTML document under which to load the JavaScript. Default is
- *     the current document.
- * cleanupWhenDone: If true clean up the script tag after script completes to
- *     load. This is important if you just want to read data from the JavaScript
- *     and then throw it away. Default is false.
- *
- * @typedef {{
- *   timeout: (number|undefined),
- *   document: (HTMLDocument|undefined),
- *   cleanupWhenDone: (boolean|undefined)
- * }}
- */
-goog.net.jsloader.Options;
+  /**
+   * If Firefox shows a browser error page, such as a connection reset by
+   * server or access denied, then it will fail silently without the error or
+   * load handlers firing.
+   */
+  FF_SILENT_ERROR: 3,
 
+  /**
+   * Custom error provided by the client through the error check hook.
+   */
+  CUSTOM_ERROR: 4,
 
-/**
- * Scripts (URIs) waiting to be loaded.
- * @type {Array.<string>}
- * @private
- */
-goog.net.jsloader.scriptsToLoad_ = [];
+  /**
+   * Exception was thrown while processing the request.
+   */
+  EXCEPTION: 5,
 
+  /**
+   * The Http response returned a non-successful http status code.
+   */
+  HTTP_ERROR: 6,
 
-/**
- * Loads and evaluates the JavaScript files at the specified URIs, guaranteeing
- * the order of script loads.
- *
- * Because we have to load the scripts in serial (load script 1, exec script 1,
- * load script 2, exec script 2, and so on), this will be slower than doing
- * the network fetches in parallel.
- *
- * If you need to load a large number of scripts but dependency order doesn't
- * matter, you should just call goog.net.jsloader.load N times.
- *
- * If you need to load a large number of scripts on the same domain,
- * you may want to use goog.module.ModuleLoader.
- *
- * @param {Array.<string>} uris The URIs to load.
- * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
- *     goog.net.jsloader.options documentation for details.
- */
-goog.net.jsloader.loadMany = function(uris, opt_options) {
-  // Loading the scripts in serial introduces asynchronosity into the flow.
-  // Therefore, there are race conditions where client A can kick off the load
-  // sequence for client B, even though client A's scripts haven't all been
-  // loaded yet.
-  //
-  // To work around this issue, all module loads share a queue.
-  if (!uris.length) {
-    return;
-  }
+  /**
+   * The request was aborted.
+   */
+  ABORT: 7,
 
-  var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length;
-  goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris);
-  if (isAnotherModuleLoading) {
-    // jsloader is still loading some other scripts.
-    // In order to prevent the race condition noted above, we just add
-    // these URIs to the end of the scripts' queue and return.
-    return;
-  }
+  /**
+   * The request timed out.
+   */
+  TIMEOUT: 8,
 
-  uris = goog.net.jsloader.scriptsToLoad_;
-  var popAndLoadNextScript = function() {
-    var uri = uris.shift();
-    var deferred = goog.net.jsloader.load(uri, opt_options);
-    if (uris.length) {
-      deferred.addBoth(popAndLoadNextScript);
-    }
-  };
-  popAndLoadNextScript();
+  /**
+   * The resource is not available offline.
+   */
+  OFFLINE: 9
 };
 
 
 /**
- * Loads and evaluates a JavaScript file.
- * When the script loads, a user callback is called.
- * It is the client's responsibility to verify that the script ran successfully.
- *
- * @param {string} uri The URI of the JavaScript.
- * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
- *     goog.net.jsloader.Options documentation for details.
- * @return {!goog.async.Deferred} The deferred result, that may be used to add
- *     callbacks and/or cancel the transmission.
- *     The error callback will be called with a single goog.net.jsloader.Error
- *     parameter.
+ * Returns a friendly error message for an error code. These messages are for
+ * debugging and are not localized.
+ * @param {goog.net.ErrorCode} errorCode An error code.
+ * @return {string} A message for debugging.
  */
-goog.net.jsloader.load = function(uri, opt_options) {
-  var options = opt_options || {};
-  var doc = options.document || document;
-
-  var script = goog.dom.createElement(goog.dom.TagName.SCRIPT);
-  var request = {script_: script, timeout_: undefined};
-  var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request);
-
-  // Set a timeout.
-  var timeout = null;
-  var timeoutDuration = goog.isDefAndNotNull(options.timeout) ?
-      options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT;
-  if (timeoutDuration > 0) {
-    timeout = window.setTimeout(function() {
-      goog.net.jsloader.cleanup_(script, true);
-      deferred.errback(new goog.net.jsloader.Error(
-          goog.net.jsloader.ErrorCode.TIMEOUT,
-          'Timeout reached for loading script ' + uri));
-    }, timeoutDuration);
-    request.timeout_ = timeout;
-  }
-
-  // Hang the user callback to be called when the script completes to load.
-  // NOTE(user): This callback will be called in IE even upon error. In any
-  // case it is the client's responsibility to verify that the script ran
-  // successfully.
-  script.onload = script.onreadystatechange = function() {
-    if (!script.readyState || script.readyState == 'loaded' ||
-        script.readyState == 'complete') {
-      var removeScriptNode = options.cleanupWhenDone || false;
-      goog.net.jsloader.cleanup_(script, removeScriptNode, timeout);
-      deferred.callback(null);
-    }
-  };
-
-  // Add an error callback.
-  // NOTE(user): Not supported in IE.
-  script.onerror = function() {
-    goog.net.jsloader.cleanup_(script, true, timeout);
-    deferred.errback(new goog.net.jsloader.Error(
-        goog.net.jsloader.ErrorCode.LOAD_ERROR,
-        'Error while loading script ' + uri));
-  };
-
-  // Add the script element to the document.
-  goog.dom.setProperties(script, {
-    'type': 'text/javascript',
-    'charset': 'UTF-8',
-    // NOTE(user): Safari never loads the script if we don't set
-    // the src attribute before appending.
-    'src': uri
-  });
-  var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);
-  scriptParent.appendChild(script);
-
-  return deferred;
-};
+goog.net.ErrorCode.getDebugMessage = function(errorCode) {
+  switch (errorCode) {
+    case goog.net.ErrorCode.NO_ERROR:
+      return 'No Error';
 
+    case goog.net.ErrorCode.ACCESS_DENIED:
+      return 'Access denied to content document';
 
-/**
- * Loads a JavaScript file and verifies it was evaluated successfully, using a
- * verification object.
- * The verification object is set by the loaded JavaScript at the end of the
- * script.
- * We verify this object was set and return its value in the success callback.
- * If the object is not defined we trigger an error callback.
- *
- * @param {string} uri The URI of the JavaScript.
- * @param {string} verificationObjName The name of the verification object that
- *     the loaded script should set.
- * @param {goog.net.jsloader.Options} options Optional parameters. See
- *     goog.net.jsloader.Options documentation for details.
- * @return {!goog.async.Deferred} The deferred result, that may be used to add
- *     callbacks and/or cancel the transmission.
- *     The success callback will be called with a single parameter containing
- *     the value of the verification object.
- *     The error callback will be called with a single goog.net.jsloader.Error
- *     parameter.
- */
-goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) {
-  // Define the global objects variable.
-  if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) {
-    goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {};
-  }
-  var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_];
+    case goog.net.ErrorCode.FILE_NOT_FOUND:
+      return 'File not found';
 
-  // Verify that the expected object does not exist yet.
-  if (goog.isDef(verifyObjs[verificationObjName])) {
-    // TODO(user): Error or reset variable?
-    return goog.async.Deferred.fail(new goog.net.jsloader.Error(
-        goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS,
-        'Verification object ' + verificationObjName + ' already defined.'));
-  }
+    case goog.net.ErrorCode.FF_SILENT_ERROR:
+      return 'Firefox silently errored';
 
-  // Send request to load the JavaScript.
-  var sendDeferred = goog.net.jsloader.load(uri, options);
+    case goog.net.ErrorCode.CUSTOM_ERROR:
+      return 'Application custom error';
 
-  // Create a deferred object wrapping the send result.
-  var deferred = new goog.async.Deferred(
-      goog.bind(sendDeferred.cancel, sendDeferred));
+    case goog.net.ErrorCode.EXCEPTION:
+      return 'An exception occurred';
 
-  // Call user back with object that was set by the script.
-  sendDeferred.addCallback(function() {
-    var result = verifyObjs[verificationObjName];
-    if (goog.isDef(result)) {
-      deferred.callback(result);
-      delete verifyObjs[verificationObjName];
-    } else {
-      // Error: script was not loaded properly.
-      deferred.errback(new goog.net.jsloader.Error(
-          goog.net.jsloader.ErrorCode.VERIFY_ERROR,
-          'Script ' + uri + ' loaded, but verification object ' +
-          verificationObjName + ' was not defined.'));
-    }
-  });
+    case goog.net.ErrorCode.HTTP_ERROR:
+      return 'Http response at 400 or 500 level';
 
-  // Pass error to new deferred object.
-  sendDeferred.addErrback(function(error) {
-    if (goog.isDef(verifyObjs[verificationObjName])) {
-      delete verifyObjs[verificationObjName];
-    }
-    deferred.errback(error);
-  });
+    case goog.net.ErrorCode.ABORT:
+      return 'Request was aborted';
 
-  return deferred;
-};
+    case goog.net.ErrorCode.TIMEOUT:
+      return 'Request timed out';
 
+    case goog.net.ErrorCode.OFFLINE:
+      return 'The resource is not available offline';
 
-/**
- * Gets the DOM element under which we should add new script elements.
- * How? Take the first head element, and if not found take doc.documentElement,
- * which always exists.
- *
- * @param {!HTMLDocument} doc The relevant document.
- * @return {!Element} The script parent element.
- * @private
- */
-goog.net.jsloader.getScriptParentElement_ = function(doc) {
-  var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD);
-  if (!headElements || goog.array.isEmpty(headElements)) {
-    return doc.documentElement;
-  } else {
-    return headElements[0];
+    default:
+      return 'Unrecognized error code';
   }
 };
 
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 /**
- * Cancels a given request.
- * @this {{script_: Element, timeout_: number}} The request context.
- * @private
+ * @fileoverview Constants for HTTP status codes.
  */
-goog.net.jsloader.cancel_ = function() {
-  var request = this;
-  if (request && request.script_) {
-    var scriptNode = request.script_;
-    if (scriptNode && scriptNode.tagName == 'SCRIPT') {
-      goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);
-    }
-  }
-};
+
+goog.provide('goog.net.HttpStatus');
 
 
 /**
- * Removes the script node and the timeout.
- *
- * @param {Node} scriptNode The node to be cleaned up.
- * @param {boolean} removeScriptNode If true completely remove the script node.
- * @param {?number=} opt_timeout The timeout handler to cleanup.
- * @private
+ * HTTP Status Codes defined in RFC 2616 and RFC 6585.
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ * @see http://tools.ietf.org/html/rfc6585
+ * @enum {number}
  */
-goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode,
-                                      opt_timeout) {
-  if (goog.isDefAndNotNull(opt_timeout)) {
-    goog.global.clearTimeout(opt_timeout);
-  }
+goog.net.HttpStatus = {
+  // Informational 1xx
+  CONTINUE: 100,
+  SWITCHING_PROTOCOLS: 101,
 
-  scriptNode.onload = goog.nullFunction;
-  scriptNode.onerror = goog.nullFunction;
-  scriptNode.onreadystatechange = goog.nullFunction;
+  // Successful 2xx
+  OK: 200,
+  CREATED: 201,
+  ACCEPTED: 202,
+  NON_AUTHORITATIVE_INFORMATION: 203,
+  NO_CONTENT: 204,
+  RESET_CONTENT: 205,
+  PARTIAL_CONTENT: 206,
 
-  // Do this after a delay (removing the script node of a running script can
-  // confuse older IEs).
-  if (removeScriptNode) {
-    window.setTimeout(function() {
-      goog.dom.removeNode(scriptNode);
-    }, 0);
-  }
-};
+  // Redirection 3xx
+  MULTIPLE_CHOICES: 300,
+  MOVED_PERMANENTLY: 301,
+  FOUND: 302,
+  SEE_OTHER: 303,
+  NOT_MODIFIED: 304,
+  USE_PROXY: 305,
+  TEMPORARY_REDIRECT: 307,
+
+  // Client Error 4xx
+  BAD_REQUEST: 400,
+  UNAUTHORIZED: 401,
+  PAYMENT_REQUIRED: 402,
+  FORBIDDEN: 403,
+  NOT_FOUND: 404,
+  METHOD_NOT_ALLOWED: 405,
+  NOT_ACCEPTABLE: 406,
+  PROXY_AUTHENTICATION_REQUIRED: 407,
+  REQUEST_TIMEOUT: 408,
+  CONFLICT: 409,
+  GONE: 410,
+  LENGTH_REQUIRED: 411,
+  PRECONDITION_FAILED: 412,
+  REQUEST_ENTITY_TOO_LARGE: 413,
+  REQUEST_URI_TOO_LONG: 414,
+  UNSUPPORTED_MEDIA_TYPE: 415,
+  REQUEST_RANGE_NOT_SATISFIABLE: 416,
+  EXPECTATION_FAILED: 417,
+  PRECONDITION_REQUIRED: 428,
+  TOO_MANY_REQUESTS: 429,
+  REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
 
+  // Server Error 5xx
+  INTERNAL_SERVER_ERROR: 500,
+  NOT_IMPLEMENTED: 501,
+  BAD_GATEWAY: 502,
+  SERVICE_UNAVAILABLE: 503,
+  GATEWAY_TIMEOUT: 504,
+  HTTP_VERSION_NOT_SUPPORTED: 505,
+  NETWORK_AUTHENTICATION_REQUIRED: 511,
 
-/**
- * Possible error codes for jsloader.
- * @enum {number}
- */
-goog.net.jsloader.ErrorCode = {
-  LOAD_ERROR: 0,
-  TIMEOUT: 1,
-  VERIFY_ERROR: 2,
-  VERIFY_OBJECT_ALREADY_EXISTS: 3
+  /*
+   * IE returns this code for 204 due to its use of URLMon, which returns this
+   * code for 'Operation Aborted'. The status text is 'Unknown', the response
+   * headers are ''. Known to occur on IE 6 on XP through IE9 on Win7.
+   */
+  QUIRK_IE_NO_CONTENT: 1223
 };
 
 
-
 /**
- * A jsloader error.
+ * Returns whether the given status should be considered successful.
  *
- * @param {goog.net.jsloader.ErrorCode} code The error code.
- * @param {string=} opt_message Additional message.
- * @constructor
- * @extends {goog.debug.Error}
- * @final
+ * Successful codes are OK (200), CREATED (201), ACCEPTED (202),
+ * NO CONTENT (204), PARTIAL CONTENT (206), NOT MODIFIED (304),
+ * and IE's no content code (1223).
+ *
+ * @param {number} status The status code to test.
+ * @return {boolean} Whether the status code should be considered successful.
  */
-goog.net.jsloader.Error = function(code, opt_message) {
-  var msg = 'Jsloader error (code #' + code + ')';
-  if (opt_message) {
-    msg += ': ' + opt_message;
-  }
-  goog.net.jsloader.Error.base(this, 'constructor', msg);
+goog.net.HttpStatus.isSuccess = function(status) {
+  switch (status) {
+    case goog.net.HttpStatus.OK:
+    case goog.net.HttpStatus.CREATED:
+    case goog.net.HttpStatus.ACCEPTED:
+    case goog.net.HttpStatus.NO_CONTENT:
+    case goog.net.HttpStatus.PARTIAL_CONTENT:
+    case goog.net.HttpStatus.NOT_MODIFIED:
+    case goog.net.HttpStatus.QUIRK_IE_NO_CONTENT:
+      return true;
 
-  /**
-   * The code for this error.
-   *
-   * @type {goog.net.jsloader.ErrorCode}
-   */
-  this.code = code;
+    default:
+      return false;
+  }
 };
-goog.inherits(goog.net.jsloader.Error, goog.debug.Error);
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -92234,3736 +105694,3234 @@ goog.inherits(goog.net.jsloader.Error, goog.debug.Error);
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// The original file lives here: http://go/cross_domain_channel.js
-
-/**
- * @fileoverview Implements a cross-domain communication channel. A
- * typical web page is prevented by browser security from sending
- * request, such as a XMLHttpRequest, to other servers than the ones
- * from which it came. The Jsonp class provides a workaround by
- * using dynamically generated script tags. Typical usage:.
- *
- * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
- * var payload = { 'foo': 1, 'bar': true };
- * jsonp.send(payload, function(reply) { alert(reply) });
- *
- * This script works in all browsers that are currently supported by
- * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
- * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
- *
- */
-
-goog.provide('goog.net.Jsonp');
-
-goog.require('goog.Uri');
-goog.require('goog.net.jsloader');
-
-// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
-//
-// This class allows us (Google) to send data from non-Google and thus
-// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
-// anything sensitive, such as session or cookie specific data. Return
-// only data that you want parties external to Google to have. Also
-// NEVER use this method to send data from web pages to untrusted
-// servers, or redirects to unknown servers (www.google.com/cache,
-// /q=xx&btnl, /url, www.googlepages.com, etc.)
-//
-// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+goog.provide('goog.net.XhrLike');
 
 
 
 /**
- * Creates a new cross domain channel that sends data to the specified
- * host URL. By default, if no reply arrives within 5s, the channel
- * assumes the call failed to complete successfully.
- *
- * @param {goog.Uri|string} uri The Uri of the server side code that receives
- *     data posted through this channel (e.g.,
- *     "http://maps.google.com/maps/geo").
+ * Interface for the common parts of XMLHttpRequest.
  *
- * @param {string=} opt_callbackParamName The parameter name that is used to
- *     specify the callback. Defaults to "callback".
+ * Mostly copied from externs/w3c_xml.js.
  *
- * @constructor
- * @final
+ * @interface
+ * @see http://www.w3.org/TR/XMLHttpRequest/
  */
-goog.net.Jsonp = function(uri, opt_callbackParamName) {
-  /**
-   * The uri_ object will be used to encode the payload that is sent to the
-   * server.
-   * @type {goog.Uri}
-   * @private
-   */
-  this.uri_ = new goog.Uri(uri);
-
-  /**
-   * This is the callback parameter name that is added to the uri.
-   * @type {string}
-   * @private
-   */
-  this.callbackParamName_ = opt_callbackParamName ?
-      opt_callbackParamName : 'callback';
-
-  /**
-   * The length of time, in milliseconds, this channel is prepared
-   * to wait for for a request to complete. The default value is 5 seconds.
-   * @type {number}
-   * @private
-   */
-  this.timeout_ = 5000;
-};
+goog.net.XhrLike = function() {};
 
 
 /**
- * The name of the property of goog.global under which the callback is
- * stored.
+ * Typedef that refers to either native or custom-implemented XHR objects.
+ * @typedef {!goog.net.XhrLike|!XMLHttpRequest}
  */
-goog.net.Jsonp.CALLBACKS = '_callbacks_';
+goog.net.XhrLike.OrNative;
 
 
 /**
- * Used to generate unique callback IDs. The counter must be global because
- * all channels share a common callback object.
- * @private
+ * @type {function()|null|undefined}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange
  */
-goog.net.Jsonp.scriptCounter_ = 0;
+goog.net.XhrLike.prototype.onreadystatechange;
 
 
 /**
- * Sets the length of time, in milliseconds, this channel is prepared
- * to wait for for a request to complete. If the call is not competed
- * within the set time span, it is assumed to have failed. To wait
- * indefinitely for a request to complete set the timout to a negative
- * number.
- *
- * @param {number} timeout The length of time before calls are
- * interrupted.
+ * @type {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
  */
-goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
-  this.timeout_ = timeout;
-};
+goog.net.XhrLike.prototype.responseText;
 
 
 /**
- * Returns the current timeout value, in milliseconds.
- *
- * @return {number} The timeout value.
+ * @type {Document}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute
  */
-goog.net.Jsonp.prototype.getRequestTimeout = function() {
-  return this.timeout_;
-};
+goog.net.XhrLike.prototype.responseXML;
 
 
 /**
- * Sends the given payload to the URL specified at the construction
- * time. The reply is delivered to the given replyCallback. If the
- * errorCallback is specified and the reply does not arrive within the
- * timeout period set on this channel, the errorCallback is invoked
- * with the original payload.
- *
- * If no reply callback is specified, then the response is expected to
- * consist of calls to globally registered functions. No &callback=
- * URL parameter will be sent in the request, and the script element
- * will be cleaned up after the timeout.
- *
- * @param {Object=} opt_payload Name-value pairs.  If given, these will be
- *     added as parameters to the supplied URI as GET parameters to the
- *     given server URI.
- *
- * @param {Function=} opt_replyCallback A function expecting one
- *     argument, called when the reply arrives, with the response data.
- *
- * @param {Function=} opt_errorCallback A function expecting one
- *     argument, called on timeout, with the payload (if given), otherwise
- *     null.
- *
- * @param {string=} opt_callbackParamValue Value to be used as the
- *     parameter value for the callback parameter (callbackParamName).
- *     To be used when the value needs to be fixed by the client for a
- *     particular request, to make use of the cached responses for the request.
- *     NOTE: If multiple requests are made with the same
- *     opt_callbackParamValue, only the last call will work whenever the
- *     response comes back.
- *
- * @return {!Object} A request descriptor that may be used to cancel this
- *     transmission, or null, if the message may not be cancelled.
+ * @type {number}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#readystate
  */
-goog.net.Jsonp.prototype.send = function(opt_payload,
-                                         opt_replyCallback,
-                                         opt_errorCallback,
-                                         opt_callbackParamValue) {
-
-  var payload = opt_payload || null;
-
-  var id = opt_callbackParamValue ||
-      '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
-      goog.now().toString(36);
+goog.net.XhrLike.prototype.readyState;
 
-  if (!goog.global[goog.net.Jsonp.CALLBACKS]) {
-    goog.global[goog.net.Jsonp.CALLBACKS] = {};
-  }
 
-  // Create a new Uri object onto which this payload will be added
-  var uri = this.uri_.clone();
-  if (payload) {
-    goog.net.Jsonp.addPayloadToUri_(payload, uri);
-  }
+/**
+ * @type {number}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#status
+ */
+goog.net.XhrLike.prototype.status;
 
-  if (opt_replyCallback) {
-    var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback);
-    goog.global[goog.net.Jsonp.CALLBACKS][id] = reply;
 
-    uri.setParameterValues(this.callbackParamName_,
-                           goog.net.Jsonp.CALLBACKS + '.' + id);
-  }
+/**
+ * @type {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#statustext
+ */
+goog.net.XhrLike.prototype.statusText;
 
-  var deferred = goog.net.jsloader.load(uri.toString(),
-      {timeout: this.timeout_, cleanupWhenDone: true});
-  var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback);
-  deferred.addErrback(error);
 
-  return {id_: id, deferred_: deferred};
-};
+/**
+ * @param {string} method
+ * @param {string} url
+ * @param {?boolean=} opt_async
+ * @param {?string=} opt_user
+ * @param {?string=} opt_password
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
+ */
+goog.net.XhrLike.prototype.open = function(method, url, opt_async, opt_user,
+    opt_password) {};
 
 
 /**
- * Cancels a given request. The request must be exactly the object returned by
- * the send method.
- *
- * @param {Object} request The request object returned by the send method.
+ * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} opt_data
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-send()-method
  */
-goog.net.Jsonp.prototype.cancel = function(request) {
-  if (request) {
-    if (request.deferred_) {
-      request.deferred_.cancel();
-    }
-    if (request.id_) {
-      goog.net.Jsonp.cleanup_(request.id_, false);
-    }
-  }
-};
+goog.net.XhrLike.prototype.send = function(opt_data) {};
 
 
 /**
- * Creates a timeout callback that calls the given timeoutCallback with the
- * original payload.
- *
- * @param {string} id The id of the script node.
- * @param {Object} payload The payload that was sent to the server.
- * @param {Function=} opt_errorCallback The function called on timeout.
- * @return {!Function} A zero argument function that handles callback duties.
- * @private
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method
  */
-goog.net.Jsonp.newErrorHandler_ = function(id,
-                                           payload,
-                                           opt_errorCallback) {
-  /**
-   * When we call across domains with a request, this function is the
-   * timeout handler. Once it's done executing the user-specified
-   * error-handler, it removes the script node and original function.
-   */
-  return function() {
-    goog.net.Jsonp.cleanup_(id, false);
-    if (opt_errorCallback) {
-      opt_errorCallback(payload);
-    }
-  };
-};
+goog.net.XhrLike.prototype.abort = function() {};
 
 
 /**
- * Creates a reply callback that calls the given replyCallback with data
- * returned by the server.
- *
- * @param {string} id The id of the script node.
- * @param {Function} replyCallback The function called on reply.
- * @return {!Function} A reply callback function.
- * @private
+ * @param {string} header
+ * @param {string} value
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
  */
-goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) {
-  /**
-   * This function is the handler for the all-is-well response. It
-   * clears the error timeout handler, calls the user's handler, then
-   * removes the script node and itself.
-   *
-   * @param {...Object} var_args The response data sent from the server.
-   */
-  return function(var_args) {
-    goog.net.Jsonp.cleanup_(id, true);
-    replyCallback.apply(undefined, arguments);
-  };
-};
+goog.net.XhrLike.prototype.setRequestHeader = function(header, value) {};
 
 
 /**
- * Removes the script node and reply handler with the given id.
- *
- * @param {string} id The id of the script node to be removed.
- * @param {boolean} deleteReplyHandler If true, delete the reply handler
- *     instead of setting it to nullFunction (if we know the callback could
- *     never be called again).
- * @private
+ * @param {string} header
+ * @return {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
  */
-goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) {
-  if (goog.global[goog.net.Jsonp.CALLBACKS][id]) {
-    if (deleteReplyHandler) {
-      delete goog.global[goog.net.Jsonp.CALLBACKS][id];
-    } else {
-      // Removing the script tag doesn't necessarily prevent the script
-      // from firing, so we make the callback a noop.
-      goog.global[goog.net.Jsonp.CALLBACKS][id] = goog.nullFunction;
-    }
-  }
-};
+goog.net.XhrLike.prototype.getResponseHeader = function(header) {};
 
 
 /**
- * Returns URL encoded payload. The payload should be a map of name-value
- * pairs, in the form {"foo": 1, "bar": true, ...}.  If the map is empty,
- * the URI will be unchanged.
- *
- * <p>The method uses hasOwnProperty() to assure the properties are on the
- * object, not on its prototype.
- *
- * @param {!Object} payload A map of value name pairs to be encoded.
- *     A value may be specified as an array, in which case a query parameter
- *     will be created for each value, e.g.:
- *     {"foo": [1,2]} will encode to "foo=1&foo=2".
- *
- * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
- *     will be encoded.
- *
- * @return {!goog.Uri} A reference to the Uri sent as a parameter.
- * @private
+ * @return {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
  */
-goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
-  for (var name in payload) {
-    // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
-    // case, we iterate over all properties as a very lame workaround.
-    if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
-      uri.setParameterValues(name, payload[name]);
-    }
-  }
-  return uri;
-};
-
+goog.net.XhrLike.prototype.getAllResponseHeaders = function() {};
 
-// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
 //
-// This class allows us (Google) to send data from non-Google and thus
-// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
-// anything sensitive, such as session or cookie specific data. Return
-// only data that you want parties external to Google to have. Also
-// NEVER use this method to send data from web pages to untrusted
-// servers, or redirects to unknown servers (www.google.com/cache,
-// /q=xx&btnl, /url, www.googlepages.com, etc.)
+// 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
 //
-// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
-
-goog.provide('ol.TileUrlFunction');
-goog.provide('ol.TileUrlFunctionType');
-
-goog.require('goog.array');
-goog.require('goog.math');
-goog.require('ol.TileCoord');
-goog.require('ol.tilecoord');
-
+//      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.
 
 /**
- * A function that takes an {@link ol.TileCoord} for the tile coordinate,
- * a `{number}` representing the pixel ratio and an {@link ol.proj.Projection}
- * for the projection  as arguments and returns a `{string}` or
- * undefined representing the tile URL.
- *
- * @typedef {function(ol.TileCoord, number,
- *           ol.proj.Projection): (string|undefined)}
- * @api
+ * @fileoverview Interface for a factory for creating XMLHttpRequest objects
+ * and metadata about them.
+ * @author dbk@google.com (David Barrett-Kahn)
  */
-ol.TileUrlFunctionType;
 
+goog.provide('goog.net.XmlHttpFactory');
 
-/**
- * @typedef {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=):
- *     ol.TileCoord}
- */
-ol.TileCoordTransformType;
-
+/** @suppress {extraRequire} Typedef. */
+goog.require('goog.net.XhrLike');
 
-/**
- * @param {string} template Template.
- * @return {ol.TileUrlFunctionType} Tile URL function.
- */
-ol.TileUrlFunction.createFromTemplate = function(template) {
-  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 (goog.isNull(tileCoord)) {
-          return undefined;
-        } else {
-          return template.replace(zRegEx, tileCoord[0].toString())
-                         .replace(xRegEx, tileCoord[1].toString())
-                         .replace(yRegEx, tileCoord[2].toString())
-                         .replace(dashYRegEx, function() {
-                           var y = (1 << tileCoord[0]) - tileCoord[2] - 1;
-                           return y.toString();
-                         });
-        }
-      });
-};
 
 
 /**
- * @param {Array.<string>} templates Templates.
- * @return {ol.TileUrlFunctionType} Tile URL function.
+ * Abstract base class for an XmlHttpRequest factory.
+ * @constructor
  */
-ol.TileUrlFunction.createFromTemplates = function(templates) {
-  return ol.TileUrlFunction.createFromTileUrlFunctions(
-      goog.array.map(templates, ol.TileUrlFunction.createFromTemplate));
+goog.net.XmlHttpFactory = function() {
 };
 
 
-/**
- * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
- * @return {ol.TileUrlFunctionType} Tile URL function.
- */
-ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
-  if (tileUrlFunctions.length === 1) {
-    return tileUrlFunctions[0];
-  }
-  return (
-      /**
-       * @param {ol.TileCoord} tileCoord Tile Coordinate.
-       * @param {number} pixelRatio Pixel ratio.
-       * @param {ol.proj.Projection} projection Projection.
-       * @return {string|undefined} Tile URL.
-       */
-      function(tileCoord, pixelRatio, projection) {
-        if (goog.isNull(tileCoord)) {
-          return undefined;
-        } else {
-          var h = ol.tilecoord.hash(tileCoord);
-          var index = goog.math.modulo(h, tileUrlFunctions.length);
-          return tileUrlFunctions[index](tileCoord, pixelRatio, projection);
-        }
-      });
-};
+/**
+ * Cache of options - we only actually call internalGetOptions once.
+ * @type {Object}
+ * @private
+ */
+goog.net.XmlHttpFactory.prototype.cachedOptions_ = null;
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {string|undefined} Tile URL.
+ * @return {!goog.net.XhrLike.OrNative} A new XhrLike instance.
  */
-ol.TileUrlFunction.nullTileUrlFunction =
-    function(tileCoord, pixelRatio, projection) {
-  return undefined;
-};
+goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod;
 
 
 /**
- * @param {ol.TileCoordTransformType} transformFn Transform function.
- * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
- * @return {ol.TileUrlFunctionType} Tile URL function.
+ * @return {Object} Options describing how xhr objects obtained from this
+ *     factory should be used.
  */
-ol.TileUrlFunction.withTileCoordTransform =
-    function(transformFn, tileUrlFunction) {
-  var tmpTileCoord = [0, 0, 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 (goog.isNull(tileCoord)) {
-          return undefined;
-        } else {
-          return tileUrlFunction(
-              transformFn(tileCoord, projection, tmpTileCoord),
-              pixelRatio,
-              projection);
-        }
-      });
+goog.net.XmlHttpFactory.prototype.getOptions = function() {
+  return this.cachedOptions_ ||
+      (this.cachedOptions_ = this.internalGetOptions());
 };
 
 
 /**
- * @param {string} url URL.
- * @return {Array.<string>} Array of urls.
+ * Override this method in subclasses to preserve the caching offered by
+ * getOptions().
+ * @return {Object} Options describing how xhr objects obtained from this
+ *     factory should be used.
+ * @protected
  */
-ol.TileUrlFunction.expandUrl = function(url) {
-  var urls = [];
-  var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url);
-  if (match) {
-    var startCharCode = match[1].charCodeAt(0);
-    var stopCharCode = match[2].charCodeAt(0);
-    var charCode;
-    for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) {
-      urls.push(url.replace(match[0], String.fromCharCode(charCode)));
-    }
-  } else {
-    urls.push(url);
-  }
-  return urls;
-};
+goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod;
 
-goog.provide('ol.TileCache');
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
-goog.require('goog.asserts');
-goog.require('ol');
-goog.require('ol.TileRange');
-goog.require('ol.structs.LRUCache');
-goog.require('ol.tilecoord');
+/**
+ * @fileoverview Implementation of XmlHttpFactory which allows construction from
+ * simple factory methods.
+ * @author dbk@google.com (David Barrett-Kahn)
+ */
+
+goog.provide('goog.net.WrapperXmlHttpFactory');
+
+/** @suppress {extraRequire} Typedef. */
+goog.require('goog.net.XhrLike');
+goog.require('goog.net.XmlHttpFactory');
 
 
 
 /**
+ * An xhr factory subclass which can be constructed using two factory methods.
+ * This exists partly to allow the preservation of goog.net.XmlHttp.setFactory()
+ * with an unchanged signature.
+ * @param {function():!goog.net.XhrLike.OrNative} xhrFactory
+ *     A function which returns a new XHR object.
+ * @param {function():!Object} optionsFactory A function which returns the
+ *     options associated with xhr objects from this factory.
+ * @extends {goog.net.XmlHttpFactory}
  * @constructor
- * @extends {ol.structs.LRUCache.<ol.Tile>}
- * @param {number=} opt_highWaterMark High water mark.
- * @struct
+ * @final
  */
-ol.TileCache = function(opt_highWaterMark) {
+goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) {
+  goog.net.XmlHttpFactory.call(this);
 
-  goog.base(this);
+  /**
+   * XHR factory method.
+   * @type {function() : !goog.net.XhrLike.OrNative}
+   * @private
+   */
+  this.xhrFactory_ = xhrFactory;
 
   /**
+   * Options factory method.
+   * @type {function() : !Object}
    * @private
-   * @type {number}
    */
-  this.highWaterMark_ = goog.isDef(opt_highWaterMark) ?
-      opt_highWaterMark : ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK;
+  this.optionsFactory_ = optionsFactory;
+};
+goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory);
+
 
+/** @override */
+goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() {
+  return this.xhrFactory_();
+};
+
+
+/** @override */
+goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() {
+  return this.optionsFactory_();
 };
-goog.inherits(ol.TileCache, ol.structs.LRUCache);
 
 
+// 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.
+
 /**
- * @return {boolean} Can expire cache.
+ * @fileoverview Low level handling of XMLHttpRequest.
+ * @author arv@google.com (Erik Arvidsson)
+ * @author dbk@google.com (David Barrett-Kahn)
  */
-ol.TileCache.prototype.canExpireCache = function() {
-  return this.getCount() > this.highWaterMark_;
-};
+
+goog.provide('goog.net.DefaultXmlHttpFactory');
+goog.provide('goog.net.XmlHttp');
+goog.provide('goog.net.XmlHttp.OptionType');
+goog.provide('goog.net.XmlHttp.ReadyState');
+goog.provide('goog.net.XmlHttpDefines');
+
+goog.require('goog.asserts');
+goog.require('goog.net.WrapperXmlHttpFactory');
+goog.require('goog.net.XmlHttpFactory');
 
 
 /**
- * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
+ * Static class for creating XMLHttpRequest objects.
+ * @return {!goog.net.XhrLike.OrNative} A new XMLHttpRequest object.
  */
-ol.TileCache.prototype.expireCache = function(usedTiles) {
-  var tile, zKey;
-  while (this.canExpireCache()) {
-    tile = /** @type {ol.Tile} */ (this.peekLast());
-    zKey = tile.tileCoord[0].toString();
-    if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) {
-      break;
-    } else {
-      this.pop().dispose();
-    }
-  }
+goog.net.XmlHttp = function() {
+  return goog.net.XmlHttp.factory_.createInstance();
 };
 
 
 /**
- * Remove a tile range from the cache, e.g. to invalidate tiles.
- * @param {ol.TileRange} tileRange The tile range to prune.
+ * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
+ *     true bypasses the ActiveX probing code.
+ * NOTE(ruilopes): Due to the way JSCompiler works, this define *will not* strip
+ * out the ActiveX probing code from binaries.  To achieve this, use
+ * {@code goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR} instead.
+ * TODO(ruilopes): Collapse both defines.
  */
-ol.TileCache.prototype.pruneTileRange = function(tileRange) {
-  var i = this.getCount(),
-      key;
-  while (i--) {
-    key = this.peekLastKey();
-    if (tileRange.contains(ol.tilecoord.createFromString(key))) {
-      this.pop().dispose();
-    } else {
-      this.get(key);
-    }
-  }
-};
+goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false);
 
-goog.provide('ol.source.TileImage');
 
-goog.require('goog.asserts');
-goog.require('ol.ImageTile');
-goog.require('ol.TileCache');
-goog.require('ol.TileCoord');
-goog.require('ol.TileLoadFunctionType');
-goog.require('ol.TileState');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.TileUrlFunctionType');
-goog.require('ol.source.Tile');
+/** @const */
+goog.net.XmlHttpDefines = {};
 
 
+/**
+ * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
+ *     true eliminates the ActiveX probing code.
+ */
+goog.define('goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR', false);
+
 
 /**
- * @classdesc
- * Base class for sources providing images divided into a tile grid.
- *
- * @constructor
- * @extends {ol.source.Tile}
- * @param {olx.source.TileImageOptions} options Image tile options.
- * @api
+ * Gets the options to use with the XMLHttpRequest objects obtained using
+ * the static methods.
+ * @return {Object} The options.
  */
-ol.source.TileImage = function(options) {
+goog.net.XmlHttp.getOptions = function() {
+  return goog.net.XmlHttp.factory_.getOptions();
+};
 
-  goog.base(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    opaque: options.opaque,
-    projection: options.projection,
-    state: goog.isDef(options.state) ?
-        /** @type {ol.source.State} */ (options.state) : undefined,
-    tileGrid: options.tileGrid,
-    tilePixelRatio: options.tilePixelRatio
-  });
 
+/**
+ * Type of options that an XmlHttp object can have.
+ * @enum {number}
+ */
+goog.net.XmlHttp.OptionType = {
   /**
-   * @protected
-   * @type {ol.TileUrlFunctionType}
+   * Whether a goog.nullFunction should be used to clear the onreadystatechange
+   * handler instead of null.
    */
-  this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ?
-      options.tileUrlFunction :
-      ol.TileUrlFunction.nullTileUrlFunction;
+  USE_NULL_FUNCTION: 0,
 
   /**
-   * @protected
-   * @type {?string}
+   * NOTE(user): In IE if send() errors on a *local* request the readystate
+   * is still changed to COMPLETE.  We need to ignore it and allow the
+   * try/catch around send() to pick up the error.
    */
-  this.crossOrigin =
-      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
+  LOCAL_REQUEST_ERROR: 1
+};
+
 
+/**
+ * Status constants for XMLHTTP, matches:
+ * http://msdn.microsoft.com/library/default.asp?url=/library/
+ *   en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp
+ * @enum {number}
+ */
+goog.net.XmlHttp.ReadyState = {
   /**
-   * @protected
-   * @type {ol.TileCache}
+   * Constant for when xmlhttprequest.readyState is uninitialized
    */
-  this.tileCache = new ol.TileCache();
+  UNINITIALIZED: 0,
 
   /**
-   * @protected
-   * @type {ol.TileLoadFunctionType}
+   * Constant for when xmlhttprequest.readyState is loading.
    */
-  this.tileLoadFunction = goog.isDef(options.tileLoadFunction) ?
-      options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction;
+  LOADING: 1,
 
   /**
-   * @protected
-   * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string,
-   *        ?string, ol.TileLoadFunctionType)}
+   * Constant for when xmlhttprequest.readyState is loaded.
    */
-  this.tileClass = goog.isDef(options.tileClass) ?
-      options.tileClass : ol.ImageTile;
+  LOADED: 2,
+
+  /**
+   * Constant for when xmlhttprequest.readyState is in an interactive state.
+   */
+  INTERACTIVE: 3,
 
+  /**
+   * Constant for when xmlhttprequest.readyState is completed
+   */
+  COMPLETE: 4
 };
-goog.inherits(ol.source.TileImage, ol.source.Tile);
 
 
 /**
- * @param {ol.ImageTile} imageTile Image tile.
- * @param {string} src Source.
+ * The global factory instance for creating XMLHttpRequest objects.
+ * @type {goog.net.XmlHttpFactory}
+ * @private
  */
-ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
-  imageTile.getImage().src = src;
-};
+goog.net.XmlHttp.factory_;
 
 
 /**
- * @inheritDoc
+ * Sets the factories for creating XMLHttpRequest objects and their options.
+ * @param {Function} factory The factory for XMLHttpRequest objects.
+ * @param {Function} optionsFactory The factory for options.
+ * @deprecated Use setGlobalFactory instead.
  */
-ol.source.TileImage.prototype.canExpireCache = function() {
-  return this.tileCache.canExpireCache();
+goog.net.XmlHttp.setFactory = function(factory, optionsFactory) {
+  goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(
+      goog.asserts.assert(factory),
+      goog.asserts.assert(optionsFactory)));
 };
 
 
 /**
- * @inheritDoc
+ * Sets the global factory object.
+ * @param {!goog.net.XmlHttpFactory} factory New global factory object.
  */
-ol.source.TileImage.prototype.expireCache = function(usedTiles) {
-  this.tileCache.expireCache(usedTiles);
+goog.net.XmlHttp.setGlobalFactory = function(factory) {
+  goog.net.XmlHttp.factory_ = factory;
 };
 
 
+
 /**
- * @inheritDoc
+ * Default factory to use when creating xhr objects.  You probably shouldn't be
+ * instantiating this directly, but rather using it via goog.net.XmlHttp.
+ * @extends {goog.net.XmlHttpFactory}
+ * @constructor
  */
-ol.source.TileImage.prototype.getTile =
-    function(z, x, y, pixelRatio, projection) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
+goog.net.DefaultXmlHttpFactory = function() {
+  goog.net.XmlHttpFactory.call(this);
+};
+goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory);
+
+
+/** @override */
+goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() {
+  var progId = this.getProgId_();
+  if (progId) {
+    return new ActiveXObject(progId);
   } else {
-    goog.asserts.assert(projection);
-    var tileCoord = [z, x, y];
-    var tileUrl = this.tileUrlFunction(tileCoord, pixelRatio, projection);
-    var tile = new this.tileClass(
-        tileCoord,
-        goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY,
-        goog.isDef(tileUrl) ? tileUrl : '',
-        this.crossOrigin,
-        this.tileLoadFunction);
-    this.tileCache.set(tileCoordKey, tile);
-    return tile;
+    return new XMLHttpRequest();
   }
 };
 
 
-/**
- * @return {ol.TileLoadFunctionType} TileLoadFunction
- * @api
- */
-ol.source.TileImage.prototype.getTileLoadFunction = function() {
-  return this.tileLoadFunction;
+/** @override */
+goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() {
+  var progId = this.getProgId_();
+  var options = {};
+  if (progId) {
+    options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true;
+    options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true;
+  }
+  return options;
 };
 
 
 /**
- * @return {ol.TileUrlFunctionType} TileUrlFunction
- * @api
+ * The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.
+ * @type {string|undefined}
+ * @private
  */
-ol.source.TileImage.prototype.getTileUrlFunction = function() {
-  return this.tileUrlFunction;
-};
+goog.net.DefaultXmlHttpFactory.prototype.ieProgId_;
 
 
 /**
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
- * @api
+ * Initialize the private state used by other functions.
+ * @return {string} The ActiveX PROG ID string to use to create xhr's in IE.
+ * @private
  */
-ol.source.TileImage.prototype.setTileLoadFunction = function(tileLoadFunction) {
-  this.tileCache.clear();
-  this.tileLoadFunction = tileLoadFunction;
-  this.dispatchChangeEvent();
-};
+goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() {
+  if (goog.net.XmlHttp.ASSUME_NATIVE_XHR ||
+      goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR) {
+    return '';
+  }
+
+  // The following blog post describes what PROG IDs to use to create the
+  // XMLHTTP object in Internet Explorer:
+  // http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
+  // However we do not (yet) fully trust that this will be OK for old versions
+  // of IE on Win9x so we therefore keep the last 2.
+  if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' &&
+      typeof ActiveXObject != 'undefined') {
+    // Candidate Active X types.
+    var ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0',
+                           'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];
+    for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) {
+      var candidate = ACTIVE_X_IDENTS[i];
+      /** @preserveTry */
+      try {
+        new ActiveXObject(candidate);
+        // NOTE(user): cannot assign progid and return candidate in one line
+        // because JSCompiler complaings: BUG 658126
+        this.ieProgId_ = candidate;
+        return candidate;
+      } catch (e) {
+        // do nothing; try next choice
+      }
+    }
 
+    // couldn't find any matches
+    throw Error('Could not create ActiveXObject. ActiveX might be disabled,' +
+                ' or MSXML might not be installed');
+  }
 
-/**
- * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
- * @api
- */
-ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) {
-  // FIXME It should be possible to be more intelligent and avoid clearing the
-  // FIXME cache.  The tile URL function would need to be incorporated into the
-  // FIXME cache key somehow.
-  this.tileCache.clear();
-  this.tileUrlFunction = tileUrlFunction;
-  this.dispatchChangeEvent();
+  return /** @type {string} */ (this.ieProgId_);
 };
 
 
+//Set the global factory to an instance of the default factory.
+goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory());
+
+// Copyright 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.
+
 /**
- * @inheritDoc
+ * @fileoverview Wrapper class for handling XmlHttpRequests.
+ *
+ * One off requests can be sent through goog.net.XhrIo.send() or an
+ * instance can be created to send multiple requests.  Each request uses its
+ * own XmlHttpRequest object and handles clearing of the event callback to
+ * ensure no leaks.
+ *
+ * XhrIo is event based, it dispatches events when a request finishes, fails or
+ * succeeds or when the ready-state changes. The ready-state or timeout event
+ * fires first, followed by a generic completed event. Then the abort, error,
+ * or success event is fired as appropriate. Lastly, the ready event will fire
+ * to indicate that the object may be used to make another request.
+ *
+ * The error event may also be called before completed and
+ * ready-state-change if the XmlHttpRequest.open() or .send() methods throw.
+ *
+ * This class does not support multiple requests, queuing, or prioritization.
+ *
+ * Tested = IE6, FF1.5, Safari, Opera 8.5
+ *
+ * TODO(user): Error cases aren't playing nicely in Safari.
+ *
  */
-ol.source.TileImage.prototype.useTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    this.tileCache.get(tileCoordKey);
-  }
-};
 
-goog.provide('ol.tilegrid.XYZ');
 
-goog.require('goog.math');
-goog.require('ol');
-goog.require('ol.TileCoord');
-goog.require('ol.TileRange');
-goog.require('ol.extent');
-goog.require('ol.extent.Corner');
-goog.require('ol.proj');
-goog.require('ol.proj.EPSG3857');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.TileGrid');
+goog.provide('goog.net.XhrIo');
+goog.provide('goog.net.XhrIo.ResponseType');
+
+goog.require('goog.Timer');
+goog.require('goog.array');
+goog.require('goog.debug.entryPointRegistry');
+goog.require('goog.events.EventTarget');
+goog.require('goog.json');
+goog.require('goog.log');
+goog.require('goog.net.ErrorCode');
+goog.require('goog.net.EventType');
+goog.require('goog.net.HttpStatus');
+goog.require('goog.net.XmlHttp');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.structs');
+goog.require('goog.structs.Map');
+goog.require('goog.uri.utils');
+goog.require('goog.userAgent');
+
+goog.forwardDeclare('goog.Uri');
 
 
 
 /**
- * @classdesc
- * Set the grid pattern for sources accessing XYZ tiled-image servers.
- *
+ * Basic class for handling XMLHttpRequests.
+ * @param {goog.net.XmlHttpFactory=} opt_xmlHttpFactory Factory to use when
+ *     creating XMLHttpRequest objects.
  * @constructor
- * @extends {ol.tilegrid.TileGrid}
- * @param {olx.tilegrid.XYZOptions} options XYZ options.
- * @struct
- * @api
+ * @extends {goog.events.EventTarget}
  */
-ol.tilegrid.XYZ = function(options) {
-  var extent = goog.isDef(options.extent) ?
-      options.extent : ol.proj.EPSG3857.EXTENT;
-  var resolutions = ol.tilegrid.resolutionsFromExtent(
-      extent, options.maxZoom, options.tileSize);
+goog.net.XhrIo = function(opt_xmlHttpFactory) {
+  goog.net.XhrIo.base(this, 'constructor');
 
-  goog.base(this, {
-    minZoom: options.minZoom,
-    origin: ol.extent.getCorner(extent, ol.extent.Corner.TOP_LEFT),
-    resolutions: resolutions,
-    tileSize: options.tileSize
-  });
+  /**
+   * Map of default headers to add to every request, use:
+   * XhrIo.headers.set(name, value)
+   * @type {!goog.structs.Map}
+   */
+  this.headers = new goog.structs.Map();
 
-};
-goog.inherits(ol.tilegrid.XYZ, ol.tilegrid.TileGrid);
+  /**
+   * Optional XmlHttpFactory
+   * @private {goog.net.XmlHttpFactory}
+   */
+  this.xmlHttpFactory_ = opt_xmlHttpFactory || null;
 
+  /**
+   * Whether XMLHttpRequest is active.  A request is active from the time send()
+   * is called until onReadyStateChange() is complete, or error() or abort()
+   * is called.
+   * @private {boolean}
+   */
+  this.active_ = false;
 
-/**
- * @inheritDoc
- */
-ol.tilegrid.XYZ.prototype.createTileCoordTransform = function(opt_options) {
-  var options = goog.isDef(opt_options) ? opt_options : {};
-  var minZ = this.minZoom;
-  var maxZ = this.maxZoom;
-  var wrapX = goog.isDef(options.wrapX) ? options.wrapX : true;
-  /** @type {Array.<ol.TileRange>} */
-  var tileRangeByZ = null;
-  if (goog.isDef(options.extent)) {
-    tileRangeByZ = new Array(maxZ + 1);
-    var z;
-    for (z = 0; z <= maxZ; ++z) {
-      if (z < minZ) {
-        tileRangeByZ[z] = null;
-      } else {
-        tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z);
-      }
-    }
-  }
-  return (
-      /**
-       * @param {ol.TileCoord} tileCoord Tile coordinate.
-       * @param {ol.proj.Projection} projection Projection.
-       * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate.
-       * @return {ol.TileCoord} Tile coordinate.
-       */
-      function(tileCoord, projection, opt_tileCoord) {
-        var z = tileCoord[0];
-        if (z < minZ || maxZ < z) {
-          return null;
-        }
-        var n = Math.pow(2, z);
-        var x = tileCoord[1];
-        if (wrapX) {
-          x = goog.math.modulo(x, n);
-        } else if (x < 0 || n <= x) {
-          return null;
-        }
-        var y = tileCoord[2];
-        if (y < -n || -1 < y) {
-          return null;
-        }
-        if (!goog.isNull(tileRangeByZ)) {
-          if (!tileRangeByZ[z].containsXY(x, y)) {
-            return null;
-          }
-        }
-        return ol.tilecoord.createOrUpdate(z, x, -y - 1, opt_tileCoord);
-      });
-};
+  /**
+   * The XMLHttpRequest object that is being used for the transfer.
+   * @private {?goog.net.XhrLike.OrNative}
+   */
+  this.xhr_ = null;
 
+  /**
+   * The options to use with the current XMLHttpRequest object.
+   * @private {Object}
+   */
+  this.xhrOptions_ = null;
 
-/**
- * @inheritDoc
- */
-ol.tilegrid.XYZ.prototype.getTileCoordChildTileRange =
-    function(tileCoord, opt_tileRange) {
-  if (tileCoord[0] < this.maxZoom) {
-    return ol.TileRange.createOrUpdate(
-        2 * tileCoord[1], 2 * (tileCoord[1] + 1),
-        2 * tileCoord[2], 2 * (tileCoord[2] + 1),
-        opt_tileRange);
-  } else {
-    return null;
-  }
-};
+  /**
+   * Last URL that was requested.
+   * @private {string|goog.Uri}
+   */
+  this.lastUri_ = '';
 
+  /**
+   * Method for the last request.
+   * @private {string}
+   */
+  this.lastMethod_ = '';
 
-/**
- * @inheritDoc
- */
-ol.tilegrid.XYZ.prototype.forEachTileCoordParentTileRange =
-    function(tileCoord, callback, opt_this, opt_tileRange) {
-  var tileRange = ol.TileRange.createOrUpdate(
-      0, tileCoord[1], 0, tileCoord[2], opt_tileRange);
-  var z;
-  for (z = tileCoord[0] - 1; z >= this.minZoom; --z) {
-    tileRange.minX = tileRange.maxX >>= 1;
-    tileRange.minY = tileRange.maxY >>= 1;
-    if (callback.call(opt_this, z, tileRange)) {
-      return true;
-    }
-  }
-  return false;
-};
+  /**
+   * Last error code.
+   * @private {!goog.net.ErrorCode}
+   */
+  this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
 
-goog.provide('ol.source.BingMaps');
+  /**
+   * Last error message.
+   * @private {Error|string}
+   */
+  this.lastError_ = '';
 
-goog.require('goog.Uri');
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.net.Jsonp');
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.TileRange');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.source.State');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.XYZ');
+  /**
+   * Used to ensure that we don't dispatch an multiple ERROR events. This can
+   * happen in IE when it does a synchronous load and one error is handled in
+   * the ready statte change and one is handled due to send() throwing an
+   * exception.
+   * @private {boolean}
+   */
+  this.errorDispatched_ = false;
 
+  /**
+   * Used to make sure we don't fire the complete event from inside a send call.
+   * @private {boolean}
+   */
+  this.inSend_ = false;
 
+  /**
+   * Used in determining if a call to {@link #onReadyStateChange_} is from
+   * within a call to this.xhr_.open.
+   * @private {boolean}
+   */
+  this.inOpen_ = false;
 
-/**
- * @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) {
+  /**
+   * Used in determining if a call to {@link #onReadyStateChange_} is from
+   * within a call to this.xhr_.abort.
+   * @private {boolean}
+   */
+  this.inAbort_ = false;
 
-  goog.base(this, {
-    crossOrigin: 'anonymous',
-    opaque: true,
-    projection: ol.proj.get('EPSG:3857'),
-    state: ol.source.State.LOADING,
-    tileLoadFunction: options.tileLoadFunction
-  });
+  /**
+   * Number of milliseconds after which an incomplete request will be aborted
+   * and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no timeout
+   * is set.
+   * @private {number}
+   */
+  this.timeoutInterval_ = 0;
 
   /**
-   * @private
-   * @type {string}
+   * Timer to track request timeout.
+   * @private {?number}
    */
-  this.culture_ = goog.isDef(options.culture) ? options.culture : 'en-us';
+  this.timeoutId_ = null;
 
-  var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
-  var uri = new goog.Uri(
-      protocol + '//dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
-      options.imagerySet);
+  /**
+   * The requested type for the response. The empty string means use the default
+   * XHR behavior.
+   * @private {goog.net.XhrIo.ResponseType}
+   */
+  this.responseType_ = goog.net.XhrIo.ResponseType.DEFAULT;
 
-  var jsonp = new goog.net.Jsonp(uri, 'jsonp');
-  jsonp.send({
-    'include': 'ImageryProviders',
-    'key': options.key
-  }, goog.bind(this.handleImageryMetadataResponse, this));
+  /**
+   * Whether a "credentialed" request is to be sent (one that is aware of
+   * cookies and authentication). This is applicable only for cross-domain
+   * requests and more recent browsers that support this part of the HTTP Access
+   * Control standard.
+   *
+   * @see http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute
+   *
+   * @private {boolean}
+   */
+  this.withCredentials_ = false;
 
+  /**
+   * True if we can use XMLHttpRequest's timeout directly.
+   * @private {boolean}
+   */
+  this.useXhr2Timeout_ = false;
 };
-goog.inherits(ol.source.BingMaps, ol.source.TileImage);
+goog.inherits(goog.net.XhrIo, goog.events.EventTarget);
 
 
 /**
- * @const
- * @type {ol.Attribution}
- * @api
+ * Response types that may be requested for XMLHttpRequests.
+ * @enum {string}
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute
  */
-ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({
-  html: '<a class="ol-attribution-bing-tos" target="_blank" ' +
-      'href="http://www.microsoft.com/maps/product/terms.html">' +
-      'Terms of Use</a>'
-});
+goog.net.XhrIo.ResponseType = {
+  DEFAULT: '',
+  TEXT: 'text',
+  DOCUMENT: 'document',
+  // Not supported as of Chrome 10.0.612.1 dev
+  BLOB: 'blob',
+  ARRAY_BUFFER: 'arraybuffer'
+};
 
 
 /**
- * @param {BingMapsImageryMetadataResponse} response Response.
+ * A reference to the XhrIo logger
+ * @private {goog.debug.Logger}
+ * @const
  */
-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;
-  }
+goog.net.XhrIo.prototype.logger_ =
+    goog.log.getLogger('goog.net.XhrIo');
 
-  var brandLogoUri = response.brandLogoUri;
-  //var copyright = response.copyright;  // FIXME do we need to display this?
-  var resource = response.resourceSets[0].resources[0];
 
-  goog.asserts.assert(resource.imageWidth == resource.imageHeight);
-  var sourceProjection = this.getProjection();
-  var tileGrid = new ol.tilegrid.XYZ({
-    extent: ol.tilegrid.extentFromProjection(sourceProjection),
-    minZoom: resource.zoomMin,
-    maxZoom: resource.zoomMax,
-    tileSize: resource.imageWidth
-  });
-  this.tileGrid = tileGrid;
+/**
+ * The Content-Type HTTP header name
+ * @type {string}
+ */
+goog.net.XhrIo.CONTENT_TYPE_HEADER = 'Content-Type';
 
-  var culture = this.culture_;
-  this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
-      tileGrid.createTileCoordTransform(),
-      ol.TileUrlFunction.createFromTileUrlFunctions(
-          goog.array.map(
-              resource.imageUrlSubdomains,
-              function(subdomain) {
-                var imageUrl = resource.imageUrl
-                    .replace('{subdomain}', subdomain)
-                    .replace('{culture}', culture);
-                return (
-                    /**
-                     * @param {ol.TileCoord} tileCoord Tile coordinate.
-                     * @param {number} pixelRatio Pixel ratio.
-                     * @param {ol.proj.Projection} projection Projection.
-                     * @return {string|undefined} Tile URL.
-                     */
-                    function(tileCoord, pixelRatio, projection) {
-                      goog.asserts.assert(ol.proj.equivalent(
-                          projection, sourceProjection));
-                      if (goog.isNull(tileCoord)) {
-                        return undefined;
-                      } else {
-                        return imageUrl.replace(
-                            '{quadkey}', ol.tilecoord.quadKey(tileCoord));
-                      }
-                    });
-              })));
 
-  if (resource.imageryProviders) {
-    var transform = ol.proj.getTransformFromProjections(
-        ol.proj.get('EPSG:4326'), this.getProjection());
+/**
+ * The pattern matching the 'http' and 'https' URI schemes
+ * @type {!RegExp}
+ */
+goog.net.XhrIo.HTTP_SCHEME_PATTERN = /^https?$/i;
 
-    var attributions = goog.array.map(
-        resource.imageryProviders,
-        function(imageryProvider) {
-          var html = imageryProvider.attribution;
-          /** @type {Object.<string, Array.<ol.TileRange>>} */
-          var tileRanges = {};
-          goog.array.forEach(
-              imageryProvider.coverageAreas,
-              function(coverageArea) {
-                var minZ = coverageArea.zoomMin;
-                var maxZ = coverageArea.zoomMax;
-                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);
+/**
+ * The methods that typically come along with form data.  We set different
+ * headers depending on whether the HTTP action is one of these.
+ */
+goog.net.XhrIo.METHODS_WITH_FORM_DATA = ['POST', 'PUT'];
 
-  this.setState(ol.source.State.READY);
 
-};
+/**
+ * The Content-Type HTTP header value for a url-encoded form
+ * @type {string}
+ */
+goog.net.XhrIo.FORM_CONTENT_TYPE =
+    'application/x-www-form-urlencoded;charset=utf-8';
 
-// FIXME keep cluster cache by resolution ?
-// FIXME distance not respected because of the centroid
 
-goog.provide('ol.source.Cluster');
+/**
+ * The XMLHttpRequest Level two timeout delay ms property name.
+ *
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute
+ *
+ * @private {string}
+ * @const
+ */
+goog.net.XhrIo.XHR2_TIMEOUT_ = 'timeout';
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.events.EventType');
-goog.require('goog.object');
-goog.require('ol.Feature');
-goog.require('ol.coordinate');
-goog.require('ol.extent');
-goog.require('ol.geom.Point');
-goog.require('ol.source.Vector');
 
+/**
+ * The XMLHttpRequest Level two ontimeout handler property name.
+ *
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute
+ *
+ * @private {string}
+ * @const
+ */
+goog.net.XhrIo.XHR2_ON_TIMEOUT_ = 'ontimeout';
 
 
 /**
- * @constructor
- * @param {olx.source.ClusterOptions} options
- * @extends {ol.source.Vector}
- * @api
+ * All non-disposed instances of goog.net.XhrIo created
+ * by {@link goog.net.XhrIo.send} are in this Array.
+ * @see goog.net.XhrIo.cleanup
+ * @private {!Array<!goog.net.XhrIo>}
  */
-ol.source.Cluster = function(options) {
-  goog.base(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    projection: options.projection
-  });
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.resolution_ = undefined;
+goog.net.XhrIo.sendInstances_ = [];
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.distance_ = goog.isDef(options.distance) ? options.distance : 20;
 
-  /**
-   * @type {Array.<ol.Feature>}
-   * @private
-   */
-  this.features_ = [];
+/**
+ * Static send that creates a short lived instance of XhrIo to send the
+ * request.
+ * @see goog.net.XhrIo.cleanup
+ * @param {string|goog.Uri} url Uri to make request to.
+ * @param {?function(this:goog.net.XhrIo, ?)=} opt_callback Callback function
+ *     for when request is complete.
+ * @param {string=} opt_method Send method, default: GET.
+ * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}
+ *     opt_content Body data.
+ * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
+ *     request.
+ * @param {number=} opt_timeoutInterval Number of milliseconds after which an
+ *     incomplete request will be aborted; 0 means no timeout is set.
+ * @param {boolean=} opt_withCredentials Whether to send credentials with the
+ *     request. Default to false. See {@link goog.net.XhrIo#setWithCredentials}.
+ * @return {!goog.net.XhrIo} The sent XhrIo.
+ */
+goog.net.XhrIo.send = function(url, opt_callback, opt_method, opt_content,
+                               opt_headers, opt_timeoutInterval,
+                               opt_withCredentials) {
+  var x = new goog.net.XhrIo();
+  goog.net.XhrIo.sendInstances_.push(x);
+  if (opt_callback) {
+    x.listen(goog.net.EventType.COMPLETE, opt_callback);
+  }
+  x.listenOnce(goog.net.EventType.READY, x.cleanupSend_);
+  if (opt_timeoutInterval) {
+    x.setTimeoutInterval(opt_timeoutInterval);
+  }
+  if (opt_withCredentials) {
+    x.setWithCredentials(opt_withCredentials);
+  }
+  x.send(url, opt_method, opt_content, opt_headers);
+  return x;
+};
 
-  /**
-   * @type {ol.source.Vector}
-   * @private
-   */
-  this.source_ = options.source;
 
-  this.source_.on(goog.events.EventType.CHANGE,
-      ol.source.Cluster.prototype.onSourceChange_, this);
+/**
+ * Disposes all non-disposed instances of goog.net.XhrIo created by
+ * {@link goog.net.XhrIo.send}.
+ * {@link goog.net.XhrIo.send} cleans up the goog.net.XhrIo instance
+ * it creates when the request completes or fails.  However, if
+ * the request never completes, then the goog.net.XhrIo is not disposed.
+ * This can occur if the window is unloaded before the request completes.
+ * We could have {@link goog.net.XhrIo.send} return the goog.net.XhrIo
+ * it creates and make the client of {@link goog.net.XhrIo.send} be
+ * responsible for disposing it in this case.  However, this makes things
+ * significantly more complicated for the client, and the whole point
+ * of {@link goog.net.XhrIo.send} is that it's simple and easy to use.
+ * Clients of {@link goog.net.XhrIo.send} should call
+ * {@link goog.net.XhrIo.cleanup} when doing final
+ * cleanup on window unload.
+ */
+goog.net.XhrIo.cleanup = function() {
+  var instances = goog.net.XhrIo.sendInstances_;
+  while (instances.length) {
+    instances.pop().dispose();
+  }
 };
-goog.inherits(ol.source.Cluster, ol.source.Vector);
 
 
 /**
- * @param {ol.Extent} extent
- * @param {number} resolution
+ * Installs exception protection for all entry point introduced by
+ * goog.net.XhrIo instances which are not protected by
+ * {@link goog.debug.ErrorHandler#protectWindowSetTimeout},
+ * {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or
+ * {@link goog.events.protectBrowserEventEntryPoint}.
+ *
+ * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
+ *     protect the entry point(s).
  */
-ol.source.Cluster.prototype.loadFeatures = function(extent, resolution) {
-  if (resolution !== this.resolution_) {
-    this.clear();
-    this.resolution_ = resolution;
-    this.cluster_();
-    this.addFeatures(this.features_);
-  }
+goog.net.XhrIo.protectEntryPoints = function(errorHandler) {
+  goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ =
+      errorHandler.protectEntryPoint(
+          goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);
 };
 
 
 /**
- * handle the source changing
+ * Disposes of the specified goog.net.XhrIo created by
+ * {@link goog.net.XhrIo.send} and removes it from
+ * {@link goog.net.XhrIo.pendingStaticSendInstances_}.
  * @private
  */
-ol.source.Cluster.prototype.onSourceChange_ = function() {
-  this.clear();
-  this.cluster_();
-  this.addFeatures(this.features_);
-  this.dispatchChangeEvent();
+goog.net.XhrIo.prototype.cleanupSend_ = function() {
+  this.dispose();
+  goog.array.remove(goog.net.XhrIo.sendInstances_, this);
 };
 
 
 /**
- * @private
+ * Returns the number of milliseconds after which an incomplete request will be
+ * aborted, or 0 if no timeout is set.
+ * @return {number} Timeout interval in milliseconds.
  */
-ol.source.Cluster.prototype.cluster_ = function() {
-  if (!goog.isDef(this.resolution_)) {
-    return;
-  }
-  goog.array.clear(this.features_);
-  var extent = ol.extent.createEmpty();
-  var mapDistance = this.distance_ * this.resolution_;
-  var features = this.source_.getFeatures();
-
-  /**
-   * @type {Object.<string, boolean>}
-   */
-  var clustered = {};
-
-  for (var i = 0, ii = features.length; i < ii; i++) {
-    var feature = features[i];
-    if (!goog.object.containsKey(clustered, goog.getUid(feature).toString())) {
-      var geometry = feature.getGeometry();
-      goog.asserts.assert(geometry instanceof ol.geom.Point);
-      var coordinates = geometry.getCoordinates();
-      ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
-      ol.extent.buffer(extent, mapDistance, extent);
-
-      var neighbors = this.source_.getFeaturesInExtent(extent);
-      goog.asserts.assert(neighbors.length >= 1);
-      neighbors = goog.array.filter(neighbors, function(neighbor) {
-        var uid = goog.getUid(neighbor).toString();
-        if (!goog.object.containsKey(clustered, uid)) {
-          goog.object.set(clustered, uid, true);
-          return true;
-        } else {
-          return false;
-        }
-      });
-      this.features_.push(this.createCluster_(neighbors));
-    }
-  }
-  goog.asserts.assert(
-      goog.object.getCount(clustered) == this.source_.getFeatures().length);
+goog.net.XhrIo.prototype.getTimeoutInterval = function() {
+  return this.timeoutInterval_;
 };
 
 
 /**
- * @param {Array.<ol.Feature>} features Features
- * @return {ol.Feature}
- * @private
+ * Sets the number of milliseconds after which an incomplete request will be
+ * aborted and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no
+ * timeout is set.
+ * @param {number} ms Timeout interval in milliseconds; 0 means none.
  */
-ol.source.Cluster.prototype.createCluster_ = function(features) {
-  var length = features.length;
-  var centroid = [0, 0];
-  for (var i = 0; i < length; i++) {
-    var geometry = features[i].getGeometry();
-    goog.asserts.assert(geometry instanceof ol.geom.Point);
-    var coordinates = geometry.getCoordinates();
-    ol.coordinate.add(centroid, coordinates);
-  }
-  ol.coordinate.scale(centroid, 1 / length);
-
-  var cluster = new ol.Feature(new ol.geom.Point(centroid));
-  cluster.set('features', features);
-  return cluster;
+goog.net.XhrIo.prototype.setTimeoutInterval = function(ms) {
+  this.timeoutInterval_ = Math.max(0, ms);
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Common events for the network classes.
+ * Sets the desired type for the response. At time of writing, this is only
+ * supported in very recent versions of WebKit (10.0.612.1 dev and later).
+ *
+ * If this is used, the response may only be accessed via {@link #getResponse}.
+ *
+ * @param {goog.net.XhrIo.ResponseType} type The desired type for the response.
  */
-
-
-goog.provide('goog.net.EventType');
+goog.net.XhrIo.prototype.setResponseType = function(type) {
+  this.responseType_ = type;
+};
 
 
 /**
- * Event names for network events
- * @enum {string}
+ * Gets the desired type for the response.
+ * @return {goog.net.XhrIo.ResponseType} The desired type for the response.
  */
-goog.net.EventType = {
-  COMPLETE: 'complete',
-  SUCCESS: 'success',
-  ERROR: 'error',
-  ABORT: 'abort',
-  READY: 'ready',
-  READY_STATE_CHANGE: 'readystatechange',
-  TIMEOUT: 'timeout',
-  INCREMENTAL_DATA: 'incrementaldata',
-  PROGRESS: 'progress'
+goog.net.XhrIo.prototype.getResponseType = function() {
+  return this.responseType_;
 };
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview A timer class to which other classes and objects can
- * listen on.  This is only an abstraction above setInterval.
+ * Sets whether a "credentialed" request that is aware of cookie and
+ * authentication information should be made. This option is only supported by
+ * browsers that support HTTP Access Control. As of this writing, this option
+ * is not supported in IE.
  *
- * @see ../demos/timers.html
+ * @param {boolean} withCredentials Whether this should be a "credentialed"
+ *     request.
  */
+goog.net.XhrIo.prototype.setWithCredentials = function(withCredentials) {
+  this.withCredentials_ = withCredentials;
+};
 
-goog.provide('goog.Timer');
-
-goog.require('goog.events.EventTarget');
 
+/**
+ * Gets whether a "credentialed" request is to be sent.
+ * @return {boolean} The desired type for the response.
+ */
+goog.net.XhrIo.prototype.getWithCredentials = function() {
+  return this.withCredentials_;
+};
 
 
 /**
- * Class for handling timing events.
- *
- * @param {number=} opt_interval Number of ms between ticks (Default: 1ms).
- * @param {Object=} opt_timerObject  An object that has setTimeout, setInterval,
- *     clearTimeout and clearInterval (eg Window).
- * @constructor
- * @extends {goog.events.EventTarget}
+ * Instance send that actually uses XMLHttpRequest to make a server call.
+ * @param {string|goog.Uri} url Uri to make request to.
+ * @param {string=} opt_method Send method, default: GET.
+ * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}
+ *     opt_content Body data.
+ * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
+ *     request.
  */
-goog.Timer = function(opt_interval, opt_timerObject) {
-  goog.events.EventTarget.call(this);
+goog.net.XhrIo.prototype.send = function(url, opt_method, opt_content,
+                                         opt_headers) {
+  if (this.xhr_) {
+    throw Error('[goog.net.XhrIo] Object is active with another request=' +
+        this.lastUri_ + '; newUri=' + url);
+  }
 
-  /**
-   * Number of ms between ticks
-   * @type {number}
-   * @private
-   */
-  this.interval_ = opt_interval || 1;
+  var method = opt_method ? opt_method.toUpperCase() : 'GET';
 
-  /**
-   * An object that implements setTimeout, setInterval, clearTimeout and
-   * clearInterval. We default to the window object. Changing this on
-   * goog.Timer.prototype changes the object for all timer instances which can
-   * be useful if your environment has some other implementation of timers than
-   * the window object.
-   * @type {Object}
-   * @private
-   */
-  this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject;
+  this.lastUri_ = url;
+  this.lastError_ = '';
+  this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
+  this.lastMethod_ = method;
+  this.errorDispatched_ = false;
+  this.active_ = true;
 
-  /**
-   * Cached tick_ bound to the object for later use in the timer.
-   * @type {Function}
-   * @private
-   */
-  this.boundTick_ = goog.bind(this.tick_, this);
+  // Use the factory to create the XHR object and options
+  this.xhr_ = this.createXhr();
+  this.xhrOptions_ = this.xmlHttpFactory_ ?
+      this.xmlHttpFactory_.getOptions() : goog.net.XmlHttp.getOptions();
+
+  // Set up the onreadystatechange callback
+  this.xhr_.onreadystatechange = goog.bind(this.onReadyStateChange_, this);
 
   /**
-   * Firefox browser often fires the timer event sooner
-   * (sometimes MUCH sooner) than the requested timeout. So we
-   * compare the time to when the event was last fired, and
-   * reschedule if appropriate. See also goog.Timer.intervalScale
-   * @type {number}
-   * @private
+   * Try to open the XMLHttpRequest (always async), if an error occurs here it
+   * is generally permission denied
+   * @preserveTry
    */
-  this.last_ = goog.now();
-};
-goog.inherits(goog.Timer, goog.events.EventTarget);
-
-
-/**
- * Maximum timeout value.
- *
- * Timeout values too big to fit into a signed 32-bit integer may cause
- * overflow in FF, Safari, and Chrome, resulting in the timeout being
- * scheduled immediately.  It makes more sense simply not to schedule these
- * timeouts, since 24.8 days is beyond a reasonable expectation for the
- * browser to stay open.
- *
- * @type {number}
- * @private
- */
-goog.Timer.MAX_TIMEOUT_ = 2147483647;
+  try {
+    goog.log.fine(this.logger_, this.formatMsg_('Opening Xhr'));
+    this.inOpen_ = true;
+    this.xhr_.open(method, String(url), true);  // Always async!
+    this.inOpen_ = false;
+  } catch (err) {
+    goog.log.fine(this.logger_,
+        this.formatMsg_('Error opening Xhr: ' + err.message));
+    this.error_(goog.net.ErrorCode.EXCEPTION, err);
+    return;
+  }
 
+  // We can't use null since this won't allow requests with form data to have a
+  // content length specified which will cause some proxies to return a 411
+  // error.
+  var content = opt_content || '';
 
-/**
- * Whether this timer is enabled
- * @type {boolean}
- */
-goog.Timer.prototype.enabled = false;
+  var headers = this.headers.clone();
 
+  // Add headers specific to this request
+  if (opt_headers) {
+    goog.structs.forEach(opt_headers, function(value, key) {
+      headers.set(key, value);
+    });
+  }
 
-/**
- * An object that implements setTimout, setInterval, clearTimeout and
- * clearInterval. We default to the global object. Changing
- * goog.Timer.defaultTimerObject changes the object for all timer instances
- * which can be useful if your environment has some other implementation of
- * timers you'd like to use.
- * @type {Object}
- */
-goog.Timer.defaultTimerObject = goog.global;
+  // Find whether a content type header is set, ignoring case.
+  // HTTP header names are case-insensitive.  See:
+  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+  var contentTypeKey = goog.array.find(headers.getKeys(),
+      goog.net.XhrIo.isContentTypeHeader_);
 
+  var contentIsFormData = (goog.global['FormData'] &&
+      (content instanceof goog.global['FormData']));
+  if (goog.array.contains(goog.net.XhrIo.METHODS_WITH_FORM_DATA, method) &&
+      !contentTypeKey && !contentIsFormData) {
+    // For requests typically with form data, default to the url-encoded form
+    // content type unless this is a FormData request.  For FormData,
+    // the browser will automatically add a multipart/form-data content type
+    // with an appropriate multipart boundary.
+    headers.set(goog.net.XhrIo.CONTENT_TYPE_HEADER,
+                goog.net.XhrIo.FORM_CONTENT_TYPE);
+  }
 
-/**
- * A variable that controls the timer error correction. If the
- * timer is called before the requested interval times
- * intervalScale, which often happens on mozilla, the timer is
- * rescheduled. See also this.last_
- * @type {number}
- */
-goog.Timer.intervalScale = 0.8;
+  // Add the headers to the Xhr object
+  headers.forEach(function(value, key) {
+    this.xhr_.setRequestHeader(key, value);
+  }, this);
 
+  if (this.responseType_) {
+    this.xhr_.responseType = this.responseType_;
+  }
 
-/**
- * Variable for storing the result of setInterval
- * @type {?number}
- * @private
- */
-goog.Timer.prototype.timer_ = null;
+  if (goog.object.containsKey(this.xhr_, 'withCredentials')) {
+    this.xhr_.withCredentials = this.withCredentials_;
+  }
 
+  /**
+   * Try to send the request, or other wise report an error (404 not found).
+   * @preserveTry
+   */
+  try {
+    this.cleanUpTimeoutTimer_(); // Paranoid, should never be running.
+    if (this.timeoutInterval_ > 0) {
+      this.useXhr2Timeout_ = goog.net.XhrIo.shouldUseXhr2Timeout_(this.xhr_);
+      goog.log.fine(this.logger_, this.formatMsg_('Will abort after ' +
+          this.timeoutInterval_ + 'ms if incomplete, xhr2 ' +
+          this.useXhr2Timeout_));
+      if (this.useXhr2Timeout_) {
+        this.xhr_[goog.net.XhrIo.XHR2_TIMEOUT_] = this.timeoutInterval_;
+        this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] =
+            goog.bind(this.timeout_, this);
+      } else {
+        this.timeoutId_ = goog.Timer.callOnce(this.timeout_,
+            this.timeoutInterval_, this);
+      }
+    }
+    goog.log.fine(this.logger_, this.formatMsg_('Sending request'));
+    this.inSend_ = true;
+    this.xhr_.send(content);
+    this.inSend_ = false;
 
-/**
- * Gets the interval of the timer.
- * @return {number} interval Number of ms between ticks.
- */
-goog.Timer.prototype.getInterval = function() {
-  return this.interval_;
+  } catch (err) {
+    goog.log.fine(this.logger_, this.formatMsg_('Send error: ' + err.message));
+    this.error_(goog.net.ErrorCode.EXCEPTION, err);
+  }
 };
 
 
 /**
- * Sets the interval of the timer.
- * @param {number} interval Number of ms between ticks.
+ * Determines if the argument is an XMLHttpRequest that supports the level 2
+ * timeout value and event.
+ *
+ * Currently, FF 21.0 OS X has the fields but won't actually call the timeout
+ * handler.  Perhaps the confusion in the bug referenced below hasn't
+ * entirely been resolved.
+ *
+ * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=525816
+ *
+ * @param {!goog.net.XhrLike.OrNative} xhr The request.
+ * @return {boolean} True if the request supports level 2 timeout.
+ * @private
  */
-goog.Timer.prototype.setInterval = function(interval) {
-  this.interval_ = interval;
-  if (this.timer_ && this.enabled) {
-    // Stop and then start the timer to reset the interval.
-    this.stop();
-    this.start();
-  } else if (this.timer_) {
-    this.stop();
-  }
+goog.net.XhrIo.shouldUseXhr2Timeout_ = function(xhr) {
+  return goog.userAgent.IE &&
+      goog.userAgent.isVersionOrHigher(9) &&
+      goog.isNumber(xhr[goog.net.XhrIo.XHR2_TIMEOUT_]) &&
+      goog.isDef(xhr[goog.net.XhrIo.XHR2_ON_TIMEOUT_]);
 };
 
 
 /**
- * Callback for the setTimeout used by the timer
+ * @param {string} header An HTTP header key.
+ * @return {boolean} Whether the key is a content type header (ignoring
+ *     case.
  * @private
  */
-goog.Timer.prototype.tick_ = function() {
-  if (this.enabled) {
-    var elapsed = goog.now() - this.last_;
-    if (elapsed > 0 &&
-        elapsed < this.interval_ * goog.Timer.intervalScale) {
-      this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
-          this.interval_ - elapsed);
-      return;
-    }
-
-    // Prevents setInterval from registering a duplicate timeout when called
-    // in the timer event handler.
-    if (this.timer_) {
-      this.timerObject_.clearTimeout(this.timer_);
-      this.timer_ = null;
-    }
-
-    this.dispatchTick();
-    // The timer could be stopped in the timer event handler.
-    if (this.enabled) {
-      this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
-          this.interval_);
-      this.last_ = goog.now();
-    }
-  }
+goog.net.XhrIo.isContentTypeHeader_ = function(header) {
+  return goog.string.caseInsensitiveEquals(
+      goog.net.XhrIo.CONTENT_TYPE_HEADER, header);
 };
 
 
 /**
- * Dispatches the TICK event. This is its own method so subclasses can override.
+ * Creates a new XHR object.
+ * @return {!goog.net.XhrLike.OrNative} The newly created XHR object.
+ * @protected
  */
-goog.Timer.prototype.dispatchTick = function() {
-  this.dispatchEvent(goog.Timer.TICK);
+goog.net.XhrIo.prototype.createXhr = function() {
+  return this.xmlHttpFactory_ ?
+      this.xmlHttpFactory_.createInstance() : goog.net.XmlHttp();
 };
 
 
 /**
- * Starts the timer.
+ * The request didn't complete after {@link goog.net.XhrIo#timeoutInterval_}
+ * milliseconds; raises a {@link goog.net.EventType.TIMEOUT} event and aborts
+ * the request.
+ * @private
  */
-goog.Timer.prototype.start = function() {
-  this.enabled = true;
-
-  // If there is no interval already registered, start it now
-  if (!this.timer_) {
-    // IMPORTANT!
-    // window.setInterval in FireFox has a bug - it fires based on
-    // absolute time, rather than on relative time. What this means
-    // is that if a computer is sleeping/hibernating for 24 hours
-    // and the timer interval was configured to fire every 1000ms,
-    // then after the PC wakes up the timer will fire, in rapid
-    // succession, 3600*24 times.
-    // This bug is described here and is already fixed, but it will
-    // take time to propagate, so for now I am switching this over
-    // to setTimeout logic.
-    //     https://bugzilla.mozilla.org/show_bug.cgi?id=376643
-    //
-    this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
-        this.interval_);
-    this.last_ = goog.now();
+goog.net.XhrIo.prototype.timeout_ = function() {
+  if (typeof goog == 'undefined') {
+    // If goog is undefined then the callback has occurred as the application
+    // is unloading and will error.  Thus we let it silently fail.
+  } else if (this.xhr_) {
+    this.lastError_ = 'Timed out after ' + this.timeoutInterval_ +
+                      'ms, aborting';
+    this.lastErrorCode_ = goog.net.ErrorCode.TIMEOUT;
+    goog.log.fine(this.logger_, this.formatMsg_(this.lastError_));
+    this.dispatchEvent(goog.net.EventType.TIMEOUT);
+    this.abort(goog.net.ErrorCode.TIMEOUT);
   }
 };
 
 
 /**
- * Stops the timer.
+ * Something errorred, so inactivate, fire error callback and clean up
+ * @param {goog.net.ErrorCode} errorCode The error code.
+ * @param {Error} err The error object.
+ * @private
  */
-goog.Timer.prototype.stop = function() {
-  this.enabled = false;
-  if (this.timer_) {
-    this.timerObject_.clearTimeout(this.timer_);
-    this.timer_ = null;
+goog.net.XhrIo.prototype.error_ = function(errorCode, err) {
+  this.active_ = false;
+  if (this.xhr_) {
+    this.inAbort_ = true;
+    this.xhr_.abort();  // Ensures XHR isn't hung (FF)
+    this.inAbort_ = false;
   }
-};
-
-
-/** @override */
-goog.Timer.prototype.disposeInternal = function() {
-  goog.Timer.superClass_.disposeInternal.call(this);
-  this.stop();
-  delete this.timerObject_;
+  this.lastError_ = err;
+  this.lastErrorCode_ = errorCode;
+  this.dispatchErrors_();
+  this.cleanUpXhr_();
 };
 
 
 /**
- * Constant for the timer's event type
- * @type {string}
+ * Dispatches COMPLETE and ERROR in case of an error. This ensures that we do
+ * not dispatch multiple error events.
+ * @private
  */
-goog.Timer.TICK = 'tick';
+goog.net.XhrIo.prototype.dispatchErrors_ = function() {
+  if (!this.errorDispatched_) {
+    this.errorDispatched_ = true;
+    this.dispatchEvent(goog.net.EventType.COMPLETE);
+    this.dispatchEvent(goog.net.EventType.ERROR);
+  }
+};
 
 
 /**
- * Calls the given function once, after the optional pause.
- *
- * The function is always called asynchronously, even if the delay is 0. This
- * is a common trick to schedule a function to run after a batch of browser
- * event processing.
- *
- * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function
- *     or object that has a handleEvent method.
- * @param {number=} opt_delay Milliseconds to wait; default is 0.
- * @param {SCOPE=} opt_handler Object in whose scope to call the listener.
- * @return {number} A handle to the timer ID.
- * @template SCOPE
+ * Abort the current XMLHttpRequest
+ * @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use -
+ *     defaults to ABORT.
  */
-goog.Timer.callOnce = function(listener, opt_delay, opt_handler) {
-  if (goog.isFunction(listener)) {
-    if (opt_handler) {
-      listener = goog.bind(listener, opt_handler);
-    }
-  } else if (listener && typeof listener.handleEvent == 'function') {
-    // using typeof to prevent strict js warning
-    listener = goog.bind(listener.handleEvent, listener);
-  } else {
-    throw Error('Invalid listener argument');
-  }
-
-  if (opt_delay > goog.Timer.MAX_TIMEOUT_) {
-    // Timeouts greater than MAX_INT return immediately due to integer
-    // overflow in many browsers.  Since MAX_INT is 24.8 days, just don't
-    // schedule anything at all.
-    return -1;
-  } else {
-    return goog.Timer.defaultTimerObject.setTimeout(
-        listener, opt_delay || 0);
+goog.net.XhrIo.prototype.abort = function(opt_failureCode) {
+  if (this.xhr_ && this.active_) {
+    goog.log.fine(this.logger_, this.formatMsg_('Aborting'));
+    this.active_ = false;
+    this.inAbort_ = true;
+    this.xhr_.abort();
+    this.inAbort_ = false;
+    this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT;
+    this.dispatchEvent(goog.net.EventType.COMPLETE);
+    this.dispatchEvent(goog.net.EventType.ABORT);
+    this.cleanUpXhr_();
   }
 };
 
 
 /**
- * Clears a timeout initiated by callOnce
- * @param {?number} timerId a timer ID.
+ * Nullifies all callbacks to reduce risks of leaks.
+ * @override
+ * @protected
  */
-goog.Timer.clear = function(timerId) {
-  goog.Timer.defaultTimerObject.clearTimeout(timerId);
+goog.net.XhrIo.prototype.disposeInternal = function() {
+  if (this.xhr_) {
+    // We explicitly do not call xhr_.abort() unless active_ is still true.
+    // This is to avoid unnecessarily aborting a successful request when
+    // dispose() is called in a callback triggered by a complete response, but
+    // in which browser cleanup has not yet finished.
+    // (See http://b/issue?id=1684217.)
+    if (this.active_) {
+      this.active_ = false;
+      this.inAbort_ = true;
+      this.xhr_.abort();
+      this.inAbort_ = false;
+    }
+    this.cleanUpXhr_(true);
+  }
+
+  goog.net.XhrIo.base(this, 'disposeInternal');
 };
 
-// Copyright 2007 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Error codes shared between goog.net.IframeIo and
- * goog.net.XhrIo.
+ * Internal handler for the XHR object's readystatechange event.  This method
+ * checks the status and the readystate and fires the correct callbacks.
+ * If the request has ended, the handlers are cleaned up and the XHR object is
+ * nullified.
+ * @private
  */
-
-goog.provide('goog.net.ErrorCode');
+goog.net.XhrIo.prototype.onReadyStateChange_ = function() {
+  if (this.isDisposed()) {
+    // This method is the target of an untracked goog.Timer.callOnce().
+    return;
+  }
+  if (!this.inOpen_ && !this.inSend_ && !this.inAbort_) {
+    // Were not being called from within a call to this.xhr_.send
+    // this.xhr_.abort, or this.xhr_.open, so this is an entry point
+    this.onReadyStateChangeEntryPoint_();
+  } else {
+    this.onReadyStateChangeHelper_();
+  }
+};
 
 
 /**
- * Error codes
- * @enum {number}
+ * Used to protect the onreadystatechange handler entry point.  Necessary
+ * as {#onReadyStateChange_} maybe called from within send or abort, this
+ * method is only called when {#onReadyStateChange_} is called as an
+ * entry point.
+ * {@see #protectEntryPoints}
+ * @private
  */
-goog.net.ErrorCode = {
-
-  /**
-   * There is no error condition.
-   */
-  NO_ERROR: 0,
-
-  /**
-   * The most common error from iframeio, unfortunately, is that the browser
-   * responded with an error page that is classed as a different domain. The
-   * situations, are when a browser error page  is shown -- 404, access denied,
-   * DNS failure, connection reset etc.)
-   *
-   */
-  ACCESS_DENIED: 1,
-
-  /**
-   * Currently the only case where file not found will be caused is when the
-   * code is running on the local file system and a non-IE browser makes a
-   * request to a file that doesn't exist.
-   */
-  FILE_NOT_FOUND: 2,
-
-  /**
-   * If Firefox shows a browser error page, such as a connection reset by
-   * server or access denied, then it will fail silently without the error or
-   * load handlers firing.
-   */
-  FF_SILENT_ERROR: 3,
-
-  /**
-   * Custom error provided by the client through the error check hook.
-   */
-  CUSTOM_ERROR: 4,
-
-  /**
-   * Exception was thrown while processing the request.
-   */
-  EXCEPTION: 5,
-
-  /**
-   * The Http response returned a non-successful http status code.
-   */
-  HTTP_ERROR: 6,
-
-  /**
-   * The request was aborted.
-   */
-  ABORT: 7,
-
-  /**
-   * The request timed out.
-   */
-  TIMEOUT: 8,
-
-  /**
-   * The resource is not available offline.
-   */
-  OFFLINE: 9
+goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = function() {
+  this.onReadyStateChangeHelper_();
 };
 
 
 /**
- * Returns a friendly error message for an error code. These messages are for
- * debugging and are not localized.
- * @param {goog.net.ErrorCode} errorCode An error code.
- * @return {string} A message for debugging.
+ * Helper for {@link #onReadyStateChange_}.  This is used so that
+ * entry point calls to {@link #onReadyStateChange_} can be routed through
+ * {@link #onReadyStateChangeEntryPoint_}.
+ * @private
  */
-goog.net.ErrorCode.getDebugMessage = function(errorCode) {
-  switch (errorCode) {
-    case goog.net.ErrorCode.NO_ERROR:
-      return 'No Error';
-
-    case goog.net.ErrorCode.ACCESS_DENIED:
-      return 'Access denied to content document';
-
-    case goog.net.ErrorCode.FILE_NOT_FOUND:
-      return 'File not found';
+goog.net.XhrIo.prototype.onReadyStateChangeHelper_ = function() {
+  if (!this.active_) {
+    // can get called inside abort call
+    return;
+  }
 
-    case goog.net.ErrorCode.FF_SILENT_ERROR:
-      return 'Firefox silently errored';
+  if (typeof goog == 'undefined') {
+    // NOTE(user): If goog is undefined then the callback has occurred as the
+    // application is unloading and will error.  Thus we let it silently fail.
 
-    case goog.net.ErrorCode.CUSTOM_ERROR:
-      return 'Application custom error';
+  } else if (
+      this.xhrOptions_[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] &&
+      this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE &&
+      this.getStatus() == 2) {
+    // NOTE(user): In IE if send() errors on a *local* request the readystate
+    // is still changed to COMPLETE.  We need to ignore it and allow the
+    // try/catch around send() to pick up the error.
+    goog.log.fine(this.logger_, this.formatMsg_(
+        'Local request error detected and ignored'));
 
-    case goog.net.ErrorCode.EXCEPTION:
-      return 'An exception occurred';
+  } else {
 
-    case goog.net.ErrorCode.HTTP_ERROR:
-      return 'Http response at 400 or 500 level';
+    // In IE when the response has been cached we sometimes get the callback
+    // from inside the send call and this usually breaks code that assumes that
+    // XhrIo is asynchronous.  If that is the case we delay the callback
+    // using a timer.
+    if (this.inSend_ &&
+        this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE) {
+      goog.Timer.callOnce(this.onReadyStateChange_, 0, this);
+      return;
+    }
 
-    case goog.net.ErrorCode.ABORT:
-      return 'Request was aborted';
+    this.dispatchEvent(goog.net.EventType.READY_STATE_CHANGE);
 
-    case goog.net.ErrorCode.TIMEOUT:
-      return 'Request timed out';
+    // readyState indicates the transfer has finished
+    if (this.isComplete()) {
+      goog.log.fine(this.logger_, this.formatMsg_('Request complete'));
 
-    case goog.net.ErrorCode.OFFLINE:
-      return 'The resource is not available offline';
+      this.active_ = false;
 
-    default:
-      return 'Unrecognized error code';
+      try {
+        // Call the specific callbacks for success or failure. Only call the
+        // success if the status is 200 (HTTP_OK) or 304 (HTTP_CACHED)
+        if (this.isSuccess()) {
+          this.dispatchEvent(goog.net.EventType.COMPLETE);
+          this.dispatchEvent(goog.net.EventType.SUCCESS);
+        } else {
+          this.lastErrorCode_ = goog.net.ErrorCode.HTTP_ERROR;
+          this.lastError_ =
+              this.getStatusText() + ' [' + this.getStatus() + ']';
+          this.dispatchErrors_();
+        }
+      } finally {
+        this.cleanUpXhr_();
+      }
+    }
   }
 };
 
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview Constants for HTTP status codes.
- */
-
-goog.provide('goog.net.HttpStatus');
-
 
 /**
- * HTTP Status Codes defined in RFC 2616.
- * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- * @enum {number}
+ * Remove the listener to protect against leaks, and nullify the XMLHttpRequest
+ * object.
+ * @param {boolean=} opt_fromDispose If this is from the dispose (don't want to
+ *     fire any events).
+ * @private
  */
-goog.net.HttpStatus = {
-  // Informational 1xx
-  CONTINUE: 100,
-  SWITCHING_PROTOCOLS: 101,
-
-  // Successful 2xx
-  OK: 200,
-  CREATED: 201,
-  ACCEPTED: 202,
-  NON_AUTHORITATIVE_INFORMATION: 203,
-  NO_CONTENT: 204,
-  RESET_CONTENT: 205,
-  PARTIAL_CONTENT: 206,
-
-  // Redirection 3xx
-  MULTIPLE_CHOICES: 300,
-  MOVED_PERMANENTLY: 301,
-  FOUND: 302,
-  SEE_OTHER: 303,
-  NOT_MODIFIED: 304,
-  USE_PROXY: 305,
-  TEMPORARY_REDIRECT: 307,
+goog.net.XhrIo.prototype.cleanUpXhr_ = function(opt_fromDispose) {
+  if (this.xhr_) {
+    // Cancel any pending timeout event handler.
+    this.cleanUpTimeoutTimer_();
 
-  // Client Error 4xx
-  BAD_REQUEST: 400,
-  UNAUTHORIZED: 401,
-  PAYMENT_REQUIRED: 402,
-  FORBIDDEN: 403,
-  NOT_FOUND: 404,
-  METHOD_NOT_ALLOWED: 405,
-  NOT_ACCEPTABLE: 406,
-  PROXY_AUTHENTICATION_REQUIRED: 407,
-  REQUEST_TIMEOUT: 408,
-  CONFLICT: 409,
-  GONE: 410,
-  LENGTH_REQUIRED: 411,
-  PRECONDITION_FAILED: 412,
-  REQUEST_ENTITY_TOO_LARGE: 413,
-  REQUEST_URI_TOO_LONG: 414,
-  UNSUPPORTED_MEDIA_TYPE: 415,
-  REQUEST_RANGE_NOT_SATISFIABLE: 416,
-  EXPECTATION_FAILED: 417,
+    // Save reference so we can mark it as closed after the READY event.  The
+    // READY event may trigger another request, thus we must nullify this.xhr_
+    var xhr = this.xhr_;
+    var clearedOnReadyStateChange =
+        this.xhrOptions_[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] ?
+            goog.nullFunction : null;
+    this.xhr_ = null;
+    this.xhrOptions_ = null;
 
-  // Server Error 5xx
-  INTERNAL_SERVER_ERROR: 500,
-  NOT_IMPLEMENTED: 501,
-  BAD_GATEWAY: 502,
-  SERVICE_UNAVAILABLE: 503,
-  GATEWAY_TIMEOUT: 504,
-  HTTP_VERSION_NOT_SUPPORTED: 505,
+    if (!opt_fromDispose) {
+      this.dispatchEvent(goog.net.EventType.READY);
+    }
 
-  /*
-   * IE returns this code for 204 due to its use of URLMon, which returns this
-   * code for 'Operation Aborted'. The status text is 'Unknown', the response
-   * headers are ''. Known to occur on IE 6 on XP through IE9 on Win7.
-   */
-  QUIRK_IE_NO_CONTENT: 1223
+    try {
+      // NOTE(user): Not nullifying in FireFox can still leak if the callbacks
+      // are defined in the same scope as the instance of XhrIo. But, IE doesn't
+      // allow you to set the onreadystatechange to NULL so nullFunction is
+      // used.
+      xhr.onreadystatechange = clearedOnReadyStateChange;
+    } catch (e) {
+      // This seems to occur with a Gears HTTP request. Delayed the setting of
+      // this onreadystatechange until after READY is sent out and catching the
+      // error to see if we can track down the problem.
+      goog.log.error(this.logger_,
+          'Problem encountered resetting onreadystatechange: ' + e.message);
+    }
+  }
 };
 
 
 /**
- * Returns whether the given status should be considered successful.
- *
- * Successful codes are OK (200), CREATED (201), ACCEPTED (202),
- * NO CONTENT (204), PARTIAL CONTENT (206), NOT MODIFIED (304),
- * and IE's no content code (1223).
- *
- * @param {number} status The status code to test.
- * @return {boolean} Whether the status code should be considered successful.
+ * Make sure the timeout timer isn't running.
+ * @private
  */
-goog.net.HttpStatus.isSuccess = function(status) {
-  switch (status) {
-    case goog.net.HttpStatus.OK:
-    case goog.net.HttpStatus.CREATED:
-    case goog.net.HttpStatus.ACCEPTED:
-    case goog.net.HttpStatus.NO_CONTENT:
-    case goog.net.HttpStatus.PARTIAL_CONTENT:
-    case goog.net.HttpStatus.NOT_MODIFIED:
-    case goog.net.HttpStatus.QUIRK_IE_NO_CONTENT:
-      return true;
-
-    default:
-      return false;
+goog.net.XhrIo.prototype.cleanUpTimeoutTimer_ = function() {
+  if (this.xhr_ && this.useXhr2Timeout_) {
+    this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] = null;
+  }
+  if (goog.isNumber(this.timeoutId_)) {
+    goog.Timer.clear(this.timeoutId_);
+    this.timeoutId_ = null;
   }
 };
 
-// Copyright 2013 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-goog.provide('goog.net.XhrLike');
 
+/**
+ * @return {boolean} Whether there is an active request.
+ */
+goog.net.XhrIo.prototype.isActive = function() {
+  return !!this.xhr_;
+};
 
 
 /**
- * Interface for the common parts of XMLHttpRequest.
- *
- * Mostly copied from externs/w3c_xml.js.
- *
- * @interface
- * @see http://www.w3.org/TR/XMLHttpRequest/
+ * @return {boolean} Whether the request has completed.
  */
-goog.net.XhrLike = function() {};
+goog.net.XhrIo.prototype.isComplete = function() {
+  return this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE;
+};
 
 
 /**
- * Typedef that refers to either native or custom-implemented XHR objects.
- * @typedef {!goog.net.XhrLike|!XMLHttpRequest}
+ * @return {boolean} Whether the request completed with a success.
  */
-goog.net.XhrLike.OrNative;
+goog.net.XhrIo.prototype.isSuccess = function() {
+  var status = this.getStatus();
+  // A zero status code is considered successful for local files.
+  return goog.net.HttpStatus.isSuccess(status) ||
+      status === 0 && !this.isLastUriEffectiveSchemeHttp_();
+};
 
 
 /**
- * @type {function()|null|undefined}
- * @see http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange
+ * @return {boolean} whether the effective scheme of the last URI that was
+ *     fetched was 'http' or 'https'.
+ * @private
  */
-goog.net.XhrLike.prototype.onreadystatechange;
+goog.net.XhrIo.prototype.isLastUriEffectiveSchemeHttp_ = function() {
+  var scheme = goog.uri.utils.getEffectiveScheme(String(this.lastUri_));
+  return goog.net.XhrIo.HTTP_SCHEME_PATTERN.test(scheme);
+};
 
 
 /**
- * @type {string}
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
+ * Get the readystate from the Xhr object
+ * Will only return correct result when called from the context of a callback
+ * @return {goog.net.XmlHttp.ReadyState} goog.net.XmlHttp.ReadyState.*.
  */
-goog.net.XhrLike.prototype.responseText;
+goog.net.XhrIo.prototype.getReadyState = function() {
+  return this.xhr_ ?
+      /** @type {goog.net.XmlHttp.ReadyState} */ (this.xhr_.readyState) :
+      goog.net.XmlHttp.ReadyState.UNINITIALIZED;
+};
 
 
 /**
- * @type {Document}
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute
+ * Get the status from the Xhr object
+ * Will only return correct result when called from the context of a callback
+ * @return {number} Http status.
  */
-goog.net.XhrLike.prototype.responseXML;
+goog.net.XhrIo.prototype.getStatus = function() {
+  /**
+   * IE doesn't like you checking status until the readystate is greater than 2
+   * (i.e. it is receiving or complete).  The try/catch is used for when the
+   * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_.
+   * @preserveTry
+   */
+  try {
+    return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ?
+        this.xhr_.status : -1;
+  } catch (e) {
+    return -1;
+  }
+};
 
 
 /**
- * @type {number}
- * @see http://www.w3.org/TR/XMLHttpRequest/#readystate
+ * Get the status text from the Xhr object
+ * Will only return correct result when called from the context of a callback
+ * @return {string} Status text.
  */
-goog.net.XhrLike.prototype.readyState;
+goog.net.XhrIo.prototype.getStatusText = function() {
+  /**
+   * IE doesn't like you checking status until the readystate is greater than 2
+   * (i.e. it is recieving or complete).  The try/catch is used for when the
+   * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_.
+   * @preserveTry
+   */
+  try {
+    return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ?
+        this.xhr_.statusText : '';
+  } catch (e) {
+    goog.log.fine(this.logger_, 'Can not get status: ' + e.message);
+    return '';
+  }
+};
 
 
 /**
- * @type {number}
- * @see http://www.w3.org/TR/XMLHttpRequest/#status
+ * Get the last Uri that was requested
+ * @return {string} Last Uri.
  */
-goog.net.XhrLike.prototype.status;
+goog.net.XhrIo.prototype.getLastUri = function() {
+  return String(this.lastUri_);
+};
 
 
 /**
- * @type {string}
- * @see http://www.w3.org/TR/XMLHttpRequest/#statustext
+ * Get the response text from the Xhr object
+ * Will only return correct result when called from the context of a callback.
+ * @return {string} Result from the server, or '' if no result available.
  */
-goog.net.XhrLike.prototype.statusText;
+goog.net.XhrIo.prototype.getResponseText = function() {
+  /** @preserveTry */
+  try {
+    return this.xhr_ ? this.xhr_.responseText : '';
+  } catch (e) {
+    // http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
+    // states that responseText should return '' (and responseXML null)
+    // when the state is not LOADING or DONE. Instead, IE can
+    // throw unexpected exceptions, for example when a request is aborted
+    // or no data is available yet.
+    goog.log.fine(this.logger_, 'Can not get responseText: ' + e.message);
+    return '';
+  }
+};
 
 
 /**
- * @param {string} method
- * @param {string} url
- * @param {?boolean=} opt_async
- * @param {?string=} opt_user
- * @param {?string=} opt_password
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
+ * Get the response body from the Xhr object. This property is only available
+ * in IE since version 7 according to MSDN:
+ * http://msdn.microsoft.com/en-us/library/ie/ms534368(v=vs.85).aspx
+ * Will only return correct result when called from the context of a callback.
+ *
+ * One option is to construct a VBArray from the returned object and convert
+ * it to a JavaScript array using the toArray method:
+ * {@code (new window['VBArray'](xhrIo.getResponseBody())).toArray()}
+ * This will result in an array of numbers in the range of [0..255]
+ *
+ * Another option is to use the VBScript CStr method to convert it into a
+ * string as outlined in http://stackoverflow.com/questions/1919972
+ *
+ * @return {Object} Binary result from the server or null if not available.
  */
-goog.net.XhrLike.prototype.open = function(method, url, opt_async, opt_user,
-    opt_password) {};
+goog.net.XhrIo.prototype.getResponseBody = function() {
+  /** @preserveTry */
+  try {
+    if (this.xhr_ && 'responseBody' in this.xhr_) {
+      return this.xhr_['responseBody'];
+    }
+  } catch (e) {
+    // IE can throw unexpected exceptions, for example when a request is aborted
+    // or no data is yet available.
+    goog.log.fine(this.logger_, 'Can not get responseBody: ' + e.message);
+  }
+  return null;
+};
 
 
 /**
- * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} opt_data
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-send()-method
+ * Get the response XML from the Xhr object
+ * Will only return correct result when called from the context of a callback.
+ * @return {Document} The DOM Document representing the XML file, or null
+ * if no result available.
  */
-goog.net.XhrLike.prototype.send = function(opt_data) {};
+goog.net.XhrIo.prototype.getResponseXml = function() {
+  /** @preserveTry */
+  try {
+    return this.xhr_ ? this.xhr_.responseXML : null;
+  } catch (e) {
+    goog.log.fine(this.logger_, 'Can not get responseXML: ' + e.message);
+    return null;
+  }
+};
 
 
 /**
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method
+ * Get the response and evaluates it as JSON from the Xhr object
+ * Will only return correct result when called from the context of a callback
+ * @param {string=} opt_xssiPrefix Optional XSSI prefix string to use for
+ *     stripping of the response before parsing. This needs to be set only if
+ *     your backend server prepends the same prefix string to the JSON response.
+ * @return {Object|undefined} JavaScript object.
  */
-goog.net.XhrLike.prototype.abort = function() {};
+goog.net.XhrIo.prototype.getResponseJson = function(opt_xssiPrefix) {
+  if (!this.xhr_) {
+    return undefined;
+  }
 
+  var responseText = this.xhr_.responseText;
+  if (opt_xssiPrefix && responseText.indexOf(opt_xssiPrefix) == 0) {
+    responseText = responseText.substring(opt_xssiPrefix.length);
+  }
 
-/**
- * @param {string} header
- * @param {string} value
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
- */
-goog.net.XhrLike.prototype.setRequestHeader = function(header, value) {};
+  return goog.json.parse(responseText);
+};
 
 
 /**
- * @param {string} header
- * @return {string}
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
+ * Get the response as the type specificed by {@link #setResponseType}. At time
+ * of writing, this is only directly supported in very recent versions of WebKit
+ * (10.0.612.1 dev and later). If the field is not supported directly, we will
+ * try to emulate it.
+ *
+ * Emulating the response means following the rules laid out at
+ * http://www.w3.org/TR/XMLHttpRequest/#the-response-attribute
+ *
+ * On browsers with no support for this (Chrome < 10, Firefox < 4, etc), only
+ * response types of DEFAULT or TEXT may be used, and the response returned will
+ * be the text response.
+ *
+ * On browsers with Mozilla's draft support for array buffers (Firefox 4, 5),
+ * only response types of DEFAULT, TEXT, and ARRAY_BUFFER may be used, and the
+ * response returned will be either the text response or the Mozilla
+ * implementation of the array buffer response.
+ *
+ * On browsers will full support, any valid response type supported by the
+ * browser may be used, and the response provided by the browser will be
+ * returned.
+ *
+ * @return {*} The response.
  */
-goog.net.XhrLike.prototype.getResponseHeader = function(header) {};
+goog.net.XhrIo.prototype.getResponse = function() {
+  /** @preserveTry */
+  try {
+    if (!this.xhr_) {
+      return null;
+    }
+    if ('response' in this.xhr_) {
+      return this.xhr_.response;
+    }
+    switch (this.responseType_) {
+      case goog.net.XhrIo.ResponseType.DEFAULT:
+      case goog.net.XhrIo.ResponseType.TEXT:
+        return this.xhr_.responseText;
+        // DOCUMENT and BLOB don't need to be handled here because they are
+        // introduced in the same spec that adds the .response field, and would
+        // have been caught above.
+        // ARRAY_BUFFER needs an implementation for Firefox 4, where it was
+        // implemented using a draft spec rather than the final spec.
+      case goog.net.XhrIo.ResponseType.ARRAY_BUFFER:
+        if ('mozResponseArrayBuffer' in this.xhr_) {
+          return this.xhr_.mozResponseArrayBuffer;
+        }
+    }
+    // Fell through to a response type that is not supported on this browser.
+    goog.log.error(this.logger_,
+        'Response type ' + this.responseType_ + ' is not ' +
+        'supported on this browser');
+    return null;
+  } catch (e) {
+    goog.log.fine(this.logger_, 'Can not get response: ' + e.message);
+    return null;
+  }
+};
 
 
 /**
- * @return {string}
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
+ * Get the value of the response-header with the given name from the Xhr object
+ * Will only return correct result when called from the context of a callback
+ * and the request has completed
+ * @param {string} key The name of the response-header to retrieve.
+ * @return {string|undefined} The value of the response-header named key.
  */
-goog.net.XhrLike.prototype.getAllResponseHeaders = function() {};
+goog.net.XhrIo.prototype.getResponseHeader = function(key) {
+  return this.xhr_ && this.isComplete() ?
+      this.xhr_.getResponseHeader(key) : undefined;
+};
 
-// Copyright 2010 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Interface for a factory for creating XMLHttpRequest objects
- * and metadata about them.
- * @author dbk@google.com (David Barrett-Kahn)
+ * Gets the text of all the headers in the response.
+ * Will only return correct result when called from the context of a callback
+ * and the request has completed.
+ * @return {string} The value of the response headers or empty string.
  */
-
-goog.provide('goog.net.XmlHttpFactory');
-
-/** @suppress {extraRequire} Typedef. */
-goog.require('goog.net.XhrLike');
-
+goog.net.XhrIo.prototype.getAllResponseHeaders = function() {
+  return this.xhr_ && this.isComplete() ?
+      this.xhr_.getAllResponseHeaders() : '';
+};
 
 
 /**
- * Abstract base class for an XmlHttpRequest factory.
- * @constructor
+ * Returns all response headers as a key-value map.
+ * Multiple values for the same header key can be combined into one,
+ * separated by a comma and a space.
+ * Note that the native getResponseHeader method for retrieving a single header
+ * does a case insensitive match on the header name. This method does not
+ * include any case normalization logic, it will just return a key-value
+ * representation of the headers.
+ * See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
+ * @return {!Object<string, string>} An object with the header keys as keys
+ *     and header values as values.
  */
-goog.net.XmlHttpFactory = function() {
+goog.net.XhrIo.prototype.getResponseHeaders = function() {
+  var headersObject = {};
+  var headersArray = this.getAllResponseHeaders().split('\r\n');
+  for (var i = 0; i < headersArray.length; i++) {
+    if (goog.string.isEmptyOrWhitespace(headersArray[i])) {
+      continue;
+    }
+    var keyValue = goog.string.splitLimit(headersArray[i], ': ', 2);
+    if (headersObject[keyValue[0]]) {
+      headersObject[keyValue[0]] += ', ' + keyValue[1];
+    } else {
+      headersObject[keyValue[0]] = keyValue[1];
+    }
+  }
+  return headersObject;
 };
 
 
 /**
- * Cache of options - we only actually call internalGetOptions once.
- * @type {Object}
- * @private
+ * Get the last error message
+ * @return {goog.net.ErrorCode} Last error code.
  */
-goog.net.XmlHttpFactory.prototype.cachedOptions_ = null;
+goog.net.XhrIo.prototype.getLastErrorCode = function() {
+  return this.lastErrorCode_;
+};
 
 
 /**
- * @return {!goog.net.XhrLike.OrNative} A new XhrLike instance.
+ * Get the last error message
+ * @return {string} Last error message.
  */
-goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod;
+goog.net.XhrIo.prototype.getLastError = function() {
+  return goog.isString(this.lastError_) ? this.lastError_ :
+      String(this.lastError_);
+};
 
 
 /**
- * @return {Object} Options describing how xhr objects obtained from this
- *     factory should be used.
+ * Adds the last method, status and URI to the message.  This is used to add
+ * this information to the logging calls.
+ * @param {string} msg The message text that we want to add the extra text to.
+ * @return {string} The message with the extra text appended.
+ * @private
  */
-goog.net.XmlHttpFactory.prototype.getOptions = function() {
-  return this.cachedOptions_ ||
-      (this.cachedOptions_ = this.internalGetOptions());
+goog.net.XhrIo.prototype.formatMsg_ = function(msg) {
+  return msg + ' [' + this.lastMethod_ + ' ' + this.lastUri_ + ' ' +
+      this.getStatus() + ']';
 };
 
 
-/**
- * Override this method in subclasses to preserve the caching offered by
- * getOptions().
- * @return {Object} Options describing how xhr objects obtained from this
- *     factory should be used.
- * @protected
- */
-goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod;
-
-// Copyright 2010 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+// Register the xhr handler as an entry point, so that
+// it can be monitored for exception handling, etc.
+goog.debug.entryPointRegistry.register(
+    /**
+     * @param {function(!Function): !Function} transformer The transforming
+     *     function.
+     */
+    function(transformer) {
+      goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ =
+          transformer(goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);
+    });
 
-/**
- * @fileoverview Implementation of XmlHttpFactory which allows construction from
- * simple factory methods.
- * @author dbk@google.com (David Barrett-Kahn)
- */
+// FIXME consider delaying feature reading so projection can be provided by
+// consumer (e.g. the view)
 
-goog.provide('goog.net.WrapperXmlHttpFactory');
+goog.provide('ol.source.FormatVector');
 
-/** @suppress {extraRequire} Typedef. */
-goog.require('goog.net.XhrLike');
-goog.require('goog.net.XmlHttpFactory');
+goog.require('goog.asserts');
+goog.require('goog.dispose');
+goog.require('goog.events');
+goog.require('goog.net.EventType');
+goog.require('goog.net.XhrIo');
+goog.require('goog.net.XhrIo.ResponseType');
+goog.require('goog.userAgent');
+goog.require('ol.format.FormatType');
+goog.require('ol.has');
+goog.require('ol.source.State');
+goog.require('ol.source.Vector');
+goog.require('ol.xml');
 
 
 
 /**
- * An xhr factory subclass which can be constructed using two factory methods.
- * This exists partly to allow the preservation of goog.net.XmlHttp.setFactory()
- * with an unchanged signature.
- * @param {function():!goog.net.XhrLike.OrNative} xhrFactory
- *     A function which returns a new XHR object.
- * @param {function():!Object} optionsFactory A function which returns the
- *     options associated with xhr objects from this factory.
- * @extends {goog.net.XmlHttpFactory}
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for vector sources in one of the supported formats.
+ *
  * @constructor
- * @final
+ * @extends {ol.source.Vector}
+ * @param {olx.source.FormatVectorOptions} options Options.
  */
-goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) {
-  goog.net.XmlHttpFactory.call(this);
+ol.source.FormatVector = function(options) {
 
-  /**
-   * XHR factory method.
-   * @type {function() : !goog.net.XhrLike.OrNative}
-   * @private
-   */
-  this.xhrFactory_ = xhrFactory;
+  goog.base(this, {
+    attributions: options.attributions,
+    logo: options.logo,
+    projection: options.projection
+  });
 
   /**
-   * Options factory method.
-   * @type {function() : !Object}
-   * @private
+   * @protected
+   * @type {ol.format.Feature}
    */
-  this.optionsFactory_ = optionsFactory;
+  this.format = options.format;
+
 };
-goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory);
+goog.inherits(ol.source.FormatVector, ol.source.Vector);
 
 
-/** @override */
-goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() {
-  return this.xhrFactory_();
+/**
+ * @param {goog.Uri|string} url URL.
+ * @param {function(this: T, Array.<ol.Feature>)} success Success Callback.
+ * @param {function(this: T)} error Error callback.
+ * @param {T} thisArg Value to use as `this` when executing `success` or
+ *     `error`.
+ * @template T
+ */
+ol.source.FormatVector.prototype.loadFeaturesFromURL =
+    function(url, success, error, thisArg) {
+  var xhrIo = new goog.net.XhrIo();
+  var type = this.format.getType();
+  var responseType;
+  // FIXME maybe use ResponseType.DOCUMENT?
+  if (type == ol.format.FormatType.BINARY &&
+      ol.has.ARRAY_BUFFER) {
+    responseType = goog.net.XhrIo.ResponseType.ARRAY_BUFFER;
+  } else {
+    responseType = goog.net.XhrIo.ResponseType.TEXT;
+  }
+  xhrIo.setResponseType(responseType);
+  goog.events.listen(xhrIo, goog.net.EventType.COMPLETE,
+      /**
+       * @param {Event} event Event.
+       * @private
+       * @this {ol.source.FormatVector}
+       */
+      function(event) {
+        var xhrIo = event.target;
+        goog.asserts.assertInstanceof(xhrIo, goog.net.XhrIo);
+        if (xhrIo.isSuccess()) {
+          var type = this.format.getType();
+          /** @type {ArrayBuffer|Document|Node|Object|string|undefined} */
+          var source;
+          if (type == ol.format.FormatType.BINARY &&
+              ol.has.ARRAY_BUFFER) {
+            source = xhrIo.getResponse();
+            goog.asserts.assertInstanceof(source, ArrayBuffer);
+          } else if (type == ol.format.FormatType.JSON) {
+            source = xhrIo.getResponseText();
+          } else if (type == ol.format.FormatType.TEXT) {
+            source = xhrIo.getResponseText();
+          } else if (type == ol.format.FormatType.XML) {
+            if (!goog.userAgent.IE) {
+              source = xhrIo.getResponseXml();
+            }
+            if (!goog.isDefAndNotNull(source)) {
+              source = ol.xml.parse(xhrIo.getResponseText());
+            }
+          } else {
+            goog.asserts.fail();
+          }
+          if (goog.isDefAndNotNull(source)) {
+            success.call(thisArg, this.readFeatures(source));
+          } else {
+            this.setState(ol.source.State.ERROR);
+            goog.asserts.fail();
+          }
+        } else {
+          error.call(thisArg);
+        }
+        goog.dispose(xhrIo);
+      }, false, this);
+  xhrIo.send(url);
 };
 
 
-/** @override */
-goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() {
-  return this.optionsFactory_();
+/**
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
+ */
+ol.source.FormatVector.prototype.readFeatures = function(source) {
+  var format = this.format;
+  var projection = this.getProjection();
+  return format.readFeatures(source, {featureProjection: projection});
 };
 
+goog.provide('ol.source.StaticVector');
+
+goog.require('ol.source.FormatVector');
+goog.require('ol.source.State');
+
 
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 /**
- * @fileoverview Low level handling of XMLHttpRequest.
- * @author arv@google.com (Erik Arvidsson)
- * @author dbk@google.com (David Barrett-Kahn)
+ * @classdesc
+ * A vector source that uses one of the supported formats to read the data from
+ * a file or other static source.
+ *
+ * @constructor
+ * @extends {ol.source.FormatVector}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.StaticVectorOptions} options Options.
+ * @api
  */
+ol.source.StaticVector = function(options) {
 
-goog.provide('goog.net.DefaultXmlHttpFactory');
-goog.provide('goog.net.XmlHttp');
-goog.provide('goog.net.XmlHttp.OptionType');
-goog.provide('goog.net.XmlHttp.ReadyState');
-goog.provide('goog.net.XmlHttpDefines');
+  goog.base(this, {
+    attributions: options.attributions,
+    format: options.format,
+    logo: options.logo,
+    projection: options.projection
+  });
 
-goog.require('goog.asserts');
-goog.require('goog.net.WrapperXmlHttpFactory');
-goog.require('goog.net.XmlHttpFactory');
+  if (goog.isDef(options.arrayBuffer)) {
+    this.addFeaturesInternal(this.readFeatures(options.arrayBuffer));
+  }
+
+  if (goog.isDef(options.doc)) {
+    this.addFeaturesInternal(this.readFeatures(options.doc));
+  }
+
+  if (goog.isDef(options.node)) {
+    this.addFeaturesInternal(this.readFeatures(options.node));
+  }
+
+  if (goog.isDef(options.object)) {
+    this.addFeaturesInternal(this.readFeatures(options.object));
+  }
+
+  if (goog.isDef(options.text)) {
+    this.addFeaturesInternal(this.readFeatures(options.text));
+  }
+
+  if (goog.isDef(options.url) || goog.isDef(options.urls)) {
+    this.setState(ol.source.State.LOADING);
+    if (goog.isDef(options.url)) {
+      this.loadFeaturesFromURL(options.url,
+          this.onFeaturesLoadedSuccess_, this.onFeaturesLoadedError_, this);
+    }
+    if (goog.isDef(options.urls)) {
+      var urls = options.urls;
+      var i, ii;
+      for (i = 0, ii = urls.length; i < ii; ++i) {
+        this.loadFeaturesFromURL(urls[i],
+            this.onFeaturesLoadedSuccess_, this.onFeaturesLoadedError_, this);
+      }
+    }
+  }
+
+};
+goog.inherits(ol.source.StaticVector, ol.source.FormatVector);
 
 
 /**
- * Static class for creating XMLHttpRequest objects.
- * @return {!goog.net.XhrLike.OrNative} A new XMLHttpRequest object.
+ * @private
  */
-goog.net.XmlHttp = function() {
-  return goog.net.XmlHttp.factory_.createInstance();
+ol.source.StaticVector.prototype.onFeaturesLoadedError_ = function() {
+  this.setState(ol.source.State.ERROR);
 };
 
 
 /**
- * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
- *     true bypasses the ActiveX probing code.
- * NOTE(user): Due to the way JSCompiler works, this define *will not* strip
- * out the ActiveX probing code from binaries.  To achieve this, use
- * {@code goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR} instead.
- * TODO(user): Collapse both defines.
+ * @param {Array.<ol.Feature>} features Features.
+ * @private
  */
-goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false);
+ol.source.StaticVector.prototype.onFeaturesLoadedSuccess_ = function(features) {
+  this.addFeaturesInternal(features);
+  this.setState(ol.source.State.READY);
+};
 
+goog.provide('ol.source.GeoJSON');
+
+goog.require('ol.format.GeoJSON');
+goog.require('ol.source.StaticVector');
 
-/** @const */
-goog.net.XmlHttpDefines = {};
 
 
 /**
- * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
- *     true eliminates the ActiveX probing code.
+ * @classdesc
+ * Static vector source in GeoJSON format
+ *
+ * @constructor
+ * @extends {ol.source.StaticVector}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.GeoJSONOptions=} opt_options Options.
+ * @api
  */
-goog.define('goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR', false);
+ol.source.GeoJSON = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this, {
+    attributions: options.attributions,
+    extent: options.extent,
+    format: new ol.format.GeoJSON({
+      defaultDataProjection: options.defaultProjection
+    }),
+    logo: options.logo,
+    object: options.object,
+    projection: options.projection,
+    text: options.text,
+    url: options.url,
+    urls: options.urls
+  });
 
-/**
- * Gets the options to use with the XMLHttpRequest objects obtained using
- * the static methods.
- * @return {Object} The options.
- */
-goog.net.XmlHttp.getOptions = function() {
-  return goog.net.XmlHttp.factory_.getOptions();
 };
+goog.inherits(ol.source.GeoJSON, ol.source.StaticVector);
 
+goog.provide('ol.source.GPX');
 
-/**
- * Type of options that an XmlHttp object can have.
- * @enum {number}
- */
-goog.net.XmlHttp.OptionType = {
-  /**
-   * Whether a goog.nullFunction should be used to clear the onreadystatechange
-   * handler instead of null.
-   */
-  USE_NULL_FUNCTION: 0,
+goog.require('ol.format.GPX');
+goog.require('ol.source.StaticVector');
 
-  /**
-   * NOTE(user): In IE if send() errors on a *local* request the readystate
-   * is still changed to COMPLETE.  We need to ignore it and allow the
-   * try/catch around send() to pick up the error.
-   */
-  LOCAL_REQUEST_ERROR: 1
-};
 
 
 /**
- * Status constants for XMLHTTP, matches:
- * http://msdn.microsoft.com/library/default.asp?url=/library/
- *   en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp
- * @enum {number}
+ * @classdesc
+ * Static vector source in GPX format
+ *
+ * @constructor
+ * @extends {ol.source.StaticVector}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.GPXOptions=} opt_options Options.
+ * @api
  */
-goog.net.XmlHttp.ReadyState = {
-  /**
-   * Constant for when xmlhttprequest.readyState is uninitialized
-   */
-  UNINITIALIZED: 0,
-
-  /**
-   * Constant for when xmlhttprequest.readyState is loading.
-   */
-  LOADING: 1,
+ol.source.GPX = function(opt_options) {
 
-  /**
-   * Constant for when xmlhttprequest.readyState is loaded.
-   */
-  LOADED: 2,
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  /**
-   * Constant for when xmlhttprequest.readyState is in an interactive state.
-   */
-  INTERACTIVE: 3,
+  goog.base(this, {
+    attributions: options.attributions,
+    doc: options.doc,
+    extent: options.extent,
+    format: new ol.format.GPX(),
+    logo: options.logo,
+    node: options.node,
+    projection: options.projection,
+    text: options.text,
+    url: options.url,
+    urls: options.urls
+  });
 
-  /**
-   * Constant for when xmlhttprequest.readyState is completed
-   */
-  COMPLETE: 4
 };
+goog.inherits(ol.source.GPX, ol.source.StaticVector);
 
+goog.provide('ol.source.IGC');
+
+goog.require('ol.format.IGC');
+goog.require('ol.source.StaticVector');
 
-/**
- * The global factory instance for creating XMLHttpRequest objects.
- * @type {goog.net.XmlHttpFactory}
- * @private
- */
-goog.net.XmlHttp.factory_;
 
 
 /**
- * Sets the factories for creating XMLHttpRequest objects and their options.
- * @param {Function} factory The factory for XMLHttpRequest objects.
- * @param {Function} optionsFactory The factory for options.
- * @deprecated Use setGlobalFactory instead.
+ * @classdesc
+ * Static vector source in IGC format
+ *
+ * @constructor
+ * @extends {ol.source.StaticVector}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.IGCOptions=} opt_options Options.
+ * @api
  */
-goog.net.XmlHttp.setFactory = function(factory, optionsFactory) {
-  goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(
-      goog.asserts.assert(factory),
-      goog.asserts.assert(optionsFactory)));
+ol.source.IGC = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this, {
+    format: new ol.format.IGC({
+      altitudeMode: options.altitudeMode
+    }),
+    projection: options.projection,
+    text: options.text,
+    url: options.url,
+    urls: options.urls
+  });
+
 };
+goog.inherits(ol.source.IGC, ol.source.StaticVector);
+
+goog.provide('ol.source.Image');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('ol.Attribution');
+goog.require('ol.Extent');
+goog.require('ol.array');
+goog.require('ol.source.Source');
 
 
 /**
- * Sets the global factory object.
- * @param {!goog.net.XmlHttpFactory} factory New global factory object.
+ * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
+ *            extent: (null|ol.Extent|undefined),
+ *            logo: (string|olx.LogoOptions|undefined),
+ *            projection: ol.proj.ProjectionLike,
+ *            resolutions: (Array.<number>|undefined),
+ *            state: (ol.source.State|undefined)}}
  */
-goog.net.XmlHttp.setGlobalFactory = function(factory) {
-  goog.net.XmlHttp.factory_ = factory;
-};
+ol.source.ImageOptions;
 
 
 
 /**
- * Default factory to use when creating xhr objects.  You probably shouldn't be
- * instantiating this directly, but rather using it via goog.net.XmlHttp.
- * @extends {goog.net.XmlHttpFactory}
+ * @classdesc
+ * Abstract base class; normally only used for creating subclasses and not
+ * instantiated in apps.
+ * Base class for sources providing a single image.
+ *
  * @constructor
+ * @extends {ol.source.Source}
+ * @param {ol.source.ImageOptions} options Single image source options.
+ * @api
  */
-goog.net.DefaultXmlHttpFactory = function() {
-  goog.net.XmlHttpFactory.call(this);
-};
-goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory);
-
+ol.source.Image = function(options) {
 
-/** @override */
-goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() {
-  var progId = this.getProgId_();
-  if (progId) {
-    return new ActiveXObject(progId);
-  } else {
-    return new XMLHttpRequest();
-  }
-};
+  goog.base(this, {
+    attributions: options.attributions,
+    extent: options.extent,
+    logo: options.logo,
+    projection: options.projection,
+    state: options.state
+  });
 
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.resolutions_ = goog.isDef(options.resolutions) ?
+      options.resolutions : null;
+  goog.asserts.assert(goog.isNull(this.resolutions_) ||
+      goog.array.isSorted(this.resolutions_,
+          function(a, b) {
+            return b - a;
+          }, true));
 
-/** @override */
-goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() {
-  var progId = this.getProgId_();
-  var options = {};
-  if (progId) {
-    options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true;
-    options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true;
-  }
-  return options;
 };
+goog.inherits(ol.source.Image, ol.source.Source);
 
 
 /**
- * The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.
- * @type {string|undefined}
- * @private
+ * @return {Array.<number>} Resolutions.
  */
-goog.net.DefaultXmlHttpFactory.prototype.ieProgId_;
+ol.source.Image.prototype.getResolutions = function() {
+  return this.resolutions_;
+};
 
 
 /**
- * Initialize the private state used by other functions.
- * @return {string} The ActiveX PROG ID string to use to create xhr's in IE.
- * @private
+ * @protected
+ * @param {number} resolution Resolution.
+ * @return {number} Resolution.
  */
-goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() {
-  if (goog.net.XmlHttp.ASSUME_NATIVE_XHR ||
-      goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR) {
-    return '';
-  }
-
-  // The following blog post describes what PROG IDs to use to create the
-  // XMLHTTP object in Internet Explorer:
-  // http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
-  // However we do not (yet) fully trust that this will be OK for old versions
-  // of IE on Win9x so we therefore keep the last 2.
-  if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' &&
-      typeof ActiveXObject != 'undefined') {
-    // Candidate Active X types.
-    var ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0',
-                           'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];
-    for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) {
-      var candidate = ACTIVE_X_IDENTS[i];
-      /** @preserveTry */
-      try {
-        new ActiveXObject(candidate);
-        // NOTE(user): cannot assign progid and return candidate in one line
-        // because JSCompiler complaings: BUG 658126
-        this.ieProgId_ = candidate;
-        return candidate;
-      } catch (e) {
-        // do nothing; try next choice
-      }
-    }
-
-    // couldn't find any matches
-    throw Error('Could not create ActiveXObject. ActiveX might be disabled,' +
-                ' or MSXML might not be installed');
+ol.source.Image.prototype.findNearestResolution =
+    function(resolution) {
+  if (!goog.isNull(this.resolutions_)) {
+    var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0);
+    resolution = this.resolutions_[idx];
   }
-
-  return /** @type {string} */ (this.ieProgId_);
+  return resolution;
 };
 
 
-//Set the global factory to an instance of the default factory.
-goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory());
-
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
 /**
- * @fileoverview Wrapper class for handling XmlHttpRequests.
- *
- * One off requests can be sent through goog.net.XhrIo.send() or an
- * instance can be created to send multiple requests.  Each request uses its
- * own XmlHttpRequest object and handles clearing of the event callback to
- * ensure no leaks.
- *
- * XhrIo is event based, it dispatches events when a request finishes, fails or
- * succeeds or when the ready-state changes. The ready-state or timeout event
- * fires first, followed by a generic completed event. Then the abort, error,
- * or success event is fired as appropriate. Lastly, the ready event will fire
- * to indicate that the object may be used to make another request.
- *
- * The error event may also be called before completed and
- * ready-state-change if the XmlHttpRequest.open() or .send() methods throw.
- *
- * This class does not support multiple requests, queuing, or prioritization.
- *
- * Tested = IE6, FF1.5, Safari, Opera 8.5
- *
- * TODO(user): Error cases aren't playing nicely in Safari.
- *
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.ImageBase} Single image.
  */
+ol.source.Image.prototype.getImage = goog.abstractMethod;
 
 
-goog.provide('goog.net.XhrIo');
-goog.provide('goog.net.XhrIo.ResponseType');
+/**
+ * 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;
+};
 
-goog.require('goog.Timer');
-goog.require('goog.array');
-goog.require('goog.debug.entryPointRegistry');
-goog.require('goog.events.EventTarget');
-goog.require('goog.json');
-goog.require('goog.log');
-goog.require('goog.net.ErrorCode');
-goog.require('goog.net.EventType');
-goog.require('goog.net.HttpStatus');
-goog.require('goog.net.XmlHttp');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('goog.structs');
-goog.require('goog.structs.Map');
-goog.require('goog.uri.utils');
-goog.require('goog.userAgent');
+goog.provide('ol.source.ImageCanvas');
 
-goog.forwardDeclare('goog.Uri');
+goog.require('ol.CanvasFunctionType');
+goog.require('ol.ImageCanvas');
+goog.require('ol.extent');
+goog.require('ol.source.Image');
 
 
 
 /**
- * Basic class for handling XMLHttpRequests.
- * @param {goog.net.XmlHttpFactory=} opt_xmlHttpFactory Factory to use when
- *     creating XMLHttpRequest objects.
+ * @classdesc
+ * Base class for image sources where a canvas element is the image.
+ *
  * @constructor
- * @extends {goog.events.EventTarget}
+ * @extends {ol.source.Image}
+ * @param {olx.source.ImageCanvasOptions} options
+ * @api
  */
-goog.net.XhrIo = function(opt_xmlHttpFactory) {
-  goog.net.XhrIo.base(this, 'constructor');
+ol.source.ImageCanvas = function(options) {
 
-  /**
-   * Map of default headers to add to every request, use:
-   * XhrIo.headers.set(name, value)
-   * @type {!goog.structs.Map}
-   */
-  this.headers = new goog.structs.Map();
+  goog.base(this, {
+    attributions: options.attributions,
+    logo: options.logo,
+    projection: options.projection,
+    resolutions: options.resolutions,
+    state: goog.isDef(options.state) ?
+        /** @type {ol.source.State} */ (options.state) : undefined
+  });
 
   /**
-   * Optional XmlHttpFactory
-   * @private {goog.net.XmlHttpFactory}
+   * @private
+   * @type {ol.CanvasFunctionType}
    */
-  this.xmlHttpFactory_ = opt_xmlHttpFactory || null;
+  this.canvasFunction_ = options.canvasFunction;
 
   /**
-   * Whether XMLHttpRequest is active.  A request is active from the time send()
-   * is called until onReadyStateChange() is complete, or error() or abort()
-   * is called.
-   * @private {boolean}
+   * @private
+   * @type {ol.ImageCanvas}
    */
-  this.active_ = false;
+  this.canvas_ = null;
 
   /**
-   * The XMLHttpRequest object that is being used for the transfer.
-   * @private {?goog.net.XhrLike.OrNative}
+   * @private
+   * @type {number}
    */
-  this.xhr_ = null;
+  this.renderedRevision_ = 0;
 
   /**
-   * The options to use with the current XMLHttpRequest object.
-   * @private {Object}
+   * @private
+   * @type {number}
    */
-  this.xhrOptions_ = null;
+  this.ratio_ = goog.isDef(options.ratio) ?
+      options.ratio : 1.5;
 
-  /**
-   * Last URL that was requested.
-   * @private {string|goog.Uri}
-   */
-  this.lastUri_ = '';
+};
+goog.inherits(ol.source.ImageCanvas, ol.source.Image);
 
-  /**
-   * Method for the last request.
-   * @private {string}
-   */
-  this.lastMethod_ = '';
 
-  /**
-   * Last error code.
-   * @private {!goog.net.ErrorCode}
-   */
-  this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
+/**
+ * @inheritDoc
+ */
+ol.source.ImageCanvas.prototype.getImage =
+    function(extent, resolution, pixelRatio, projection) {
+  resolution = this.findNearestResolution(resolution);
+
+  var canvas = this.canvas_;
+  if (!goog.isNull(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 (!goog.isNull(canvasElement)) {
+    canvas = new ol.ImageCanvas(extent, resolution, pixelRatio,
+        this.getAttributions(), canvasElement);
+  }
+  this.canvas_ = canvas;
+  this.renderedRevision_ = this.getRevision();
+
+  return canvas;
+};
+
+goog.provide('ol.source.ImageMapGuide');
+
+goog.require('goog.object');
+goog.require('goog.uri.utils');
+goog.require('ol.Image');
+goog.require('ol.ImageLoadFunctionType');
+goog.require('ol.ImageUrlFunction');
+goog.require('ol.extent');
+goog.require('ol.source.Image');
+
+
+
+/**
+ * @classdesc
+ * Source for images from Mapguide servers
+ *
+ * @constructor
+ * @extends {ol.source.Image}
+ * @param {olx.source.ImageMapGuideOptions} options Options.
+ * @api stable
+ */
+ol.source.ImageMapGuide = function(options) {
+
+  goog.base(this, {
+    projection: options.projection,
+    resolutions: options.resolutions
+  });
 
   /**
-   * Last error message.
-   * @private {Error|string}
+   * @private
+   * @type {?string}
    */
-  this.lastError_ = '';
+  this.crossOrigin_ =
+      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
 
   /**
-   * Used to ensure that we don't dispatch an multiple ERROR events. This can
-   * happen in IE when it does a synchronous load and one error is handled in
-   * the ready statte change and one is handled due to send() throwing an
-   * exception.
-   * @private {boolean}
+   * @private
+   * @type {number}
    */
-  this.errorDispatched_ = false;
+  this.displayDpi_ = goog.isDef(options.displayDpi) ?
+      options.displayDpi : 96;
 
   /**
-   * Used to make sure we don't fire the complete event from inside a send call.
-   * @private {boolean}
+   * @private
+   * @type {Object}
    */
-  this.inSend_ = false;
+  this.params_ = goog.isDef(options.params) ? options.params : {};
+
+  var imageUrlFunction;
+  if (goog.isDef(options.url)) {
+    imageUrlFunction = ol.ImageUrlFunction.createFromParamsFunction(
+        options.url, this.params_, goog.bind(this.getUrl, this));
+  } else {
+    imageUrlFunction = ol.ImageUrlFunction.nullImageUrlFunction;
+  }
 
   /**
-   * Used in determining if a call to {@link #onReadyStateChange_} is from
-   * within a call to this.xhr_.open.
-   * @private {boolean}
+   * @private
+   * @type {ol.ImageUrlFunctionType}
    */
-  this.inOpen_ = false;
+  this.imageUrlFunction_ = imageUrlFunction;
 
   /**
-   * Used in determining if a call to {@link #onReadyStateChange_} is from
-   * within a call to this.xhr_.abort.
-   * @private {boolean}
+   * @private
+   * @type {ol.ImageLoadFunctionType}
    */
-  this.inAbort_ = false;
+  this.imageLoadFunction_ = goog.isDef(options.imageLoadFunction) ?
+      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
 
   /**
-   * Number of milliseconds after which an incomplete request will be aborted
-   * and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no timeout
-   * is set.
-   * @private {number}
+   * @private
+   * @type {boolean}
    */
-  this.timeoutInterval_ = 0;
+  this.hidpi_ = goog.isDef(options.hidpi) ? options.hidpi : true;
 
   /**
-   * Timer to track request timeout.
-   * @private {?number}
+   * @private
+   * @type {number}
    */
-  this.timeoutId_ = null;
+  this.metersPerUnit_ = goog.isDef(options.metersPerUnit) ?
+      options.metersPerUnit : 1;
 
   /**
-   * The requested type for the response. The empty string means use the default
-   * XHR behavior.
-   * @private {goog.net.XhrIo.ResponseType}
+   * @private
+   * @type {number}
    */
-  this.responseType_ = goog.net.XhrIo.ResponseType.DEFAULT;
+  this.ratio_ = goog.isDef(options.ratio) ? options.ratio : 1;
 
   /**
-   * Whether a "credentialed" request is to be sent (one that is aware of
-   * cookies and authentication). This is applicable only for cross-domain
-   * requests and more recent browsers that support this part of the HTTP Access
-   * Control standard.
-   *
-   * @see http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute
-   *
-   * @private {boolean}
+   * @private
+   * @type {boolean}
    */
-  this.withCredentials_ = false;
+  this.useOverlay_ = goog.isDef(options.useOverlay) ?
+      options.useOverlay : false;
 
   /**
-   * True if we can use XMLHttpRequest's timeout directly.
-   * @private {boolean}
+   * @private
+   * @type {ol.Image}
    */
-  this.useXhr2Timeout_ = false;
-};
-goog.inherits(goog.net.XhrIo, goog.events.EventTarget);
+  this.image_ = null;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = 0;
 
-/**
- * Response types that may be requested for XMLHttpRequests.
- * @enum {string}
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute
- */
-goog.net.XhrIo.ResponseType = {
-  DEFAULT: '',
-  TEXT: 'text',
-  DOCUMENT: 'document',
-  // Not supported as of Chrome 10.0.612.1 dev
-  BLOB: 'blob',
-  ARRAY_BUFFER: 'arraybuffer'
 };
+goog.inherits(ol.source.ImageMapGuide, ol.source.Image);
 
 
 /**
- * A reference to the XhrIo logger
- * @private {goog.debug.Logger}
- * @const
- */
-goog.net.XhrIo.prototype.logger_ =
-    goog.log.getLogger('goog.net.XhrIo');
-
-
-/**
- * The Content-Type HTTP header name
- * @type {string}
- */
-goog.net.XhrIo.CONTENT_TYPE_HEADER = 'Content-Type';
-
-
-/**
- * The pattern matching the 'http' and 'https' URI schemes
- * @type {!RegExp}
- */
-goog.net.XhrIo.HTTP_SCHEME_PATTERN = /^https?$/i;
-
-
-/**
- * The methods that typically come along with form data.  We set different
- * headers depending on whether the HTTP action is one of these.
- */
-goog.net.XhrIo.METHODS_WITH_FORM_DATA = ['POST', 'PUT'];
-
-
-/**
- * The Content-Type HTTP header value for a url-encoded form
- * @type {string}
- */
-goog.net.XhrIo.FORM_CONTENT_TYPE =
-    'application/x-www-form-urlencoded;charset=utf-8';
-
-
-/**
- * The XMLHttpRequest Level two timeout delay ms property name.
- *
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute
- *
- * @private {string}
- * @const
- */
-goog.net.XhrIo.XHR2_TIMEOUT_ = 'timeout';
-
-
-/**
- * The XMLHttpRequest Level two ontimeout handler property name.
- *
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute
- *
- * @private {string}
- * @const
+ * 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
  */
-goog.net.XhrIo.XHR2_ON_TIMEOUT_ = 'ontimeout';
+ol.source.ImageMapGuide.prototype.getParams = function() {
+  return this.params_;
+};
 
 
 /**
- * All non-disposed instances of goog.net.XhrIo created
- * by {@link goog.net.XhrIo.send} are in this Array.
- * @see goog.net.XhrIo.cleanup
- * @private {!Array.<!goog.net.XhrIo>}
+ * @inheritDoc
  */
-goog.net.XhrIo.sendInstances_ = [];
-
+ol.source.ImageMapGuide.prototype.getImage =
+    function(extent, resolution, pixelRatio, projection) {
+  resolution = this.findNearestResolution(resolution);
+  pixelRatio = this.hidpi_ ? pixelRatio : 1;
 
-/**
- * Static send that creates a short lived instance of XhrIo to send the
- * request.
- * @see goog.net.XhrIo.cleanup
- * @param {string|goog.Uri} url Uri to make request to.
- * @param {Function=} opt_callback Callback function for when request is
- *     complete.
- * @param {string=} opt_method Send method, default: GET.
- * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}
- *     opt_content Body data.
- * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
- *     request.
- * @param {number=} opt_timeoutInterval Number of milliseconds after which an
- *     incomplete request will be aborted; 0 means no timeout is set.
- * @param {boolean=} opt_withCredentials Whether to send credentials with the
- *     request. Default to false. See {@link goog.net.XhrIo#setWithCredentials}.
- * @return {!goog.net.XhrIo} The sent XhrIo.
- */
-goog.net.XhrIo.send = function(url, opt_callback, opt_method, opt_content,
-                               opt_headers, opt_timeoutInterval,
-                               opt_withCredentials) {
-  var x = new goog.net.XhrIo();
-  goog.net.XhrIo.sendInstances_.push(x);
-  if (opt_callback) {
-    x.listen(goog.net.EventType.COMPLETE, opt_callback);
+  var image = this.image_;
+  if (!goog.isNull(image) &&
+      this.renderedRevision_ == this.getRevision() &&
+      image.getResolution() == resolution &&
+      image.getPixelRatio() == pixelRatio &&
+      ol.extent.containsExtent(image.getExtent(), extent)) {
+    return image;
   }
-  x.listenOnce(goog.net.EventType.READY, x.cleanupSend_);
-  if (opt_timeoutInterval) {
-    x.setTimeoutInterval(opt_timeoutInterval);
+
+  if (this.ratio_ != 1) {
+    extent = extent.slice();
+    ol.extent.scaleFromCenter(extent, this.ratio_);
   }
-  if (opt_withCredentials) {
-    x.setWithCredentials(opt_withCredentials);
+  var width = ol.extent.getWidth(extent) / resolution;
+  var height = ol.extent.getHeight(extent) / resolution;
+  var size = [width * pixelRatio, height * pixelRatio];
+
+  var imageUrl = this.imageUrlFunction_(extent, size, projection);
+  if (goog.isDef(imageUrl)) {
+    image = new ol.Image(extent, resolution, pixelRatio,
+        this.getAttributions(), imageUrl, this.crossOrigin_,
+        this.imageLoadFunction_);
+  } else {
+    image = null;
   }
-  x.send(url, opt_method, opt_content, opt_headers);
-  return x;
+  this.image_ = image;
+  this.renderedRevision_ = this.getRevision();
+
+  return image;
 };
 
 
 /**
- * Disposes all non-disposed instances of goog.net.XhrIo created by
- * {@link goog.net.XhrIo.send}.
- * {@link goog.net.XhrIo.send} cleans up the goog.net.XhrIo instance
- * it creates when the request completes or fails.  However, if
- * the request never completes, then the goog.net.XhrIo is not disposed.
- * This can occur if the window is unloaded before the request completes.
- * We could have {@link goog.net.XhrIo.send} return the goog.net.XhrIo
- * it creates and make the client of {@link goog.net.XhrIo.send} be
- * responsible for disposing it in this case.  However, this makes things
- * significantly more complicated for the client, and the whole point
- * of {@link goog.net.XhrIo.send} is that it's simple and easy to use.
- * Clients of {@link goog.net.XhrIo.send} should call
- * {@link goog.net.XhrIo.cleanup} when doing final
- * cleanup on window unload.
+ * @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.
  */
-goog.net.XhrIo.cleanup = function() {
-  var instances = goog.net.XhrIo.sendInstances_;
-  while (instances.length) {
-    instances.pop().dispose();
+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
   }
 };
 
 
 /**
- * Installs exception protection for all entry point introduced by
- * goog.net.XhrIo instances which are not protected by
- * {@link goog.debug.ErrorHandler#protectWindowSetTimeout},
- * {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or
- * {@link goog.events.protectBrowserEventEntryPoint}.
- *
- * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
- *     protect the entry point(s).
+ * Update the user-provided params.
+ * @param {Object} params Params.
+ * @api stable
  */
-goog.net.XhrIo.protectEntryPoints = function(errorHandler) {
-  goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ =
-      errorHandler.protectEntryPoint(
-          goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);
+ol.source.ImageMapGuide.prototype.updateParams = function(params) {
+  goog.object.extend(this.params_, params);
+  this.changed();
 };
 
 
 /**
- * Disposes of the specified goog.net.XhrIo created by
- * {@link goog.net.XhrIo.send} and removes it from
- * {@link goog.net.XhrIo.pendingStaticSendInstances_}.
- * @private
+ * @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.
  */
-goog.net.XhrIo.prototype.cleanupSend_ = function() {
-  this.dispose();
-  goog.array.remove(goog.net.XhrIo.sendInstances_, this);
+ol.source.ImageMapGuide.prototype.getUrl =
+    function(baseUrl, params, extent, size, projection) {
+  var scale = ol.source.ImageMapGuide.getScale(extent, size,
+      this.metersPerUnit_, this.displayDpi_);
+  var center = ol.extent.getCenter(extent);
+  var baseParams = {
+    'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE',
+    'VERSION': '2.0.0',
+    'LOCALE': 'en',
+    'CLIENTAGENT': 'ol.source.ImageMapGuide source',
+    'CLIP': '1',
+    'SETDISPLAYDPI': this.displayDpi_,
+    'SETDISPLAYWIDTH': Math.round(size[0]),
+    'SETDISPLAYHEIGHT': Math.round(size[1]),
+    'SETVIEWSCALE': scale,
+    'SETVIEWCENTERX': center[0],
+    'SETVIEWCENTERY': center[1]
+  };
+  goog.object.extend(baseParams, params);
+  return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams);
 };
 
+goog.provide('ol.source.ImageStatic');
 
-/**
- * Returns the number of milliseconds after which an incomplete request will be
- * aborted, or 0 if no timeout is set.
- * @return {number} Timeout interval in milliseconds.
- */
-goog.net.XhrIo.prototype.getTimeoutInterval = function() {
-  return this.timeoutInterval_;
-};
-
+goog.require('goog.events');
+goog.require('ol.Image');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.Image');
 
-/**
- * Sets the number of milliseconds after which an incomplete request will be
- * aborted and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no
- * timeout is set.
- * @param {number} ms Timeout interval in milliseconds; 0 means none.
- */
-goog.net.XhrIo.prototype.setTimeoutInterval = function(ms) {
-  this.timeoutInterval_ = Math.max(0, ms);
-};
 
 
 /**
- * Sets the desired type for the response. At time of writing, this is only
- * supported in very recent versions of WebKit (10.0.612.1 dev and later).
- *
- * If this is used, the response may only be accessed via {@link #getResponse}.
+ * @classdesc
+ * A layer source for displaying a single, static image.
  *
- * @param {goog.net.XhrIo.ResponseType} type The desired type for the response.
+ * @constructor
+ * @extends {ol.source.Image}
+ * @param {olx.source.ImageStaticOptions} options Options.
+ * @api stable
  */
-goog.net.XhrIo.prototype.setResponseType = function(type) {
-  this.responseType_ = type;
-};
+ol.source.ImageStatic = function(options) {
 
+  var attributions = goog.isDef(options.attributions) ?
+      options.attributions : null;
 
-/**
- * Gets the desired type for the response.
- * @return {goog.net.XhrIo.ResponseType} The desired type for the response.
- */
-goog.net.XhrIo.prototype.getResponseType = function() {
-  return this.responseType_;
-};
+  var imageExtent = options.imageExtent;
 
+  var resolution, resolutions;
+  if (goog.isDef(options.imageSize)) {
+    resolution = ol.extent.getHeight(imageExtent) / options.imageSize[1];
+    resolutions = [resolution];
+  }
 
-/**
- * Sets whether a "credentialed" request that is aware of cookie and
- * authentication information should be made. This option is only supported by
- * browsers that support HTTP Access Control. As of this writing, this option
- * is not supported in IE.
- *
- * @param {boolean} withCredentials Whether this should be a "credentialed"
- *     request.
- */
-goog.net.XhrIo.prototype.setWithCredentials = function(withCredentials) {
-  this.withCredentials_ = withCredentials;
-};
+  var crossOrigin = goog.isDef(options.crossOrigin) ?
+      options.crossOrigin : null;
 
+  var imageLoadFunction = goog.isDef(options.imageLoadFunction) ?
+      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+
+  goog.base(this, {
+    attributions: attributions,
+    logo: options.logo,
+    projection: ol.proj.get(options.projection),
+    resolutions: resolutions
+  });
+
+  /**
+   * @private
+   * @type {ol.Image}
+   */
+  this.image_ = new ol.Image(imageExtent, resolution, 1, attributions,
+      options.url, crossOrigin, imageLoadFunction);
 
-/**
- * Gets whether a "credentialed" request is to be sent.
- * @return {boolean} The desired type for the response.
- */
-goog.net.XhrIo.prototype.getWithCredentials = function() {
-  return this.withCredentials_;
 };
+goog.inherits(ol.source.ImageStatic, ol.source.Image);
 
 
 /**
- * Instance send that actually uses XMLHttpRequest to make a server call.
- * @param {string|goog.Uri} url Uri to make request to.
- * @param {string=} opt_method Send method, default: GET.
- * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}
- *     opt_content Body data.
- * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
- *     request.
+ * @inheritDoc
  */
-goog.net.XhrIo.prototype.send = function(url, opt_method, opt_content,
-                                         opt_headers) {
-  if (this.xhr_) {
-    throw Error('[goog.net.XhrIo] Object is active with another request=' +
-        this.lastUri_ + '; newUri=' + url);
+ol.source.ImageStatic.prototype.getImage =
+    function(extent, resolution, pixelRatio, projection) {
+  if (ol.extent.intersects(extent, this.image_.getExtent())) {
+    return this.image_;
   }
+  return null;
+};
 
-  var method = opt_method ? opt_method.toUpperCase() : 'GET';
+goog.provide('ol.source.ImageVector');
 
-  this.lastUri_ = url;
-  this.lastError_ = '';
-  this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
-  this.lastMethod_ = method;
-  this.errorDispatched_ = false;
-  this.active_ = true;
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.vec.Mat4');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.vector');
+goog.require('ol.source.ImageCanvas');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Style');
+goog.require('ol.vec.Mat4');
 
-  // Use the factory to create the XHR object and options
-  this.xhr_ = this.createXhr();
-  this.xhrOptions_ = this.xmlHttpFactory_ ?
-      this.xmlHttpFactory_.getOptions() : goog.net.XmlHttp.getOptions();
 
-  // Set up the onreadystatechange callback
-  this.xhr_.onreadystatechange = goog.bind(this.onReadyStateChange_, this);
+
+/**
+ * @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) {
 
   /**
-   * Try to open the XMLHttpRequest (always async), if an error occurs here it
-   * is generally permission denied
-   * @preserveTry
+   * @private
+   * @type {ol.source.Vector}
    */
-  try {
-    goog.log.fine(this.logger_, this.formatMsg_('Opening Xhr'));
-    this.inOpen_ = true;
-    this.xhr_.open(method, String(url), true);  // Always async!
-    this.inOpen_ = false;
-  } catch (err) {
-    goog.log.fine(this.logger_,
-        this.formatMsg_('Error opening Xhr: ' + err.message));
-    this.error_(goog.net.ErrorCode.EXCEPTION, err);
-    return;
-  }
-
-  // We can't use null since this won't allow requests with form data to have a
-  // content length specified which will cause some proxies to return a 411
-  // error.
-  var content = opt_content || '';
-
-  var headers = this.headers.clone();
+  this.source_ = options.source;
 
-  // Add headers specific to this request
-  if (opt_headers) {
-    goog.structs.forEach(opt_headers, function(value, key) {
-      headers.set(key, value);
-    });
-  }
+  /**
+   * @private
+   * @type {!goog.vec.Mat4.Number}
+   */
+  this.transform_ = goog.vec.Mat4.createNumber();
 
-  // Find whether a content type header is set, ignoring case.
-  // HTTP header names are case-insensitive.  See:
-  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
-  var contentTypeKey = goog.array.find(headers.getKeys(),
-      goog.net.XhrIo.isContentTypeHeader_);
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.canvasContext_ = ol.dom.createCanvasContext2D();
 
-  var contentIsFormData = (goog.global['FormData'] &&
-      (content instanceof goog.global['FormData']));
-  if (goog.array.contains(goog.net.XhrIo.METHODS_WITH_FORM_DATA, method) &&
-      !contentTypeKey && !contentIsFormData) {
-    // For requests typically with form data, default to the url-encoded form
-    // content type unless this is a FormData request.  For FormData,
-    // the browser will automatically add a multipart/form-data content type
-    // with an appropriate multipart boundary.
-    headers.set(goog.net.XhrIo.CONTENT_TYPE_HEADER,
-                goog.net.XhrIo.FORM_CONTENT_TYPE);
-  }
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.canvasSize_ = [0, 0];
 
-  // Add the headers to the Xhr object
-  headers.forEach(function(value, key) {
-    this.xhr_.setRequestHeader(key, value);
-  }, this);
+  /**
+   * @private
+   * @type {ol.render.canvas.ReplayGroup}
+   */
+  this.replayGroup_ = null;
 
-  if (this.responseType_) {
-    this.xhr_.responseType = this.responseType_;
-  }
+  goog.base(this, {
+    attributions: options.attributions,
+    canvasFunction: goog.bind(this.canvasFunctionInternal_, this),
+    logo: options.logo,
+    projection: options.projection,
+    ratio: options.ratio,
+    resolutions: options.resolutions,
+    state: this.source_.getState()
+  });
 
-  if (goog.object.containsKey(this.xhr_, 'withCredentials')) {
-    this.xhr_.withCredentials = this.withCredentials_;
-  }
+  /**
+   * User provided style.
+   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
+   * @private
+   */
+  this.style_ = null;
 
   /**
-   * Try to send the request, or other wise report an error (404 not found).
-   * @preserveTry
+   * Style function for use within the library.
+   * @type {ol.style.StyleFunction|undefined}
+   * @private
    */
-  try {
-    this.cleanUpTimeoutTimer_(); // Paranoid, should never be running.
-    if (this.timeoutInterval_ > 0) {
-      this.useXhr2Timeout_ = goog.net.XhrIo.shouldUseXhr2Timeout_(this.xhr_);
-      goog.log.fine(this.logger_, this.formatMsg_('Will abort after ' +
-          this.timeoutInterval_ + 'ms if incomplete, xhr2 ' +
-          this.useXhr2Timeout_));
-      if (this.useXhr2Timeout_) {
-        this.xhr_[goog.net.XhrIo.XHR2_TIMEOUT_] = this.timeoutInterval_;
-        this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] =
-            goog.bind(this.timeout_, this);
-      } else {
-        this.timeoutId_ = goog.Timer.callOnce(this.timeout_,
-            this.timeoutInterval_, this);
-      }
-    }
-    goog.log.fine(this.logger_, this.formatMsg_('Sending request'));
-    this.inSend_ = true;
-    this.xhr_.send(content);
-    this.inSend_ = false;
+  this.styleFunction_ = undefined;
 
-  } catch (err) {
-    goog.log.fine(this.logger_, this.formatMsg_('Send error: ' + err.message));
-    this.error_(goog.net.ErrorCode.EXCEPTION, err);
-  }
-};
+  this.setStyle(options.style);
 
+  goog.events.listen(this.source_, goog.events.EventType.CHANGE,
+      this.handleSourceChange_, undefined, this);
 
-/**
- * Determines if the argument is an XMLHttpRequest that supports the level 2
- * timeout value and event.
- *
- * Currently, FF 21.0 OS X has the fields but won't actually call the timeout
- * handler.  Perhaps the confusion in the bug referenced below hasn't
- * entirely been resolved.
- *
- * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute
- * @see https://bugzilla.mozilla.org/show_bug.cgi?id=525816
- *
- * @param {!goog.net.XhrLike.OrNative} xhr The request.
- * @return {boolean} True if the request supports level 2 timeout.
- * @private
- */
-goog.net.XhrIo.shouldUseXhr2Timeout_ = function(xhr) {
-  return goog.userAgent.IE &&
-      goog.userAgent.isVersionOrHigher(9) &&
-      goog.isNumber(xhr[goog.net.XhrIo.XHR2_TIMEOUT_]) &&
-      goog.isDef(xhr[goog.net.XhrIo.XHR2_ON_TIMEOUT_]);
 };
+goog.inherits(ol.source.ImageVector, ol.source.ImageCanvas);
 
 
 /**
- * @param {string} header An HTTP header key.
- * @return {boolean} Whether the key is a content type header (ignoring
- *     case.
+ * @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
  */
-goog.net.XhrIo.isContentTypeHeader_ = function(header) {
-  return goog.string.caseInsensitiveEquals(
-      goog.net.XhrIo.CONTENT_TYPE_HEADER, header);
-};
+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);
 
-/**
- * Creates a new XHR object.
- * @return {!goog.net.XhrLike.OrNative} The newly created XHR object.
- * @protected
- */
-goog.net.XhrIo.prototype.createXhr = function() {
-  return this.xmlHttpFactory_ ?
-      this.xmlHttpFactory_.createInstance() : goog.net.XmlHttp();
-};
+  this.source_.loadFeatures(extent, resolution, projection);
 
+  var loading = false;
+  this.source_.forEachFeatureInExtentAtResolution(extent, resolution,
+      /**
+       * @param {ol.Feature} feature Feature.
+       */
+      function(feature) {
+        loading = loading ||
+            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
+      }, this);
+  replayGroup.finish();
 
-/**
- * The request didn't complete after {@link goog.net.XhrIo#timeoutInterval_}
- * milliseconds; raises a {@link goog.net.EventType.TIMEOUT} event and aborts
- * the request.
- * @private
- */
-goog.net.XhrIo.prototype.timeout_ = function() {
-  if (typeof goog == 'undefined') {
-    // If goog is undefined then the callback has occurred as the application
-    // is unloading and will error.  Thus we let it silently fail.
-  } else if (this.xhr_) {
-    this.lastError_ = 'Timed out after ' + this.timeoutInterval_ +
-                      'ms, aborting';
-    this.lastErrorCode_ = goog.net.ErrorCode.TIMEOUT;
-    goog.log.fine(this.logger_, this.formatMsg_(this.lastError_));
-    this.dispatchEvent(goog.net.EventType.TIMEOUT);
-    this.abort(goog.net.ErrorCode.TIMEOUT);
+  if (loading) {
+    return null;
   }
-};
-
 
-/**
- * Something errorred, so inactivate, fire error callback and clean up
- * @param {goog.net.ErrorCode} errorCode The error code.
- * @param {Error} err The error object.
- * @private
- */
-goog.net.XhrIo.prototype.error_ = function(errorCode, err) {
-  this.active_ = false;
-  if (this.xhr_) {
-    this.inAbort_ = true;
-    this.xhr_.abort();  // Ensures XHR isn't hung (FF)
-    this.inAbort_ = false;
+  if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) {
+    this.canvasContext_.canvas.width = size[0];
+    this.canvasContext_.canvas.height = size[1];
+    this.canvasSize_[0] = size[0];
+    this.canvasSize_[1] = size[1];
+  } else {
+    this.canvasContext_.clearRect(0, 0, size[0], size[1]);
   }
-  this.lastError_ = err;
-  this.lastErrorCode_ = errorCode;
-  this.dispatchErrors_();
-  this.cleanUpXhr_();
+
+  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;
 };
 
 
 /**
- * Dispatches COMPLETE and ERROR in case of an error. This ensures that we do
- * not dispatch multiple error events.
- * @private
+ * @inheritDoc
  */
-goog.net.XhrIo.prototype.dispatchErrors_ = function() {
-  if (!this.errorDispatched_) {
-    this.errorDispatched_ = true;
-    this.dispatchEvent(goog.net.EventType.COMPLETE);
-    this.dispatchEvent(goog.net.EventType.ERROR);
+ol.source.ImageVector.prototype.forEachFeatureAtPixel = function(
+    resolution, rotation, coordinate, skippedFeatureUids, callback) {
+  if (goog.isNull(this.replayGroup_)) {
+    return undefined;
+  } else {
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+    return this.replayGroup_.forEachGeometryAtPixel(
+        resolution, 0, coordinate, skippedFeatureUids,
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          goog.asserts.assert(goog.isDef(feature));
+          var key = goog.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback(feature);
+          }
+        });
   }
 };
 
 
 /**
- * Abort the current XMLHttpRequest
- * @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use -
- *     defaults to ABORT.
+ * @return {ol.source.Vector} Source.
+ * @api
  */
-goog.net.XhrIo.prototype.abort = function(opt_failureCode) {
-  if (this.xhr_ && this.active_) {
-    goog.log.fine(this.logger_, this.formatMsg_('Aborting'));
-    this.active_ = false;
-    this.inAbort_ = true;
-    this.xhr_.abort();
-    this.inAbort_ = false;
-    this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT;
-    this.dispatchEvent(goog.net.EventType.COMPLETE);
-    this.dispatchEvent(goog.net.EventType.ABORT);
-    this.cleanUpXhr_();
-  }
+ol.source.ImageVector.prototype.getSource = function() {
+  return this.source_;
 };
 
 
 /**
- * Nullifies all callbacks to reduce risks of leaks.
- * @override
- * @protected
+ * Get the style for features.  This returns whatever was passed to the `style`
+ * option at construction or to the `setStyle` method.
+ * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
+ *     Layer style.
+ * @api stable
  */
-goog.net.XhrIo.prototype.disposeInternal = function() {
-  if (this.xhr_) {
-    // We explicitly do not call xhr_.abort() unless active_ is still true.
-    // This is to avoid unnecessarily aborting a successful request when
-    // dispose() is called in a callback triggered by a complete response, but
-    // in which browser cleanup has not yet finished.
-    // (See http://b/issue?id=1684217.)
-    if (this.active_) {
-      this.active_ = false;
-      this.inAbort_ = true;
-      this.xhr_.abort();
-      this.inAbort_ = false;
-    }
-    this.cleanUpXhr_(true);
-  }
-
-  goog.net.XhrIo.base(this, 'disposeInternal');
+ol.source.ImageVector.prototype.getStyle = function() {
+  return this.style_;
 };
 
 
 /**
- * Internal handler for the XHR object's readystatechange event.  This method
- * checks the status and the readystate and fires the correct callbacks.
- * If the request has ended, the handlers are cleaned up and the XHR object is
- * nullified.
- * @private
+ * Get the style function.
+ * @return {ol.style.StyleFunction|undefined} Layer style function.
+ * @api stable
  */
-goog.net.XhrIo.prototype.onReadyStateChange_ = function() {
-  if (this.isDisposed()) {
-    // This method is the target of an untracked goog.Timer.callOnce().
-    return;
-  }
-  if (!this.inOpen_ && !this.inSend_ && !this.inAbort_) {
-    // Were not being called from within a call to this.xhr_.send
-    // this.xhr_.abort, or this.xhr_.open, so this is an entry point
-    this.onReadyStateChangeEntryPoint_();
-  } else {
-    this.onReadyStateChangeHelper_();
-  }
+ol.source.ImageVector.prototype.getStyleFunction = function() {
+  return this.styleFunction_;
 };
 
 
 /**
- * Used to protect the onreadystatechange handler entry point.  Necessary
- * as {#onReadyStateChange_} maybe called from within send or abort, this
- * method is only called when {#onReadyStateChange_} is called as an
- * entry point.
- * {@see #protectEntryPoints}
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.Size} size Size.
+ * @return {!goog.vec.Mat4.Number} Transform.
  * @private
  */
-goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = function() {
-  this.onReadyStateChangeHelper_();
+ol.source.ImageVector.prototype.getTransform_ =
+    function(center, resolution, pixelRatio, size) {
+  return ol.vec.Mat4.makeTransform2D(this.transform_,
+      size[0] / 2, size[1] / 2,
+      pixelRatio / resolution, -pixelRatio / resolution,
+      0,
+      -center[0], -center[1]);
 };
 
 
 /**
- * Helper for {@link #onReadyStateChange_}.  This is used so that
- * entry point calls to {@link #onReadyStateChange_} can be routed through
- * {@link #onReadyStateChangeEntryPoint_}.
+ * Handle changes in image style state.
+ * @param {goog.events.Event} event Image style change event.
  * @private
  */
-goog.net.XhrIo.prototype.onReadyStateChangeHelper_ = function() {
-  if (!this.active_) {
-    // can get called inside abort call
-    return;
-  }
-
-  if (typeof goog == 'undefined') {
-    // NOTE(user): If goog is undefined then the callback has occurred as the
-    // application is unloading and will error.  Thus we let it silently fail.
-
-  } else if (
-      this.xhrOptions_[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] &&
-      this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE &&
-      this.getStatus() == 2) {
-    // NOTE(user): In IE if send() errors on a *local* request the readystate
-    // is still changed to COMPLETE.  We need to ignore it and allow the
-    // try/catch around send() to pick up the error.
-    goog.log.fine(this.logger_, this.formatMsg_(
-        'Local request error detected and ignored'));
-
-  } else {
-
-    // In IE when the response has been cached we sometimes get the callback
-    // from inside the send call and this usually breaks code that assumes that
-    // XhrIo is asynchronous.  If that is the case we delay the callback
-    // using a timer.
-    if (this.inSend_ &&
-        this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE) {
-      goog.Timer.callOnce(this.onReadyStateChange_, 0, this);
-      return;
-    }
-
-    this.dispatchEvent(goog.net.EventType.READY_STATE_CHANGE);
-
-    // readyState indicates the transfer has finished
-    if (this.isComplete()) {
-      goog.log.fine(this.logger_, this.formatMsg_('Request complete'));
-
-      this.active_ = false;
-
-      try {
-        // Call the specific callbacks for success or failure. Only call the
-        // success if the status is 200 (HTTP_OK) or 304 (HTTP_CACHED)
-        if (this.isSuccess()) {
-          this.dispatchEvent(goog.net.EventType.COMPLETE);
-          this.dispatchEvent(goog.net.EventType.SUCCESS);
-        } else {
-          this.lastErrorCode_ = goog.net.ErrorCode.HTTP_ERROR;
-          this.lastError_ =
-              this.getStatusText() + ' [' + this.getStatus() + ']';
-          this.dispatchErrors_();
-        }
-      } finally {
-        this.cleanUpXhr_();
-      }
-    }
-  }
+ol.source.ImageVector.prototype.handleImageChange_ =
+    function(event) {
+  this.changed();
 };
 
 
 /**
- * Remove the listener to protect against leaks, and nullify the XMLHttpRequest
- * object.
- * @param {boolean=} opt_fromDispose If this is from the dispose (don't want to
- *     fire any events).
  * @private
  */
-goog.net.XhrIo.prototype.cleanUpXhr_ = function(opt_fromDispose) {
-  if (this.xhr_) {
-    // Cancel any pending timeout event handler.
-    this.cleanUpTimeoutTimer_();
-
-    // Save reference so we can mark it as closed after the READY event.  The
-    // READY event may trigger another request, thus we must nullify this.xhr_
-    var xhr = this.xhr_;
-    var clearedOnReadyStateChange =
-        this.xhrOptions_[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] ?
-            goog.nullFunction : null;
-    this.xhr_ = null;
-    this.xhrOptions_ = null;
-
-    if (!opt_fromDispose) {
-      this.dispatchEvent(goog.net.EventType.READY);
-    }
-
-    try {
-      // NOTE(user): Not nullifying in FireFox can still leak if the callbacks
-      // are defined in the same scope as the instance of XhrIo. But, IE doesn't
-      // allow you to set the onreadystatechange to NULL so nullFunction is
-      // used.
-      xhr.onreadystatechange = clearedOnReadyStateChange;
-    } catch (e) {
-      // This seems to occur with a Gears HTTP request. Delayed the setting of
-      // this onreadystatechange until after READY is sent out and catching the
-      // error to see if we can track down the problem.
-      goog.log.error(this.logger_,
-          'Problem encountered resetting onreadystatechange: ' + e.message);
-    }
-  }
+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());
 };
 
 
 /**
- * Make sure the timeout timer isn't running.
+ * @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
  */
-goog.net.XhrIo.prototype.cleanUpTimeoutTimer_ = function() {
-  if (this.xhr_ && this.useXhr2Timeout_) {
-    this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] = null;
+ol.source.ImageVector.prototype.renderFeature_ =
+    function(feature, resolution, pixelRatio, replayGroup) {
+  var styles;
+  if (goog.isDef(feature.getStyleFunction())) {
+    styles = feature.getStyleFunction().call(feature, resolution);
+  } else if (goog.isDef(this.styleFunction_)) {
+    styles = this.styleFunction_(feature, resolution);
   }
-  if (goog.isNumber(this.timeoutId_)) {
-    goog.Timer.clear(this.timeoutId_);
-    this.timeoutId_ = null;
+  if (!goog.isDefAndNotNull(styles)) {
+    return false;
+  }
+  var i, ii, loading = false;
+  for (i = 0, ii = styles.length; i < ii; ++i) {
+    loading = ol.renderer.vector.renderFeature(
+        replayGroup, feature, styles[i],
+        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+        this.handleImageChange_, this) || loading;
   }
+  return loading;
 };
 
 
 /**
- * @return {boolean} Whether there is an active request.
+ * Set the style for features.  This can be a single style object, an array
+ * of styles, or a function that takes a feature and resolution and returns
+ * an array of styles. If it is `undefined` the default style is used. If
+ * it is `null` the layer has no style (a `null` style), so only features
+ * that have their own styles will be rendered in the layer. See
+ * {@link ol.style} for information on the default style.
+ * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined}
+ *     style Layer style.
+ * @api stable
  */
-goog.net.XhrIo.prototype.isActive = function() {
-  return !!this.xhr_;
+ol.source.ImageVector.prototype.setStyle = function(style) {
+  this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction;
+  this.styleFunction_ = goog.isNull(style) ?
+      undefined : ol.style.createStyleFunction(this.style_);
+  this.changed();
 };
 
+goog.provide('ol.source.wms');
+goog.provide('ol.source.wms.ServerType');
+
 
 /**
- * @return {boolean} Whether the request has completed.
+ * Available server types: `'carmentaserver'`, `'geoserver'`, `'mapserver'`,
+ *     `'qgis'`. These are servers that have vendor parameters beyond the WMS
+ *     specification that OpenLayers can make use of.
+ * @enum {string}
+ * @api
  */
-goog.net.XhrIo.prototype.isComplete = function() {
-  return this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE;
+ol.source.wms.ServerType = {
+  CARMENTA_SERVER: 'carmentaserver',
+  GEOSERVER: 'geoserver',
+  MAPSERVER: 'mapserver',
+  QGIS: 'qgis'
 };
 
+// FIXME cannot be shared between maps with different projections
+
+goog.provide('ol.source.ImageWMS');
+
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.uri.utils');
+goog.require('ol');
+goog.require('ol.Image');
+goog.require('ol.ImageLoadFunctionType');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.Image');
+goog.require('ol.source.wms');
+goog.require('ol.source.wms.ServerType');
 
-/**
- * @return {boolean} Whether the request completed with a success.
- */
-goog.net.XhrIo.prototype.isSuccess = function() {
-  var status = this.getStatus();
-  // A zero status code is considered successful for local files.
-  return goog.net.HttpStatus.isSuccess(status) ||
-      status === 0 && !this.isLastUriEffectiveSchemeHttp_();
-};
 
 
 /**
- * @return {boolean} whether the effective scheme of the last URI that was
- *     fetched was 'http' or 'https'.
- * @private
+ * @classdesc
+ * Source for WMS servers providing single, untiled images.
+ *
+ * @constructor
+ * @extends {ol.source.Image}
+ * @param {olx.source.ImageWMSOptions=} opt_options Options.
+ * @api stable
  */
-goog.net.XhrIo.prototype.isLastUriEffectiveSchemeHttp_ = function() {
-  var scheme = goog.uri.utils.getEffectiveScheme(String(this.lastUri_));
-  return goog.net.XhrIo.HTTP_SCHEME_PATTERN.test(scheme);
-};
+ol.source.ImageWMS = function(opt_options) {
 
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-/**
- * Get the readystate from the Xhr object
- * Will only return correct result when called from the context of a callback
- * @return {goog.net.XmlHttp.ReadyState} goog.net.XmlHttp.ReadyState.*.
- */
-goog.net.XhrIo.prototype.getReadyState = function() {
-  return this.xhr_ ?
-      /** @type {goog.net.XmlHttp.ReadyState} */ (this.xhr_.readyState) :
-      goog.net.XmlHttp.ReadyState.UNINITIALIZED;
-};
+  goog.base(this, {
+    attributions: options.attributions,
+    logo: options.logo,
+    projection: options.projection,
+    resolutions: options.resolutions
+  });
+
+  /**
+   * @private
+   * @type {?string}
+   */
+  this.crossOrigin_ =
+      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.url_ = options.url;
+
+  /**
+   * @private
+   * @type {ol.ImageLoadFunctionType}
+   */
+  this.imageLoadFunction_ = goog.isDef(options.imageLoadFunction) ?
+      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+
+  /**
+   * @private
+   * @type {Object}
+   */
+  this.params_ = options.params;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.v13_ = true;
+  this.updateV13_();
 
-/**
- * Get the status from the Xhr object
- * Will only return correct result when called from the context of a callback
- * @return {number} Http status.
- */
-goog.net.XhrIo.prototype.getStatus = function() {
   /**
-   * IE doesn't like you checking status until the readystate is greater than 2
-   * (i.e. it is receiving or complete).  The try/catch is used for when the
-   * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_.
-   * @preserveTry
+   * @private
+   * @type {ol.source.wms.ServerType|undefined}
    */
-  try {
-    return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ?
-        this.xhr_.status : -1;
-  } catch (e) {
-    return -1;
-  }
-};
+  this.serverType_ =
+      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.hidpi_ = goog.isDef(options.hidpi) ? options.hidpi : true;
 
-/**
- * Get the status text from the Xhr object
- * Will only return correct result when called from the context of a callback
- * @return {string} Status text.
- */
-goog.net.XhrIo.prototype.getStatusText = function() {
   /**
-   * IE doesn't like you checking status until the readystate is greater than 2
-   * (i.e. it is recieving or complete).  The try/catch is used for when the
-   * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_.
-   * @preserveTry
+   * @private
+   * @type {ol.Image}
    */
-  try {
-    return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ?
-        this.xhr_.statusText : '';
-  } catch (e) {
-    goog.log.fine(this.logger_, 'Can not get status: ' + e.message);
-    return '';
-  }
-};
+  this.image_ = null;
 
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.imageSize_ = [0, 0];
 
-/**
- * Get the last Uri that was requested
- * @return {string} Last Uri.
- */
-goog.net.XhrIo.prototype.getLastUri = function() {
-  return String(this.lastUri_);
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.ratio_ = goog.isDef(options.ratio) ? options.ratio : 1.5;
 
-/**
- * Get the response text from the Xhr object
- * Will only return correct result when called from the context of a callback.
- * @return {string} Result from the server, or '' if no result available.
- */
-goog.net.XhrIo.prototype.getResponseText = function() {
-  /** @preserveTry */
-  try {
-    return this.xhr_ ? this.xhr_.responseText : '';
-  } catch (e) {
-    // http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
-    // states that responseText should return '' (and responseXML null)
-    // when the state is not LOADING or DONE. Instead, IE can
-    // throw unexpected exceptions, for example when a request is aborted
-    // or no data is available yet.
-    goog.log.fine(this.logger_, 'Can not get responseText: ' + e.message);
-    return '';
-  }
 };
+goog.inherits(ol.source.ImageWMS, ol.source.Image);
 
 
 /**
- * Get the response body from the Xhr object. This property is only available
- * in IE since version 7 according to MSDN:
- * http://msdn.microsoft.com/en-us/library/ie/ms534368(v=vs.85).aspx
- * Will only return correct result when called from the context of a callback.
- *
- * One option is to construct a VBArray from the returned object and convert
- * it to a JavaScript array using the toArray method:
- * {@code (new window['VBArray'](xhrIo.getResponseBody())).toArray()}
- * This will result in an array of numbers in the range of [0..255]
- *
- * Another option is to use the VBScript CStr method to convert it into a
- * string as outlined in http://stackoverflow.com/questions/1919972
- *
- * @return {Object} Binary result from the server or null if not available.
+ * @const
+ * @type {ol.Size}
+ * @private
  */
-goog.net.XhrIo.prototype.getResponseBody = function() {
-  /** @preserveTry */
-  try {
-    if (this.xhr_ && 'responseBody' in this.xhr_) {
-      return this.xhr_['responseBody'];
-    }
-  } catch (e) {
-    // IE can throw unexpected exceptions, for example when a request is aborted
-    // or no data is yet available.
-    goog.log.fine(this.logger_, 'Can not get responseBody: ' + e.message);
-  }
-  return null;
-};
+ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101];
 
 
 /**
- * Get the response XML from the Xhr object
- * Will only return correct result when called from the context of a callback.
- * @return {Document} The DOM Document representing the XML file, or null
- * if no result available.
+ * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
+ * projection. Return `undefined` if the GetFeatureInfo URL cannot be
+ * constructed.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.ProjectionLike} projection Projection.
+ * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
+ *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
+ *     in the `LAYERS` parameter will be used. `VERSION` should not be
+ *     specified here.
+ * @return {string|undefined} GetFeatureInfo URL.
+ * @api stable
  */
-goog.net.XhrIo.prototype.getResponseXml = function() {
-  /** @preserveTry */
-  try {
-    return this.xhr_ ? this.xhr_.responseXML : null;
-  } catch (e) {
-    goog.log.fine(this.logger_, 'Can not get responseXML: ' + e.message);
-    return null;
-  }
-};
+ol.source.ImageWMS.prototype.getGetFeatureInfoUrl =
+    function(coordinate, resolution, projection, params) {
 
+  goog.asserts.assert(!('VERSION' in params));
 
-/**
- * Get the response and evaluates it as JSON from the Xhr object
- * Will only return correct result when called from the context of a callback
- * @param {string=} opt_xssiPrefix Optional XSSI prefix string to use for
- *     stripping of the response before parsing. This needs to be set only if
- *     your backend server prepends the same prefix string to the JSON response.
- * @return {Object|undefined} JavaScript object.
- */
-goog.net.XhrIo.prototype.getResponseJson = function(opt_xssiPrefix) {
-  if (!this.xhr_) {
+  if (!goog.isDef(this.url_)) {
     return undefined;
   }
 
-  var responseText = this.xhr_.responseText;
-  if (opt_xssiPrefix && responseText.indexOf(opt_xssiPrefix) == 0) {
-    responseText = responseText.substring(opt_xssiPrefix.length);
-  }
+  var extent = ol.extent.getForViewAndSize(
+      coordinate, resolution, 0,
+      ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_);
 
-  return goog.json.parse(responseText);
-};
+  var baseParams = {
+    'SERVICE': 'WMS',
+    'VERSION': ol.DEFAULT_WMS_VERSION,
+    'REQUEST': 'GetFeatureInfo',
+    'FORMAT': 'image/png',
+    'TRANSPARENT': true,
+    'QUERY_LAYERS': goog.object.get(this.params_, 'LAYERS')
+  };
+  goog.object.extend(baseParams, this.params_, params);
 
+  var x = Math.floor((coordinate[0] - extent[0]) / resolution);
+  var y = Math.floor((extent[3] - coordinate[1]) / resolution);
+  goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x);
+  goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y);
 
-/**
- * Get the response as the type specificed by {@link #setResponseType}. At time
- * of writing, this is only directly supported in very recent versions of WebKit
- * (10.0.612.1 dev and later). If the field is not supported directly, we will
- * try to emulate it.
- *
- * Emulating the response means following the rules laid out at
- * http://www.w3.org/TR/XMLHttpRequest/#the-response-attribute
- *
- * On browsers with no support for this (Chrome < 10, Firefox < 4, etc), only
- * response types of DEFAULT or TEXT may be used, and the response returned will
- * be the text response.
- *
- * On browsers with Mozilla's draft support for array buffers (Firefox 4, 5),
- * only response types of DEFAULT, TEXT, and ARRAY_BUFFER may be used, and the
- * response returned will be either the text response or the Mozilla
- * implementation of the array buffer response.
- *
- * On browsers will full support, any valid response type supported by the
- * browser may be used, and the response provided by the browser will be
- * returned.
- *
- * @return {*} The response.
- */
-goog.net.XhrIo.prototype.getResponse = function() {
-  /** @preserveTry */
-  try {
-    if (!this.xhr_) {
-      return null;
-    }
-    if ('response' in this.xhr_) {
-      return this.xhr_.response;
-    }
-    switch (this.responseType_) {
-      case goog.net.XhrIo.ResponseType.DEFAULT:
-      case goog.net.XhrIo.ResponseType.TEXT:
-        return this.xhr_.responseText;
-        // DOCUMENT and BLOB don't need to be handled here because they are
-        // introduced in the same spec that adds the .response field, and would
-        // have been caught above.
-        // ARRAY_BUFFER needs an implementation for Firefox 4, where it was
-        // implemented using a draft spec rather than the final spec.
-      case goog.net.XhrIo.ResponseType.ARRAY_BUFFER:
-        if ('mozResponseArrayBuffer' in this.xhr_) {
-          return this.xhr_.mozResponseArrayBuffer;
-        }
-    }
-    // Fell through to a response type that is not supported on this browser.
-    goog.log.error(this.logger_,
-        'Response type ' + this.responseType_ + ' is not ' +
-        'supported on this browser');
-    return null;
-  } catch (e) {
-    goog.log.fine(this.logger_, 'Can not get response: ' + e.message);
-    return null;
-  }
+  return this.getRequestUrl_(
+      extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_,
+      1, ol.proj.get(projection), baseParams);
 };
 
 
 /**
- * Get the value of the response-header with the given name from the Xhr object
- * Will only return correct result when called from the context of a callback
- * and the request has completed
- * @param {string} key The name of the response-header to retrieve.
- * @return {string|undefined} The value of the response-header named key.
+ * 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
  */
-goog.net.XhrIo.prototype.getResponseHeader = function(key) {
-  return this.xhr_ && this.isComplete() ?
-      this.xhr_.getResponseHeader(key) : undefined;
+ol.source.ImageWMS.prototype.getParams = function() {
+  return this.params_;
 };
 
 
 /**
- * Gets the text of all the headers in the response.
- * Will only return correct result when called from the context of a callback
- * and the request has completed.
- * @return {string} The value of the response headers or empty string.
+ * @inheritDoc
  */
-goog.net.XhrIo.prototype.getAllResponseHeaders = function() {
-  return this.xhr_ && this.isComplete() ?
-      this.xhr_.getAllResponseHeaders() : '';
-};
+ol.source.ImageWMS.prototype.getImage =
+    function(extent, resolution, pixelRatio, projection) {
 
+  if (!goog.isDef(this.url_)) {
+    return null;
+  }
 
-/**
- * Returns all response headers as a key-value map.
- * Multiple values for the same header key can be combined into one,
- * separated by a comma and a space.
- * Note that the native getResponseHeader method for retrieving a single header
- * does a case insensitive match on the header name. This method does not
- * include any case normalization logic, it will just return a key-value
- * representation of the headers.
- * See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
- * @return {!Object.<string, string>} An object with the header keys as keys
- *     and header values as values.
- */
-goog.net.XhrIo.prototype.getResponseHeaders = function() {
-  var headersObject = {};
-  var headersArray = this.getAllResponseHeaders().split('\r\n');
-  for (var i = 0; i < headersArray.length; i++) {
-    if (goog.string.isEmpty(headersArray[i])) {
-      continue;
-    }
-    var keyValue = goog.string.splitLimit(headersArray[i], ': ', 2);
-    if (headersObject[keyValue[0]]) {
-      headersObject[keyValue[0]] += ', ' + keyValue[1];
-    } else {
-      headersObject[keyValue[0]] = keyValue[1];
-    }
+  resolution = this.findNearestResolution(resolution);
+
+  if (pixelRatio != 1 && (!this.hidpi_ || !goog.isDef(this.serverType_))) {
+    pixelRatio = 1;
   }
-  return headersObject;
-};
 
+  var image = this.image_;
+  if (!goog.isNull(image) &&
+      this.renderedRevision_ == this.getRevision() &&
+      image.getResolution() == resolution &&
+      image.getPixelRatio() == pixelRatio &&
+      ol.extent.containsExtent(image.getExtent(), extent)) {
+    return image;
+  }
 
-/**
- * Get the last error message
- * @return {goog.net.ErrorCode} Last error code.
- */
-goog.net.XhrIo.prototype.getLastErrorCode = function() {
-  return this.lastErrorCode_;
-};
+  var params = {
+    'SERVICE': 'WMS',
+    'VERSION': ol.DEFAULT_WMS_VERSION,
+    'REQUEST': 'GetMap',
+    'FORMAT': 'image/png',
+    'TRANSPARENT': true
+  };
+  goog.object.extend(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;
+  }
 
-/**
- * Get the last error message
- * @return {string} Last error message.
- */
-goog.net.XhrIo.prototype.getLastError = function() {
-  return goog.isString(this.lastError_) ? this.lastError_ :
-      String(this.lastError_);
-};
+  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);
 
-/**
- * Adds the last method, status and URI to the message.  This is used to add
- * this information to the logging calls.
- * @param {string} msg The message text that we want to add the extra text to.
- * @return {string} The message with the extra text appended.
- * @private
- */
-goog.net.XhrIo.prototype.formatMsg_ = function(msg) {
-  return msg + ' [' + this.lastMethod_ + ' ' + this.lastUri_ + ' ' +
-      this.getStatus() + ']';
-};
+  // 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;
 
-// Register the xhr handler as an entry point, so that
-// it can be monitored for exception handling, etc.
-goog.debug.entryPointRegistry.register(
-    /**
-     * @param {function(!Function): !Function} transformer The transforming
-     *     function.
-     */
-    function(transformer) {
-      goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ =
-          transformer(goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);
-    });
+  var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
+      projection, params);
 
-// FIXME consider delaying feature reading so projection can be provided by
-// consumer (e.g. the view)
+  this.image_ = new ol.Image(extent, resolution, pixelRatio,
+      this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);
 
-goog.provide('ol.source.FormatVector');
+  this.renderedRevision_ = this.getRevision();
 
-goog.require('goog.asserts');
-goog.require('goog.dispose');
-goog.require('goog.events');
-goog.require('goog.net.EventType');
-goog.require('goog.net.XhrIo');
-goog.require('goog.net.XhrIo.ResponseType');
-goog.require('goog.userAgent');
-goog.require('ol.format.FormatType');
-goog.require('ol.has');
-goog.require('ol.source.State');
-goog.require('ol.source.Vector');
-goog.require('ol.xml');
+  return this.image_;
 
+};
 
 
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for vector sources in one of the supported formats.
- *
- * @constructor
- * @extends {ol.source.Vector}
- * @param {olx.source.FormatVectorOptions} options Options.
+ * @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.FormatVector = function(options) {
-
-  goog.base(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection
-  });
+ol.source.ImageWMS.prototype.getRequestUrl_ =
+    function(extent, size, pixelRatio, projection, params) {
 
-  /**
-   * @protected
-   * @type {ol.format.Feature}
-   */
-  this.format = options.format;
+  goog.asserts.assert(goog.isDef(this.url_));
 
-};
-goog.inherits(ol.source.FormatVector, ol.source.Vector);
+  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
 
+  if (!('STYLES' in this.params_)) {
+    /* jshint -W053 */
+    goog.object.set(params, 'STYLES', new String(''));
+    /* jshint +W053 */
+  }
 
-/**
- * @param {goog.Uri|string} url URL.
- * @param {function(this: T, Array.<ol.Feature>)} callback Callback.
- * @param {T} thisArg Value to use as `this` when executing `callback`.
- * @template T
- */
-ol.source.FormatVector.prototype.loadFeaturesFromURL =
-    function(url, callback, thisArg) {
-  var xhrIo = new goog.net.XhrIo();
-  var type = this.format.getType();
-  var responseType;
-  // FIXME maybe use ResponseType.DOCUMENT?
-  if (type == ol.format.FormatType.BINARY &&
-      ol.has.ARRAY_BUFFER) {
-    responseType = goog.net.XhrIo.ResponseType.ARRAY_BUFFER;
-  } else {
-    responseType = goog.net.XhrIo.ResponseType.TEXT;
+  if (pixelRatio != 1) {
+    switch (this.serverType_) {
+      case ol.source.wms.ServerType.GEOSERVER:
+        var dpi = (90 * pixelRatio + 0.5) | 0;
+        goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi);
+        break;
+      case ol.source.wms.ServerType.MAPSERVER:
+        goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio);
+        break;
+      case ol.source.wms.ServerType.CARMENTA_SERVER:
+      case ol.source.wms.ServerType.QGIS:
+        goog.object.set(params, 'DPI', 90 * pixelRatio);
+        break;
+      default:
+        goog.asserts.fail();
+        break;
+    }
   }
-  xhrIo.setResponseType(responseType);
-  goog.events.listen(xhrIo, goog.net.EventType.COMPLETE,
-      /**
-       * @param {Event} event Event.
-       * @private
-       * @this {ol.source.FormatVector}
-       */
-      function(event) {
-        var xhrIo = event.target;
-        goog.asserts.assertInstanceof(xhrIo, goog.net.XhrIo);
-        if (xhrIo.isSuccess()) {
-          var type = this.format.getType();
-          /** @type {ArrayBuffer|Document|Node|Object|string|undefined} */
-          var source;
-          if (type == ol.format.FormatType.BINARY &&
-              ol.has.ARRAY_BUFFER) {
-            source = xhrIo.getResponse();
-            goog.asserts.assertInstanceof(source, ArrayBuffer);
-          } else if (type == ol.format.FormatType.JSON) {
-            source = xhrIo.getResponseText();
-          } else if (type == ol.format.FormatType.TEXT) {
-            source = xhrIo.getResponseText();
-          } else if (type == ol.format.FormatType.XML) {
-            if (!goog.userAgent.IE) {
-              source = xhrIo.getResponseXml();
-            }
-            if (!goog.isDefAndNotNull(source)) {
-              source = ol.xml.load(xhrIo.getResponseText());
-            }
-          } else {
-            goog.asserts.fail();
-          }
-          if (goog.isDefAndNotNull(source)) {
-            callback.call(thisArg, this.readFeatures(source));
-          } else {
-            this.setState(ol.source.State.ERROR);
-            goog.asserts.fail();
-          }
-        } else {
-          this.setState(ol.source.State.ERROR);
-        }
-        goog.dispose(xhrIo);
-      }, false, this);
-  xhrIo.send(url);
+
+  goog.object.set(params, 'WIDTH', size[0]);
+  goog.object.set(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;
+  }
+  goog.object.set(params, 'BBOX', bbox.join(','));
+
+  return goog.uri.utils.appendParamsFromMap(this.url_, params);
 };
 
 
 /**
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {Array.<ol.Feature>} Features.
- * @api
+ * Return the URL used for this WMS source.
+ * @return {string|undefined} URL.
+ * @api stable
  */
-ol.source.FormatVector.prototype.readFeatures = function(source) {
-  var format = this.format;
-  var projection = this.getProjection();
-  return format.readFeatures(source, {featureProjection: projection});
+ol.source.ImageWMS.prototype.getUrl = function() {
+  return this.url_;
 };
 
-goog.provide('ol.source.StaticVector');
-
-goog.require('ol.source.FormatVector');
-goog.require('ol.source.State');
-
-
 
 /**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for vector sources in one of the supported formats, where the data
- * is read from a file or other static source.
- *
- * @constructor
- * @extends {ol.source.FormatVector}
- * @fires ol.source.VectorEvent
- * @param {olx.source.StaticVectorOptions} options Options.
- * @api
+ * @param {string|undefined} url URL.
+ * @api stable
  */
-ol.source.StaticVector = function(options) {
-
-  goog.base(this, {
-    attributions: options.attributions,
-    format: options.format,
-    logo: options.logo,
-    projection: options.projection
-  });
-
-  if (goog.isDef(options.arrayBuffer)) {
-    this.addFeaturesInternal(this.readFeatures(options.arrayBuffer));
-  }
-
-  if (goog.isDef(options.doc)) {
-    this.addFeaturesInternal(this.readFeatures(options.doc));
-  }
-
-  if (goog.isDef(options.node)) {
-    this.addFeaturesInternal(this.readFeatures(options.node));
-  }
-
-  if (goog.isDef(options.object)) {
-    this.addFeaturesInternal(this.readFeatures(options.object));
-  }
-
-  if (goog.isDef(options.text)) {
-    this.addFeaturesInternal(this.readFeatures(options.text));
+ol.source.ImageWMS.prototype.setUrl = function(url) {
+  if (url != this.url_) {
+    this.url_ = url;
+    this.image_ = null;
+    this.changed();
   }
+};
 
-  if (goog.isDef(options.url) || goog.isDef(options.urls)) {
-    this.setState(ol.source.State.LOADING);
-    if (goog.isDef(options.url)) {
-      this.loadFeaturesFromURL(options.url, this.onFeaturesLoaded_, this);
-    }
-    if (goog.isDef(options.urls)) {
-      var urls = options.urls;
-      var i, ii;
-      for (i = 0, ii = urls.length; i < ii; ++i) {
-        this.loadFeaturesFromURL(urls[i], this.onFeaturesLoaded_, this);
-      }
-    }
-  }
 
+/**
+ * Update the user-provided params.
+ * @param {Object} params Params.
+ * @api stable
+ */
+ol.source.ImageWMS.prototype.updateParams = function(params) {
+  goog.object.extend(this.params_, params);
+  this.updateV13_();
+  this.image_ = null;
+  this.changed();
 };
-goog.inherits(ol.source.StaticVector, ol.source.FormatVector);
 
 
 /**
- * @param {Array.<ol.Feature>} features Features.
  * @private
  */
-ol.source.StaticVector.prototype.onFeaturesLoaded_ = function(features) {
-  this.addFeaturesInternal(features);
-  this.setState(ol.source.State.READY);
+ol.source.ImageWMS.prototype.updateV13_ = function() {
+  var version =
+      goog.object.get(this.params_, 'VERSION', ol.DEFAULT_WMS_VERSION);
+  this.v13_ = goog.string.compareVersions(version, '1.3') >= 0;
 };
 
-goog.provide('ol.source.GeoJSON');
+goog.provide('ol.source.KML');
 
-goog.require('ol.format.GeoJSON');
+goog.require('ol.format.KML');
 goog.require('ol.source.StaticVector');
 
 
 
 /**
  * @classdesc
- * Static vector source in GeoJSON format
+ * Static vector source in KML format
  *
  * @constructor
  * @extends {ol.source.StaticVector}
  * @fires ol.source.VectorEvent
- * @param {olx.source.GeoJSONOptions=} opt_options Options.
+ * @param {olx.source.KMLOptions=} opt_options Options.
  * @api
  */
-ol.source.GeoJSON = function(opt_options) {
+ol.source.KML = function(opt_options) {
 
   var options = goog.isDef(opt_options) ? opt_options : {};
 
   goog.base(this, {
     attributions: options.attributions,
-    extent: options.extent,
-    format: new ol.format.GeoJSON({
-      defaultDataProjection: options.defaultProjection
+    doc: options.doc,
+    format: new ol.format.KML({
+      extractStyles: options.extractStyles,
+      defaultStyle: options.defaultStyle
     }),
     logo: options.logo,
-    object: options.object,
+    node: options.node,
     projection: options.projection,
     text: options.text,
     url: options.url,
@@ -95971,1231 +108929,1237 @@ ol.source.GeoJSON = function(opt_options) {
   });
 
 };
-goog.inherits(ol.source.GeoJSON, ol.source.StaticVector);
+goog.inherits(ol.source.KML, ol.source.StaticVector);
 
-goog.provide('ol.source.GPX');
+goog.provide('ol.source.XYZ');
 
-goog.require('ol.format.GPX');
-goog.require('ol.source.StaticVector');
+goog.require('ol.Attribution');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilegrid.XYZ');
 
 
 
 /**
  * @classdesc
- * Static vector source in GPX format
+ * Layer source for tile data with URLs in a set XYZ format.
  *
  * @constructor
- * @extends {ol.source.StaticVector}
- * @fires ol.source.VectorEvent
- * @param {olx.source.GPXOptions=} opt_options Options.
- * @api
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.XYZOptions} options XYZ options.
+ * @api stable
  */
-ol.source.GPX = function(opt_options) {
+ol.source.XYZ = function(options) {
+  var projection = goog.isDef(options.projection) ?
+      options.projection : 'EPSG:3857';
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  var tileGrid = new ol.tilegrid.XYZ({
+    extent: ol.tilegrid.extentFromProjection(projection),
+    maxZoom: options.maxZoom,
+    tileSize: options.tileSize
+  });
 
   goog.base(this, {
     attributions: options.attributions,
-    doc: options.doc,
-    extent: options.extent,
-    format: new ol.format.GPX(),
+    crossOrigin: options.crossOrigin,
     logo: options.logo,
-    node: options.node,
-    projection: options.projection,
-    text: options.text,
-    url: options.url,
-    urls: options.urls
+    projection: projection,
+    tileGrid: tileGrid,
+    tileLoadFunction: options.tileLoadFunction,
+    tilePixelRatio: options.tilePixelRatio,
+    tileUrlFunction: ol.TileUrlFunction.nullTileUrlFunction
   });
 
-};
-goog.inherits(ol.source.GPX, ol.source.StaticVector);
-
-goog.provide('ol.source.IGC');
+  /**
+   * @private
+   * @type {ol.TileCoordTransformType}
+   */
+  this.tileCoordTransform_ = tileGrid.createTileCoordTransform({
+    wrapX: options.wrapX
+  });
 
-goog.require('ol.format.IGC');
-goog.require('ol.source.StaticVector');
+  if (goog.isDef(options.tileUrlFunction)) {
+    this.setTileUrlFunction(options.tileUrlFunction);
+  } else if (goog.isDef(options.urls)) {
+    this.setUrls(options.urls);
+  } else if (goog.isDef(options.url)) {
+    this.setUrl(options.url);
+  }
 
+};
+goog.inherits(ol.source.XYZ, ol.source.TileImage);
 
 
 /**
- * @classdesc
- * Static vector source in IGC format
- *
- * @constructor
- * @extends {ol.source.StaticVector}
- * @fires ol.source.VectorEvent
- * @param {olx.source.IGCOptions=} opt_options Options.
+ * @inheritDoc
  * @api
  */
-ol.source.IGC = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  goog.base(this, {
-    format: new ol.format.IGC({
-      altitudeMode: options.altitudeMode
-    }),
-    projection: options.projection,
-    text: options.text,
-    url: options.url,
-    urls: options.urls
-  });
-
+ol.source.XYZ.prototype.setTileUrlFunction = function(tileUrlFunction) {
+  goog.base(this, 'setTileUrlFunction',
+      ol.TileUrlFunction.withTileCoordTransform(
+          this.tileCoordTransform_, tileUrlFunction));
 };
-goog.inherits(ol.source.IGC, ol.source.StaticVector);
 
-goog.provide('ol.source.Image');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('ol.Attribution');
-goog.require('ol.Extent');
-goog.require('ol.array');
-goog.require('ol.source.Source');
+/**
+ * @param {string} url URL.
+ * @api stable
+ */
+ol.source.XYZ.prototype.setUrl = function(url) {
+  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(
+      ol.TileUrlFunction.expandUrl(url)));
+};
 
 
 /**
- * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
- *            extent: (null|ol.Extent|undefined),
- *            logo: (string|olx.LogoOptions|undefined),
- *            projection: ol.proj.ProjectionLike,
- *            resolutions: (Array.<number>|undefined),
- *            state: (ol.source.State|undefined)}}
+ * @param {Array.<string>} urls URLs.
  */
-ol.source.ImageOptions;
+ol.source.XYZ.prototype.setUrls = function(urls) {
+  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(urls));
+};
+
+goog.provide('ol.source.OSM');
+
+goog.require('ol');
+goog.require('ol.Attribution');
+goog.require('ol.source.XYZ');
 
 
 
 /**
  * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for sources providing a single image.
+ * Layer source for the OpenStreetMap tile server.
  *
  * @constructor
- * @extends {ol.source.Source}
- * @param {ol.source.ImageOptions} options Single image source options.
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.OSMOptions=} opt_options Open Street Map options.
+ * @api stable
  */
-ol.source.Image = function(options) {
-
-  goog.base(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    projection: options.projection,
-    state: options.state
-  });
+ol.source.OSM = function(opt_options) {
 
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.resolutions_ = goog.isDef(options.resolutions) ?
-      options.resolutions : null;
-  goog.asserts.assert(goog.isNull(this.resolutions_) ||
-      goog.array.isSorted(this.resolutions_,
-          function(a, b) {
-            return b - a;
-          }, true));
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-};
-goog.inherits(ol.source.Image, ol.source.Source);
+  var attributions;
+  if (goog.isDef(options.attributions)) {
+    attributions = options.attributions;
+  } else {
+    attributions = [ol.source.OSM.ATTRIBUTION];
+  }
 
+  var crossOrigin = goog.isDef(options.crossOrigin) ?
+      options.crossOrigin : 'anonymous';
 
-/**
- * @return {Array.<number>} Resolutions.
- */
-ol.source.Image.prototype.getResolutions = function() {
-  return this.resolutions_;
-};
+  var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
+  var url = goog.isDef(options.url) ?
+      options.url : protocol + '//{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';
 
+  goog.base(this, {
+    attributions: attributions,
+    crossOrigin: crossOrigin,
+    opaque: true,
+    maxZoom: goog.isDef(options.maxZoom) ? options.maxZoom : 19,
+    tileLoadFunction: options.tileLoadFunction,
+    url: url
+  });
 
-/**
- * @protected
- * @param {number} resolution Resolution.
- * @return {number} Resolution.
- */
-ol.source.Image.prototype.findNearestResolution =
-    function(resolution) {
-  if (!goog.isNull(this.resolutions_)) {
-    var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0);
-    resolution = this.resolutions_[idx];
-  }
-  return resolution;
 };
+goog.inherits(ol.source.OSM, ol.source.XYZ);
 
 
 /**
- * @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.
+ * @const
+ * @type {ol.Attribution}
+ * @api
  */
-ol.source.Image.prototype.getImage = goog.abstractMethod;
+ol.source.OSM.ATTRIBUTION = new ol.Attribution({
+  html: '&copy; ' +
+      '<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> ' +
+      'contributors.'
+});
 
-goog.provide('ol.source.ImageCanvas');
+goog.provide('ol.source.MapQuest');
 
-goog.require('ol.CanvasFunctionType');
-goog.require('ol.ImageCanvas');
-goog.require('ol.extent');
-goog.require('ol.source.Image');
+goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.Attribution');
+goog.require('ol.source.OSM');
+goog.require('ol.source.XYZ');
 
 
 
 /**
  * @classdesc
- * Base class for image sources where a canvas element is the image.
+ * Layer source for the MapQuest tile server.
  *
  * @constructor
- * @extends {ol.source.Image}
- * @param {olx.source.ImageCanvasOptions} options
- * @api
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.MapQuestOptions=} opt_options MapQuest options.
+ * @api stable
  */
-ol.source.ImageCanvas = function(options) {
-
-  goog.base(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection,
-    resolutions: options.resolutions,
-    state: goog.isDef(options.state) ?
-        /** @type {ol.source.State} */ (options.state) : undefined
-  });
+ol.source.MapQuest = function(opt_options) {
 
-  /**
-   * @private
-   * @type {ol.CanvasFunctionType}
-   */
-  this.canvasFunction_ = options.canvasFunction;
+  var options = goog.isDef(opt_options) ? opt_options : {};
+  goog.asserts.assert(options.layer in ol.source.MapQuestConfig);
 
-  /**
-   * @private
-   * @type {ol.ImageCanvas}
-   */
-  this.canvas_ = null;
+  var layerConfig = ol.source.MapQuestConfig[options.layer];
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = 0;
+  var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
+  var url = protocol + '//otile{1-4}-s.mqcdn.com/tiles/1.0.0/' +
+      options.layer + '/{z}/{x}/{y}.jpg';
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.ratio_ = goog.isDef(options.ratio) ?
-      options.ratio : 1.5;
+  goog.base(this, {
+    attributions: layerConfig.attributions,
+    crossOrigin: 'anonymous',
+    logo: '//developer.mapquest.com/content/osm/mq_logo.png',
+    maxZoom: layerConfig.maxZoom,
+    opaque: true,
+    tileLoadFunction: options.tileLoadFunction,
+    url: url
+  });
 
 };
-goog.inherits(ol.source.ImageCanvas, ol.source.Image);
+goog.inherits(ol.source.MapQuest, ol.source.XYZ);
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {ol.Attribution}
  */
-ol.source.ImageCanvas.prototype.getImage =
-    function(extent, resolution, pixelRatio, projection) {
-  resolution = this.findNearestResolution(resolution);
-
-  var canvas = this.canvas_;
-  if (!goog.isNull(canvas) &&
-      this.renderedRevision_ == this.getRevision() &&
-      canvas.getResolution() == resolution &&
-      canvas.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(canvas.getExtent(), extent)) {
-    return canvas;
-  }
+ol.source.MapQuest.TILE_ATTRIBUTION = new ol.Attribution({
+  html: 'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'
+});
 
-  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 (!goog.isNull(canvasElement)) {
-    canvas = new ol.ImageCanvas(extent, resolution, pixelRatio,
-        this.getAttributions(), canvasElement);
+/**
+ * @type {Object.<string, {maxZoom: number, attributions: (Array.<ol.Attribution>)}>}
+ */
+ol.source.MapQuestConfig = {
+  'osm': {
+    maxZoom: 19,
+    attributions: [
+      ol.source.MapQuest.TILE_ATTRIBUTION,
+      ol.source.OSM.ATTRIBUTION
+    ]
+  },
+  'sat': {
+    maxZoom: 18,
+    attributions: [
+      ol.source.MapQuest.TILE_ATTRIBUTION,
+      new ol.Attribution({
+        html: 'Portions Courtesy NASA/JPL-Caltech and ' +
+            'U.S. Depart. of Agriculture, Farm Service Agency'
+      })
+    ]
+  },
+  'hyb': {
+    maxZoom: 18,
+    attributions: [
+      ol.source.MapQuest.TILE_ATTRIBUTION,
+      ol.source.OSM.ATTRIBUTION
+    ]
   }
-  this.canvas_ = canvas;
-  this.renderedRevision_ = this.getRevision();
-
-  return canvas;
 };
 
-goog.provide('ol.source.ImageMapGuide');
+goog.provide('ol.source.OSMXML');
 
-goog.require('goog.object');
-goog.require('goog.uri.utils');
-goog.require('ol.Image');
-goog.require('ol.ImageUrlFunction');
-goog.require('ol.extent');
-goog.require('ol.source.Image');
+goog.require('ol.format.OSMXML');
+goog.require('ol.source.StaticVector');
 
 
 
 /**
  * @classdesc
- * Source for images from Mapguide servers
+ * Static vector source in OSMXML format
  *
  * @constructor
- * @extends {ol.source.Image}
- * @param {olx.source.ImageMapGuideOptions} options Options.
- * @api stable
+ * @extends {ol.source.StaticVector}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.OSMXMLOptions=} opt_options Options.
+ * @api
  */
-ol.source.ImageMapGuide = function(options) {
+ol.source.OSMXML = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
   goog.base(this, {
+    attributions: options.attributions,
+    doc: options.doc,
+    format: new ol.format.OSMXML(),
+    logo: options.logo,
+    node: options.node,
     projection: options.projection,
-    resolutions: options.resolutions
+    text: options.text,
+    url: options.url,
+    urls: options.urls
   });
 
-  /**
-   * @private
-   * @type {?string}
-   */
-  this.crossOrigin_ =
-      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
+};
+goog.inherits(ol.source.OSMXML, ol.source.StaticVector);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.displayDpi_ = goog.isDef(options.displayDpi) ?
-      options.displayDpi : 96;
+// FIXME cache expiration
 
-  /**
-   * @private
-   * @type {Object}
-   */
-  this.params_ = goog.isDef(options.params) ? options.params : {};
+goog.provide('ol.source.ServerVector');
 
-  var imageUrlFunction;
-  if (goog.isDef(options.url)) {
-    imageUrlFunction = ol.ImageUrlFunction.createFromParamsFunction(
-        options.url, this.params_, goog.bind(this.getUrl, this));
-  } else {
-    imageUrlFunction = ol.ImageUrlFunction.nullImageUrlFunction;
-  }
+goog.require('goog.object');
+goog.require('ol.extent');
+goog.require('ol.loadingstrategy');
+goog.require('ol.source.FormatVector');
+goog.require('ol.structs.RBush');
 
-  /**
-   * @private
-   * @type {ol.ImageUrlFunctionType}
-   */
-  this.imageUrlFunction_ = imageUrlFunction;
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.hidpi_ = goog.isDef(options.hidpi) ? options.hidpi : true;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.metersPerUnit_ = goog.isDef(options.metersPerUnit) ?
-      options.metersPerUnit : 1;
+/**
+ * @classdesc
+ * A vector source in one of the supported formats, using a custom function to
+ * read in the data from a remote server.
+ *
+ * @constructor
+ * @extends {ol.source.FormatVector}
+ * @param {olx.source.ServerVectorOptions} options Options.
+ * @api
+ */
+ol.source.ServerVector = function(options) {
+
+  goog.base(this, {
+    attributions: options.attributions,
+    format: options.format,
+    logo: options.logo,
+    projection: options.projection
+  });
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.structs.RBush.<{extent: ol.Extent}>}
    */
-  this.ratio_ = goog.isDef(options.ratio) ? options.ratio : 1;
+  this.loadedExtents_ = new ol.structs.RBush();
 
   /**
    * @private
-   * @type {boolean}
+   * @type {function(this: ol.source.ServerVector, ol.Extent, number,
+   *                 ol.proj.Projection)}
    */
-  this.useOverlay_ = goog.isDef(options.useOverlay) ?
-      options.useOverlay : false;
+  this.loader_ = options.loader;
 
   /**
    * @private
-   * @type {ol.Image}
+   * @type {function(ol.Extent, number): Array.<ol.Extent>}
    */
-  this.image_ = null;
+  this.strategy_ = goog.isDef(options.strategy) ?
+      options.strategy : ol.loadingstrategy.bbox;
 
   /**
    * @private
-   * @type {number}
+   * @type {Object.<number|string, boolean>}
    */
-  this.renderedRevision_ = 0;
+  this.loadedFeatures_ = {};
 
 };
-goog.inherits(ol.source.ImageMapGuide, ol.source.Image);
+goog.inherits(ol.source.ServerVector, ol.source.FormatVector);
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.source.ImageMapGuide.prototype.getParams = function() {
-  return this.params_;
+ol.source.ServerVector.prototype.addFeaturesInternal = function(features) {
+  /** @type {Array.<ol.Feature>} */
+  var notLoadedFeatures = [];
+  var i, ii;
+  for (i = 0, ii = features.length; i < ii; ++i) {
+    var feature = features[i];
+    var featureId = feature.getId();
+    if (!goog.isDef(featureId)) {
+      notLoadedFeatures.push(feature);
+    } else if (!(featureId in this.loadedFeatures_)) {
+      notLoadedFeatures.push(feature);
+      this.loadedFeatures_[featureId] = true;
+    }
+  }
+  goog.base(this, 'addFeaturesInternal', notLoadedFeatures);
 };
 
 
 /**
  * @inheritDoc
  */
-ol.source.ImageMapGuide.prototype.getImage =
-    function(extent, resolution, pixelRatio, projection) {
-  resolution = this.findNearestResolution(resolution);
-  pixelRatio = this.hidpi_ ? pixelRatio : 1;
-
-  var image = this.image_;
-  if (!goog.isNull(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];
-
-  var imageUrl = this.imageUrlFunction_(extent, size, projection);
-  if (goog.isDef(imageUrl)) {
-    image = new ol.Image(extent, resolution, pixelRatio,
-        this.getAttributions(), imageUrl, this.crossOrigin_);
-  } else {
-    image = null;
-  }
-  this.image_ = image;
-  this.renderedRevision_ = this.getRevision();
-
-  return image;
+ol.source.ServerVector.prototype.clear = function() {
+  goog.object.clear(this.loadedFeatures_);
+  this.loadedExtents_.clear();
+  goog.base(this, 'clear');
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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
+ol.source.ServerVector.prototype.loadFeatures =
+    function(extent, resolution, projection) {
+  var loadedExtents = this.loadedExtents_;
+  var extentsToLoad = this.strategy_(extent, resolution);
+  var i, ii;
+  for (i = 0, ii = extentsToLoad.length; i < ii; ++i) {
+    var extentToLoad = extentsToLoad[i];
+    var alreadyLoaded = loadedExtents.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);
+      loadedExtents.insert(extentToLoad, {extent: extentToLoad.slice()});
+    }
   }
 };
 
 
 /**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
  */
-ol.source.ImageMapGuide.prototype.updateParams = function(params) {
-  goog.object.extend(this.params_, params);
-  this.dispatchChangeEvent();
-};
+ol.source.ServerVector.prototype.readFeatures;
+
+goog.provide('ol.source.Stamen');
+
+goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.Attribution');
+goog.require('ol.source.OSM');
+goog.require('ol.source.XYZ');
 
 
 /**
- * @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.
+ * @type {Object.<string, {extension: string, opaque: boolean}>}
  */
-ol.source.ImageMapGuide.prototype.getUrl =
-    function(baseUrl, params, extent, size, projection) {
-  var scale = ol.source.ImageMapGuide.getScale(extent, size,
-      this.metersPerUnit_, this.displayDpi_);
-  var center = ol.extent.getCenter(extent);
-  var baseParams = {
-    'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE',
-    'VERSION': '2.0.0',
-    'LOCALE': 'en',
-    'CLIENTAGENT': 'ol.source.ImageMapGuide source',
-    'CLIP': '1',
-    'SETDISPLAYDPI': this.displayDpi_,
-    'SETDISPLAYWIDTH': Math.round(size[0]),
-    'SETDISPLAYHEIGHT': Math.round(size[1]),
-    'SETVIEWSCALE': scale,
-    'SETVIEWCENTERX': center[0],
-    'SETVIEWCENTERY': center[1]
-  };
-  goog.object.extend(baseParams, params);
-  return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams);
+ol.source.StamenLayerConfig = {
+  'terrain': {
+    extension: 'jpg',
+    opaque: true
+  },
+  'terrain-background': {
+    extension: 'jpg',
+    opaque: true
+  },
+  'terrain-labels': {
+    extension: 'png',
+    opaque: false
+  },
+  'terrain-lines': {
+    extension: 'png',
+    opaque: false
+  },
+  'toner-background': {
+    extension: 'png',
+    opaque: true
+  },
+  'toner': {
+    extension: 'png',
+    opaque: true
+  },
+  'toner-hybrid': {
+    extension: 'png',
+    opaque: false
+  },
+  'toner-labels': {
+    extension: 'png',
+    opaque: false
+  },
+  'toner-lines': {
+    extension: 'png',
+    opaque: false
+  },
+  'toner-lite': {
+    extension: 'png',
+    opaque: true
+  },
+  'watercolor': {
+    extension: 'jpg',
+    opaque: true
+  }
 };
 
-goog.provide('ol.source.ImageStatic');
 
-goog.require('ol.Image');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.source.Image');
+/**
+ * @type {Object.<string, {minZoom: number, maxZoom: number}>}
+ */
+ol.source.StamenProviderConfig = {
+  'terrain': {
+    minZoom: 4,
+    maxZoom: 18
+  },
+  'toner': {
+    minZoom: 0,
+    maxZoom: 20
+  },
+  'watercolor': {
+    minZoom: 3,
+    maxZoom: 16
+  }
+};
 
 
 
 /**
  * @classdesc
- * An image source for 'static', that is, non-georeferenced, images.
- * See examples/static-image for example.
+ * Layer source for the Stamen tile server.
  *
  * @constructor
- * @extends {ol.source.Image}
- * @param {olx.source.ImageStaticOptions} options Options.
+ * @extends {ol.source.XYZ}
+ * @param {olx.source.StamenOptions} options Stamen options.
  * @api stable
  */
-ol.source.ImageStatic = function(options) {
+ol.source.Stamen = function(options) {
 
-  var attributions = goog.isDef(options.attributions) ?
-      options.attributions : null;
-  var crossOrigin = goog.isDef(options.crossOrigin) ?
-      options.crossOrigin : null;
-  var imageExtent = options.imageExtent;
-  var imageSize = options.imageSize;
-  var imageResolution = (imageExtent[3] - imageExtent[1]) / imageSize[1];
-  var imageUrl = options.url;
-  var projection = ol.proj.get(options.projection);
+  var i = options.layer.indexOf('-');
+  var provider = i == -1 ? options.layer : options.layer.slice(0, i);
+  goog.asserts.assert(provider in ol.source.StamenProviderConfig);
+  var providerConfig = ol.source.StamenProviderConfig[provider];
+
+  goog.asserts.assert(options.layer in ol.source.StamenLayerConfig);
+  var layerConfig = ol.source.StamenLayerConfig[options.layer];
+
+  var root = ol.IS_HTTPS ? 'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' :
+      'http://{a-d}.tile.stamen.com/';
+  var url = goog.isDef(options.url) ? options.url :
+      root + options.layer + '/{z}/{x}/{y}.' +
+      layerConfig.extension;
 
   goog.base(this, {
-    attributions: attributions,
-    logo: options.logo,
-    projection: projection,
-    resolutions: [imageResolution]
+    attributions: ol.source.Stamen.ATTRIBUTIONS,
+    crossOrigin: 'anonymous',
+    maxZoom: providerConfig.maxZoom,
+    // FIXME uncomment the following when tilegrid supports minZoom
+    //minZoom: providerConfig.minZoom,
+    opaque: layerConfig.opaque,
+    tileLoadFunction: options.tileLoadFunction,
+    url: url
   });
 
-  /**
-   * @private
-   * @type {ol.Image}
-   */
-  this.image_ = new ol.Image(imageExtent, imageResolution, 1, attributions,
-      imageUrl, crossOrigin);
-
 };
-goog.inherits(ol.source.ImageStatic, ol.source.Image);
+goog.inherits(ol.source.Stamen, ol.source.XYZ);
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Array.<ol.Attribution>}
  */
-ol.source.ImageStatic.prototype.getImage =
-    function(extent, resolution, pixelRatio, projection) {
-  if (ol.extent.intersects(extent, this.image_.getExtent())) {
-    return this.image_;
-  }
-  return null;
-};
+ol.source.Stamen.ATTRIBUTIONS = [
+  new ol.Attribution({
+    html: 'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ' +
+        'under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY' +
+        ' 3.0</a>.'
+  }),
+  ol.source.OSM.ATTRIBUTION
+];
 
-goog.provide('ol.source.ImageVector');
+goog.provide('ol.source.TileDebug');
 
-goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
-goog.require('goog.vec.Mat4');
+goog.require('ol.Tile');
+goog.require('ol.TileCache');
+goog.require('ol.TileCoord');
+goog.require('ol.TileState');
 goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.renderer.vector');
-goog.require('ol.source.ImageCanvas');
-goog.require('ol.source.Vector');
-goog.require('ol.style.Style');
-goog.require('ol.vec.Mat4');
+goog.require('ol.source.Tile');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.TileGrid');
 
 
 
 /**
- * @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
+ * @extends {ol.Tile}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
+ * @private
  */
-ol.source.ImageVector = function(options) {
-
-  /**
-   * @private
-   * @type {ol.source.Vector}
-   */
-  this.source_ = options.source;
-
-  /**
-   * @private
-   * @type {!ol.style.StyleFunction}
-   */
-  this.styleFunction_ = goog.isDefAndNotNull(options.style) ?
-      ol.style.createStyleFunction(options.style) :
-      ol.style.defaultStyleFunction;
-
-  /**
-   * @private
-   * @type {!goog.vec.Mat4.Number}
-   */
-  this.transform_ = goog.vec.Mat4.createNumber();
+ol.DebugTile_ = function(tileCoord, tileGrid) {
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.canvasContext_ = ol.dom.createCanvasContext2D();
+  goog.base(this, tileCoord, ol.TileState.LOADED);
 
   /**
    * @private
-   * @type {ol.Size}
+   * @type {number}
    */
-  this.canvasSize_ = [0, 0];
+  this.tileSize_ = tileGrid.getTileSize(tileCoord[0]);
 
   /**
    * @private
-   * @type {ol.render.canvas.ReplayGroup}
+   * @type {Object.<number, HTMLCanvasElement>}
    */
-  this.replayGroup_ = null;
-
-  goog.base(this, {
-    attributions: options.attributions,
-    canvasFunction: goog.bind(this.canvasFunctionInternal_, this),
-    logo: options.logo,
-    projection: options.projection,
-    ratio: options.ratio,
-    resolutions: options.resolutions,
-    state: this.source_.getState()
-  });
-
-  goog.events.listen(this.source_, goog.events.EventType.CHANGE,
-      this.handleSourceChange_, undefined, this);
+  this.canvasByContext_ = {};
 
 };
-goog.inherits(ol.source.ImageVector, ol.source.ImageCanvas);
+goog.inherits(ol.DebugTile_, ol.Tile);
 
 
 /**
- * @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
+ * @inheritDoc
  */
-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);
-
-  var loading = false;
-  this.source_.forEachFeatureInExtentAtResolution(extent, resolution,
-      /**
-       * @param {ol.Feature} feature Feature.
-       */
-      function(feature) {
-        loading = loading ||
-            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
-      }, this);
-  replayGroup.finish();
-
-  if (loading) {
-    return null;
-  }
-
-  if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) {
-    this.canvasContext_.canvas.width = size[0];
-    this.canvasContext_.canvas.height = size[1];
-    this.canvasSize_[0] = size[0];
-    this.canvasSize_[1] = size[1];
+ol.DebugTile_.prototype.getImage = function(opt_context) {
+  var key = goog.isDef(opt_context) ? goog.getUid(opt_context) : -1;
+  if (key in this.canvasByContext_) {
+    return this.canvasByContext_[key];
   } 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_, extent, pixelRatio, transform, 0,
-      {});
+    var tileSize = this.tileSize_;
+    var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
 
-  this.replayGroup_ = replayGroup;
+    context.strokeStyle = 'black';
+    context.strokeRect(0.5, 0.5, tileSize + 0.5, tileSize + 0.5);
 
-  return this.canvasContext_.canvas;
-};
+    context.fillStyle = 'black';
+    context.textAlign = 'center';
+    context.textBaseline = 'middle';
+    context.font = '24px sans-serif';
+    context.fillText(ol.tilecoord.toString(this.tileCoord),
+        tileSize / 2, tileSize / 2);
 
+    this.canvasByContext_[key] = context.canvas;
+    return context.canvas;
 
-/**
- * @inheritDoc
- */
-ol.source.ImageVector.prototype.forEachFeatureAtPixel = function(
-    extent, resolution, rotation, coordinate, skippedFeatureUids, callback) {
-  if (goog.isNull(this.replayGroup_)) {
-    return undefined;
-  } else {
-    /** @type {Object.<string, boolean>} */
-    var features = {};
-    return this.replayGroup_.forEachGeometryAtPixel(
-        extent, resolution, 0, coordinate, skippedFeatureUids,
-        /**
-         * @param {ol.geom.Geometry} geometry Geometry.
-         * @param {Object} data Data.
-         * @return {?} Callback result.
-         */
-        function(geometry, data) {
-          var feature = /** @type {ol.Feature} */ (data);
-          goog.asserts.assert(goog.isDef(feature));
-          var key = goog.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback(feature);
-          }
-        });
   }
 };
 
 
-/**
- * @return {ol.source.Vector} Source.
- * @api
- */
-ol.source.ImageVector.prototype.getSource = function() {
-  return this.source_;
-};
-
 
 /**
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Size} size Size.
- * @return {!goog.vec.Mat4.Number} Transform.
- * @private
+ * @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.ImageVector.prototype.getTransform_ =
-    function(center, resolution, pixelRatio, size) {
-  return ol.vec.Mat4.makeTransform2D(this.transform_,
-      size[0] / 2, size[1] / 2,
-      pixelRatio / resolution, -pixelRatio / resolution,
-      0,
-      -center[0], -center[1]);
+ol.source.TileDebug = function(options) {
+
+  goog.base(this, {
+    opaque: false,
+    projection: options.projection,
+    tileGrid: options.tileGrid
+  });
+
+  /**
+   * @private
+   * @type {ol.TileCache}
+   */
+  this.tileCache_ = new ol.TileCache();
+
 };
+goog.inherits(ol.source.TileDebug, ol.source.Tile);
 
 
 /**
- * Handle changes in image style state.
- * @param {goog.events.Event} event Image style change event.
- * @private
+ * @inheritDoc
  */
-ol.source.ImageVector.prototype.handleImageChange_ =
-    function(event) {
-  this.dispatchChangeEvent();
+ol.source.TileDebug.prototype.canExpireCache = function() {
+  return this.tileCache_.canExpireCache();
 };
 
 
 /**
- * @private
+ * @inheritDoc
  */
-ol.source.ImageVector.prototype.handleSourceChange_ = function() {
-  // setState will trigger a CHANGE event, so we always rely
-  // change events by calling setState.
-  this.setState(this.source_.getState());
+ol.source.TileDebug.prototype.expireCache = function(usedTiles) {
+  this.tileCache_.expireCache(usedTiles);
 };
 
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.source.ImageVector.prototype.renderFeature_ =
-    function(feature, resolution, pixelRatio, replayGroup) {
-  var styles = this.styleFunction_(feature, resolution);
-  if (!goog.isDefAndNotNull(styles)) {
-    return false;
-  }
-  var i, ii, loading = false;
-  for (i = 0, ii = styles.length; i < ii; ++i) {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles[i],
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        feature, this.handleImageChange_, this) || loading;
+ol.source.TileDebug.prototype.getTile = function(z, x, y) {
+  var tileCoordKey = this.getKeyZXY(z, x, y);
+  if (this.tileCache_.containsKey(tileCoordKey)) {
+    return /** @type {!ol.DebugTile_} */ (this.tileCache_.get(tileCoordKey));
+  } else {
+    var tile = new ol.DebugTile_([z, x, y], this.tileGrid);
+    this.tileCache_.set(tileCoordKey, tile);
+    return tile;
   }
-  return loading;
 };
 
-goog.provide('ol.source.wms');
-goog.provide('ol.source.wms.ServerType');
-
+// FIXME add some error checking
+// FIXME check order of async callbacks
 
 /**
- * Available server types: `'carmentaserver'`, `'geoserver'`, `'mapserver'`,
- *     `'qgis'`. These are servers that have vendor parameters beyond the WMS
- *     specification that OpenLayers can make use of.
- * @enum {string}
- * @api
+ * @see http://mapbox.com/developers/api/
  */
-ol.source.wms.ServerType = {
-  CARMENTA_SERVER: 'carmentaserver',
-  GEOSERVER: 'geoserver',
-  MAPSERVER: 'mapserver',
-  QGIS: 'qgis'
-};
-
-// FIXME cannot be shared between maps with different projections
 
-goog.provide('ol.source.ImageWMS');
+goog.provide('ol.source.TileJSON');
+goog.provide('ol.tilejson');
 
 goog.require('goog.asserts');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('goog.uri.utils');
-goog.require('ol');
-goog.require('ol.Image');
+goog.require('goog.net.Jsonp');
+goog.require('ol.Attribution');
+goog.require('ol.TileRange');
+goog.require('ol.TileUrlFunction');
 goog.require('ol.extent');
 goog.require('ol.proj');
-goog.require('ol.source.Image');
-goog.require('ol.source.wms');
-goog.require('ol.source.wms.ServerType');
+goog.require('ol.source.State');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilegrid.XYZ');
 
 
 
 /**
  * @classdesc
- * Source for WMS servers providing single, untiled images.
+ * Layer source for tile data in TileJSON format.
  *
  * @constructor
- * @extends {ol.source.Image}
- * @param {olx.source.ImageWMSOptions=} opt_options Options.
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.TileJSONOptions} options TileJSON options.
  * @api stable
  */
-ol.source.ImageWMS = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.source.TileJSON = function(options) {
 
   goog.base(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection,
-    resolutions: options.resolutions
+    crossOrigin: options.crossOrigin,
+    projection: ol.proj.get('EPSG:3857'),
+    state: ol.source.State.LOADING,
+    tileLoadFunction: options.tileLoadFunction
   });
 
   /**
+   * @type {boolean|undefined}
    * @private
-   * @type {?string}
    */
-  this.crossOrigin_ =
-      goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
+  this.wrapX_ = options.wrapX;
 
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.url_ = options.url;
+  var request = new goog.net.Jsonp(options.url);
+  request.send(undefined, goog.bind(this.handleTileJSONResponse, this));
 
-  /**
-   * @private
-   * @type {Object}
-   */
-  this.params_ = options.params;
+};
+goog.inherits(ol.source.TileJSON, ol.source.TileImage);
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.v13_ = true;
-  this.updateV13_();
 
-  /**
-   * @private
-   * @type {ol.source.wms.ServerType|undefined}
-   */
-  this.serverType_ =
-      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
+/**
+ * @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 (goog.isDef(tileJSON.bounds)) {
+    var transform = ol.proj.getTransformFromProjections(
+        epsg4326Projection, sourceProjection);
+    extent = ol.extent.applyTransform(tileJSON.bounds, transform);
+  }
+
+  if (goog.isDef(tileJSON.scheme)) {
+    goog.asserts.assert(tileJSON.scheme == 'xyz');
+  }
+  var minZoom = tileJSON.minzoom || 0;
+  var maxZoom = tileJSON.maxzoom || 22;
+  var tileGrid = new ol.tilegrid.XYZ({
+    extent: ol.tilegrid.extentFromProjection(sourceProjection),
+    maxZoom: maxZoom,
+    minZoom: minZoom
+  });
+  this.tileGrid = tileGrid;
+
+  this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
+      tileGrid.createTileCoordTransform({
+        extent: extent,
+        wrapX: this.wrapX_
+      }),
+      ol.TileUrlFunction.createFromTemplates(tileJSON.tiles));
+
+  if (goog.isDef(tileJSON.attribution)) {
+    var attributionExtent = goog.isDef(extent) ?
+        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);
+
+};
+
+goog.provide('ol.source.TileUTFGrid');
+
+goog.require('goog.asserts');
+goog.require('goog.async.nextTick');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.net.Jsonp');
+goog.require('ol.Attribution');
+goog.require('ol.Tile');
+goog.require('ol.TileCache');
+goog.require('ol.TileState');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.State');
+goog.require('ol.source.Tile');
+goog.require('ol.tilegrid.XYZ');
+
+
+
+/**
+ * @classdesc
+ * Layer source for UTFGrid interaction data loaded from TileJSON format.
+ *
+ * @constructor
+ * @extends {ol.source.Tile}
+ * @param {olx.source.TileUTFGridOptions} options Source options.
+ * @api
+ */
+ol.source.TileUTFGrid = function(options) {
+  goog.base(this, {
+    projection: ol.proj.get('EPSG:3857'),
+    state: ol.source.State.LOADING
+  });
 
   /**
    * @private
    * @type {boolean}
    */
-  this.hidpi_ = goog.isDef(options.hidpi) ? options.hidpi : true;
+  this.preemptive_ = goog.isDef(options.preemptive) ?
+      options.preemptive : true;
 
   /**
    * @private
-   * @type {ol.Image}
+   * @type {!ol.TileUrlFunctionType}
    */
-  this.image_ = null;
+  this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction;
 
   /**
    * @private
-   * @type {ol.Size}
+   * @type {!ol.TileCache}
    */
-  this.imageSize_ = [0, 0];
+  this.tileCache_ = new ol.TileCache();
 
   /**
    * @private
-   * @type {number}
+   * @type {string|undefined}
    */
-  this.renderedRevision_ = 0;
+  this.template_ = undefined;
+
+  var request = new goog.net.Jsonp(options.url);
+  request.send(undefined, goog.bind(this.handleTileJSONResponse, this));
+};
+goog.inherits(ol.source.TileUTFGrid, ol.source.Tile);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.ratio_ = goog.isDef(options.ratio) ? options.ratio : 1.5;
 
+/**
+ * @inheritDoc
+ */
+ol.source.TileUTFGrid.prototype.canExpireCache = function() {
+  return this.tileCache_.canExpireCache();
 };
-goog.inherits(ol.source.ImageWMS, ol.source.Image);
 
 
 /**
- * @const
- * @type {ol.Size}
- * @private
+ * @inheritDoc
  */
-ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101];
+ol.source.TileUTFGrid.prototype.expireCache = function(usedTiles) {
+  this.tileCache_.expireCache(usedTiles);
+};
 
 
 /**
- * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
- * projection. Return `undefined` if the GetFeatureInfo URL cannot be
- * constructed.
+ * @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 {ol.proj.ProjectionLike} projection Projection.
- * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
- *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
- *     in the `LAYERS` parameter will be used. `VERSION` should not be
- *     specified here.
- * @return {string|undefined} GetFeatureInfo URL.
- * @api stable
+ * @param {function(this: T, Object)} callback Callback.
+ * @param {T=} opt_this The object to use as `this` in the callback.
+ * @param {boolean=} opt_request If `true` the callback is always async.
+ *                               The tile data is requested if not yet loaded.
+ * @template T
+ * @api
  */
-ol.source.ImageWMS.prototype.getGetFeatureInfoUrl =
-    function(coordinate, resolution, projection, params) {
+ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function(
+    coordinate, resolution, callback, opt_this, opt_request) {
+  if (!goog.isNull(this.tileGrid)) {
+    var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution(
+        coordinate, resolution);
+    var tile = /** @type {!ol.source.TileUTFGridTile_} */(this.getTile(
+        tileCoord[0], tileCoord[1], tileCoord[2], 1, this.getProjection()));
+    tile.forDataAtCoordinate(coordinate, callback, opt_this, opt_request);
+  } else {
+    if (opt_request === true) {
+      goog.async.nextTick(function() {
+        callback.call(opt_this, null);
+      });
+    } else {
+      callback.call(opt_this, null);
+    }
+  }
+};
 
-  goog.asserts.assert(!('VERSION' in params));
 
-  if (!goog.isDef(this.url_)) {
-    return undefined;
+/**
+ * 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 (goog.isDef(tileJSON.bounds)) {
+    var transform = ol.proj.getTransformFromProjections(
+        epsg4326Projection, sourceProjection);
+    extent = ol.extent.applyTransform(tileJSON.bounds, transform);
   }
 
-  var extent = ol.extent.getForViewAndSize(
-      coordinate, resolution, 0,
-      ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_);
+  if (goog.isDef(tileJSON.scheme)) {
+    goog.asserts.assert(tileJSON.scheme == 'xyz');
+  }
+  var minZoom = tileJSON.minzoom || 0;
+  var maxZoom = tileJSON.maxzoom || 22;
+  var tileGrid = new ol.tilegrid.XYZ({
+    extent: ol.tilegrid.extentFromProjection(sourceProjection),
+    maxZoom: maxZoom,
+    minZoom: minZoom
+  });
+  this.tileGrid = tileGrid;
 
-  var baseParams = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetFeatureInfo',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true,
-    'QUERY_LAYERS': goog.object.get(this.params_, 'LAYERS')
-  };
-  goog.object.extend(baseParams, this.params_, params);
+  this.template_ = tileJSON.template;
 
-  var x = Math.floor((coordinate[0] - extent[0]) / resolution);
-  var y = Math.floor((extent[3] - coordinate[1]) / resolution);
-  goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x);
-  goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y);
+  var grids = tileJSON.grids;
+  if (!goog.isDefAndNotNull(grids)) {
+    this.setState(ol.source.State.ERROR);
+    return;
+  }
 
-  return this.getRequestUrl_(
-      extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_,
-      1, ol.proj.get(projection), baseParams);
-};
+  this.tileUrlFunction_ = ol.TileUrlFunction.withTileCoordTransform(
+      tileGrid.createTileCoordTransform({
+        extent: extent
+      }),
+      ol.TileUrlFunction.createFromTemplates(grids));
+
+  if (goog.isDef(tileJSON.attribution)) {
+    var attributionExtent = goog.isDef(extent) ?
+        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);
 
-/**
- * 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.getImage =
-    function(extent, resolution, pixelRatio, projection) {
-
-  if (!goog.isDef(this.url_)) {
-    return null;
+ol.source.TileUTFGrid.prototype.getTile =
+    function(z, x, y, pixelRatio, projection) {
+  var tileCoordKey = this.getKeyZXY(z, x, y);
+  if (this.tileCache_.containsKey(tileCoordKey)) {
+    return /** @type {!ol.Tile} */ (this.tileCache_.get(tileCoordKey));
+  } else {
+    goog.asserts.assert(projection);
+    var tileCoord = [z, x, y];
+    var tileUrl = this.tileUrlFunction_(tileCoord, pixelRatio, projection);
+    var tile = new ol.source.TileUTFGridTile_(
+        tileCoord,
+        goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY,
+        goog.isDef(tileUrl) ? tileUrl : '',
+        this.tileGrid.getTileCoordExtent(tileCoord),
+        this.preemptive_);
+    this.tileCache_.set(tileCoordKey, tile);
+    return tile;
   }
+};
 
-  resolution = this.findNearestResolution(resolution);
-
-  if (pixelRatio != 1 && (!this.hidpi_ || !goog.isDef(this.serverType_))) {
-    pixelRatio = 1;
-  }
 
-  var image = this.image_;
-  if (!goog.isNull(image) &&
-      this.renderedRevision_ == this.getRevision() &&
-      image.getResolution() == resolution &&
-      image.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(image.getExtent(), extent)) {
-    return image;
+/**
+ * @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);
   }
+};
 
-  var params = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetMap',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true
-  };
-  goog.object.extend(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);
+/**
+ * @constructor
+ * @extends {ol.Tile}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
+ * @param {string} src Image source URI.
+ * @param {ol.Extent} extent Extent of the tile.
+ * @param {boolean} preemptive Load the tile when visible (before it's needed).
+ * @private
+ */
+ol.source.TileUTFGridTile_ =
+    function(tileCoord, state, src, extent, preemptive) {
 
-  // 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;
+  goog.base(this, tileCoord, state);
 
-  this.imageSize_[0] = width;
-  this.imageSize_[1] = height;
+  /**
+   * @private
+   * @type {string}
+   */
+  this.src_ = src;
 
-  var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
-      projection, params);
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.extent_ = extent;
 
-  this.image_ = new ol.Image(extent, resolution, pixelRatio,
-      this.getAttributions(), url, this.crossOrigin_);
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.preemptive_ = preemptive;
 
-  this.renderedRevision_ = this.getRevision();
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.grid_ = null;
 
-  return this.image_;
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.keys_ = null;
 
+  /**
+   * @private
+   * @type {Object.<string, Object>|undefined}
+   */
+  this.data_ = null;
 };
+goog.inherits(ol.source.TileUTFGridTile_, ol.Tile);
 
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.source.ImageWMS.prototype.getRequestUrl_ =
-    function(extent, size, pixelRatio, projection, params) {
-
-  goog.asserts.assert(goog.isDef(this.url_));
-
-  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
+ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) {
+  return null;
+};
 
-  if (!('STYLES' in this.params_)) {
-    /* jshint -W053 */
-    goog.object.set(params, 'STYLES', new String(''));
-    /* jshint +W053 */
-  }
 
-  if (pixelRatio != 1) {
-    switch (this.serverType_) {
-      case ol.source.wms.ServerType.GEOSERVER:
-        var dpi = (90 * pixelRatio + 0.5) | 0;
-        goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi);
-        break;
-      case ol.source.wms.ServerType.MAPSERVER:
-        goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio);
-        break;
-      case ol.source.wms.ServerType.CARMENTA_SERVER:
-      case ol.source.wms.ServerType.QGIS:
-        goog.object.set(params, 'DPI', 90 * pixelRatio);
-        break;
-      default:
-        goog.asserts.fail();
-        break;
-    }
+/**
+ * Synchronously returns data at given coordinate (if available).
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {Object}
+ */
+ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
+  if (goog.isNull(this.grid_) || goog.isNull(this.keys_) ||
+      goog.isNull(this.data_)) {
+    return null;
   }
+  var xRelative = (coordinate[0] - this.extent_[0]) /
+      (this.extent_[2] - this.extent_[0]);
+  var yRelative = (coordinate[1] - this.extent_[1]) /
+      (this.extent_[3] - this.extent_[1]);
 
-  goog.object.set(params, 'WIDTH', size[0]);
-  goog.object.set(params, 'HEIGHT', size[1]);
+  var row = this.grid_[Math.floor((1 - yRelative) * this.grid_.length)];
 
-  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;
+  if (!goog.isString(row)) {
+    return null;
   }
-  goog.object.set(params, 'BBOX', bbox.join(','));
 
-  return goog.uri.utils.appendParamsFromMap(this.url_, params);
-};
+  var code = row.charCodeAt(Math.floor(xRelative * row.length));
+  if (code >= 93) {
+    code--;
+  }
+  if (code >= 35) {
+    code--;
+  }
+  code -= 32;
 
+  var key = this.keys_[code];
 
-/**
- * Return the URL used for this WMS source.
- * @return {string|undefined} URL.
- * @api stable
- */
-ol.source.ImageWMS.prototype.getUrl = function() {
-  return this.url_;
+  return goog.isDefAndNotNull(key) ? this.data_[key] : null;
 };
 
 
 /**
- * @param {string|undefined} url URL.
- * @api stable
+ * Calls the callback (synchronously by default) with the available data
+ * for given coordinate (or `null` if not yet loaded).
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {function(this: T, Object)} callback Callback.
+ * @param {T=} opt_this The object to use as `this` in the callback.
+ * @param {boolean=} opt_request If `true` the callback is always async.
+ *                               The tile data is requested if not yet loaded.
+ * @template T
  */
-ol.source.ImageWMS.prototype.setUrl = function(url) {
-  if (url != this.url_) {
-    this.url_ = url;
-    this.image_ = null;
-    this.dispatchChangeEvent();
+ol.source.TileUTFGridTile_.prototype.forDataAtCoordinate =
+    function(coordinate, callback, opt_this, opt_request) {
+  if (this.state == ol.TileState.IDLE && opt_request === true) {
+    goog.events.listenOnce(this, goog.events.EventType.CHANGE, function(e) {
+      callback.call(opt_this, this.getData(coordinate));
+    }, false, this);
+    this.loadInternal_();
+  } else {
+    if (opt_request === true) {
+      goog.async.nextTick(function() {
+        callback.call(opt_this, this.getData(coordinate));
+      }, this);
+    } else {
+      callback.call(opt_this, this.getData(coordinate));
+    }
   }
 };
 
 
 /**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
+ * @inheritDoc
  */
-ol.source.ImageWMS.prototype.updateParams = function(params) {
-  goog.object.extend(this.params_, params);
-  this.updateV13_();
-  this.image_ = null;
-  this.dispatchChangeEvent();
+ol.source.TileUTFGridTile_.prototype.getKey = function() {
+  return this.src_;
 };
 
 
 /**
  * @private
  */
-ol.source.ImageWMS.prototype.updateV13_ = function() {
-  var version =
-      goog.object.get(this.params_, 'VERSION', ol.DEFAULT_WMS_VERSION);
-  this.v13_ = goog.string.compareVersions(version, '1.3') >= 0;
+ol.source.TileUTFGridTile_.prototype.handleError_ = function() {
+  this.state = ol.TileState.ERROR;
+  this.changed();
 };
 
-goog.provide('ol.source.KML');
 
-goog.require('ol.format.KML');
-goog.require('ol.source.StaticVector');
+/**
+ * @param {!UTFGridJSON} json
+ * @private
+ */
+ol.source.TileUTFGridTile_.prototype.handleLoad_ = function(json) {
+  this.grid_ = json.grid;
+  this.keys_ = json.keys;
+  this.data_ = json.data;
 
+  this.state = ol.TileState.EMPTY;
+  this.changed();
+};
 
 
 /**
- * @classdesc
- * Static vector source in KML format
- *
- * @constructor
- * @extends {ol.source.StaticVector}
- * @fires ol.source.VectorEvent
- * @param {olx.source.KMLOptions=} opt_options Options.
- * @api
+ * @private
  */
-ol.source.KML = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.source.TileUTFGridTile_.prototype.loadInternal_ = function() {
+  if (this.state == ol.TileState.IDLE) {
+    this.state = ol.TileState.LOADING;
+    var request = new goog.net.Jsonp(this.src_);
+    request.send(undefined, goog.bind(this.handleLoad_, this),
+                 goog.bind(this.handleError_, this));
+  }
+};
 
-  goog.base(this, {
-    attributions: options.attributions,
-    doc: options.doc,
-    format: new ol.format.KML({
-      extractStyles: options.extractStyles,
-      defaultStyle: options.defaultStyle
-    }),
-    logo: options.logo,
-    node: options.node,
-    projection: options.projection,
-    text: options.text,
-    url: options.url,
-    urls: options.urls
-  });
 
+/**
+ * Load not yet loaded URI.
+ */
+ol.source.TileUTFGridTile_.prototype.load = function() {
+  if (this.preemptive_) {
+    this.loadInternal_();
+  }
 };
-goog.inherits(ol.source.KML, ol.source.StaticVector);
 
-goog.provide('ol.source.XYZ');
+goog.provide('ol.source.TileVector');
 
-goog.require('ol.Attribution');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('ol.TileCoord');
 goog.require('ol.TileUrlFunction');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilegrid.XYZ');
+goog.require('ol.source.FormatVector');
+goog.require('ol.source.State');
+goog.require('ol.tilegrid.TileGrid');
 
 
 
 /**
  * @classdesc
- * Layer source for tile data with URLs in a set XYZ format.
+ * A vector source in one of the supported formats, where the data is divided
+ * into tiles in a fixed grid pattern.
  *
  * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.XYZOptions} options XYZ options.
- * @api stable
+ * @extends {ol.source.FormatVector}
+ * @param {olx.source.TileVectorOptions} options Options.
+ * @api
  */
-ol.source.XYZ = function(options) {
-  var projection = goog.isDef(options.projection) ?
-      options.projection : 'EPSG:3857';
-
-  var tileGrid = new ol.tilegrid.XYZ({
-    extent: ol.tilegrid.extentFromProjection(projection),
-    maxZoom: options.maxZoom,
-    tileSize: options.tileSize
-  });
+ol.source.TileVector = function(options) {
 
   goog.base(this, {
     attributions: options.attributions,
-    crossOrigin: options.crossOrigin,
+    format: options.format,
     logo: options.logo,
-    projection: projection,
-    tileGrid: tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    tilePixelRatio: options.tilePixelRatio,
-    tileUrlFunction: ol.TileUrlFunction.nullTileUrlFunction
+    projection: options.projection
   });
 
+  /**
+   * @private
+   * @type {ol.tilegrid.TileGrid}
+   */
+  this.tileGrid_ = options.tileGrid;
+
+  /**
+   * @private
+   * @type {ol.TileUrlFunctionType}
+   */
+  this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction;
+
   /**
    * @private
    * @type {ol.TileCoordTransformType}
    */
-  this.tileCoordTransform_ = tileGrid.createTileCoordTransform({
-    wrapX: options.wrapX
-  });
+  this.tileCoordTransform_ = this.tileGrid_.createTileCoordTransform();
+
+  /**
+   * @private
+   * @type {Object.<string, Array.<ol.Feature>>}
+   */
+  this.tiles_ = {};
 
   if (goog.isDef(options.tileUrlFunction)) {
     this.setTileUrlFunction(options.tileUrlFunction);
@@ -97206,2154 +110170,2397 @@ ol.source.XYZ = function(options) {
   }
 
 };
-goog.inherits(ol.source.XYZ, ol.source.TileImage);
+goog.inherits(ol.source.TileVector, ol.source.FormatVector);
 
 
 /**
  * @inheritDoc
- * @api
  */
-ol.source.XYZ.prototype.setTileUrlFunction = function(tileUrlFunction) {
-  goog.base(this, 'setTileUrlFunction',
-      ol.TileUrlFunction.withTileCoordTransform(
-          this.tileCoordTransform_, tileUrlFunction));
-};
+ol.source.TileVector.prototype.addFeature = goog.abstractMethod;
 
 
 /**
- * @param {string} url URL.
- * @api stable
+ * @inheritDoc
  */
-ol.source.XYZ.prototype.setUrl = function(url) {
-  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(
-      ol.TileUrlFunction.expandUrl(url)));
-};
+ol.source.TileVector.prototype.addFeatures = goog.abstractMethod;
 
 
 /**
- * @param {Array.<string>} urls URLs.
+ * @inheritDoc
  */
-ol.source.XYZ.prototype.setUrls = function(urls) {
-  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(urls));
+ol.source.TileVector.prototype.clear = function() {
+  goog.object.clear(this.tiles_);
 };
 
-goog.provide('ol.source.OSM');
-
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.source.XYZ');
 
+/**
+ * @inheritDoc
+ */
+ol.source.TileVector.prototype.forEachFeature = goog.abstractMethod;
 
 
 /**
- * @classdesc
- * Layer source for the OpenStreetMap tile server.
+ * Iterate through all features whose geometries contain the provided
+ * coordinate at the provided resolution, calling the callback with each
+ * feature. If the callback returns a "truthy" value, iteration will stop and
+ * the function will return the same value.
  *
- * @constructor
- * @extends {ol.source.XYZ}
- * @param {olx.source.OSMOptions=} opt_options Open Street Map options.
- * @api stable
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {function(this: T, ol.Feature): S} callback Called with each feature
+ *     whose goemetry contains the provided coordinate.
+ * @param {T=} opt_this The object to use as `this` in the callback.
+ * @return {S|undefined} The return value from the last call to the callback.
+ * @template T,S
  */
-ol.source.OSM = function(opt_options) {
+ol.source.TileVector.prototype.forEachFeatureAtCoordinateAndResolution =
+    function(coordinate, resolution, callback, opt_this) {
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
+  var tileGrid = this.tileGrid_;
+  var tiles = this.tiles_;
+  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(coordinate,
+      resolution);
 
-  var attributions;
-  if (goog.isDef(options.attributions)) {
-    attributions = options.attributions;
-  } else {
-    attributions = ol.source.OSM.ATTRIBUTIONS;
+  var tileKey = this.getTileKeyZXY_(tileCoord[0], tileCoord[1], tileCoord[2]);
+  var features = tiles[tileKey];
+  if (goog.isDef(features)) {
+    var i, ii;
+    for (i = 0, ii = features.length; i < ii; ++i) {
+      var feature = features[i];
+      var geometry = feature.getGeometry();
+      goog.asserts.assert(goog.isDefAndNotNull(geometry));
+      if (geometry.containsCoordinate(coordinate)) {
+        var result = callback.call(opt_this, feature);
+        if (result) {
+          return result;
+        }
+      }
+    }
   }
+  return undefined;
+};
 
-  var crossOrigin = goog.isDef(options.crossOrigin) ?
-      options.crossOrigin : 'anonymous';
 
-  var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
-  var url = goog.isDef(options.url) ?
-      options.url : protocol + '//{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';
+/**
+ * @inheritDoc
+ */
+ol.source.TileVector.prototype.forEachFeatureInExtent = goog.abstractMethod;
 
-  goog.base(this, {
-    attributions: attributions,
-    crossOrigin: crossOrigin,
-    opaque: true,
-    maxZoom: goog.isDef(options.maxZoom) ? options.maxZoom : 19,
-    tileLoadFunction: options.tileLoadFunction,
-    url: url
-  });
 
+/**
+ * @inheritDoc
+ */
+ol.source.TileVector.prototype.forEachFeatureInExtentAtResolution =
+    function(extent, resolution, f, opt_this) {
+  var tileGrid = this.tileGrid_;
+  var tiles = this.tiles_;
+  var z = tileGrid.getZForResolution(resolution);
+  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
+  var x, y;
+  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
+    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+      var tileKey = this.getTileKeyZXY_(z, x, y);
+      var features = tiles[tileKey];
+      if (goog.isDef(features)) {
+        var i, ii;
+        for (i = 0, ii = features.length; i < ii; ++i) {
+          var result = f.call(opt_this, features[i]);
+          if (result) {
+            return result;
+          }
+        }
+      }
+    }
+  }
+  return undefined;
 };
-goog.inherits(ol.source.OSM, ol.source.XYZ);
 
 
 /**
- * @const
- * @type {ol.Attribution}
- * @api
+ * @inheritDoc
  */
-ol.source.OSM.DATA_ATTRIBUTION = new ol.Attribution({
-  html: 'Data &copy; ' +
-      '<a href="http://www.openstreetmap.org/">OpenStreetMap</a> ' +
-      'contributors, ' +
-      '<a href="http://www.openstreetmap.org/copyright">ODbL</a>'
-});
+ol.source.TileVector.prototype.getClosestFeatureToCoordinate =
+    goog.abstractMethod;
 
 
 /**
- * @const
- * @type {ol.Attribution}
- * @api
+ * @inheritDoc
  */
-ol.source.OSM.TILE_ATTRIBUTION = new ol.Attribution({
-  html: 'Tiles &copy; ' +
-      '<a href="http://www.openstreetmap.org/">OpenStreetMap</a> ' +
-      'contributors, ' +
-      '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA</a>'
-});
+ol.source.TileVector.prototype.getExtent = goog.abstractMethod;
 
 
 /**
- * @const
- * @type {Array.<ol.Attribution>}
+ * @inheritDoc
+ * @api
  */
-ol.source.OSM.ATTRIBUTIONS = [
-  ol.source.OSM.TILE_ATTRIBUTION,
-  ol.source.OSM.DATA_ATTRIBUTION
-];
-
-goog.provide('ol.source.MapQuest');
+ol.source.TileVector.prototype.getFeatures = function() {
+  var tiles = this.tiles_;
+  var features = [];
+  var tileKey;
+  for (tileKey in tiles) {
+    goog.array.extend(features, tiles[tileKey]);
+  }
+  return features;
+};
 
-goog.require('goog.asserts');
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.source.OSM');
-goog.require('ol.source.XYZ');
 
+/**
+ * Get all features whose geometry intersects the provided coordinate for the
+ * provided resolution.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
+ */
+ol.source.TileVector.prototype.getFeaturesAtCoordinateAndResolution =
+    function(coordinate, resolution) {
+  var features = [];
+  this.forEachFeatureAtCoordinateAndResolution(coordinate, resolution,
+      /**
+       * @param {ol.Feature} feature Feature.
+       */
+      function(feature) {
+        features.push(feature);
+      });
+  return features;
+};
 
 
 /**
- * @classdesc
- * Layer source for the MapQuest tile server.
- *
- * @constructor
- * @extends {ol.source.XYZ}
- * @param {olx.source.MapQuestOptions=} opt_options MapQuest options.
- * @api stable
+ * @inheritDoc
  */
-ol.source.MapQuest = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-  goog.asserts.assert(options.layer in ol.source.MapQuestConfig);
+ol.source.TileVector.prototype.getFeaturesInExtent = goog.abstractMethod;
 
-  var layerConfig = ol.source.MapQuestConfig[options.layer];
 
-  var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
-  var url = protocol + '//otile{1-4}-s.mqcdn.com/tiles/1.0.0/' +
-      options.layer + '/{z}/{x}/{y}.jpg';
+/**
+ * @param {number} z Z.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @private
+ * @return {string} Tile key.
+ */
+ol.source.TileVector.prototype.getTileKeyZXY_ = function(z, x, y) {
+  return z + '/' + x + '/' + y;
+};
 
-  goog.base(this, {
-    attributions: layerConfig.attributions,
-    crossOrigin: 'anonymous',
-    logo: '//developer.mapquest.com/content/osm/mq_logo.png',
-    maxZoom: layerConfig.maxZoom,
-    opaque: true,
-    tileLoadFunction: options.tileLoadFunction,
-    url: url
-  });
 
+/**
+ * @inheritDoc
+ */
+ol.source.TileVector.prototype.loadFeatures =
+    function(extent, resolution, projection) {
+  var tileCoordTransform = this.tileCoordTransform_;
+  var tileGrid = this.tileGrid_;
+  var tileUrlFunction = this.tileUrlFunction_;
+  var tiles = this.tiles_;
+  var z = tileGrid.getZForResolution(resolution);
+  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
+  var tileCoord = [z, 0, 0];
+  var x, y;
+  /**
+   * @param {string} tileKey Tile key.
+   * @param {Array.<ol.Feature>} features Features.
+   * @this {ol.source.TileVector}
+   */
+  function success(tileKey, features) {
+    tiles[tileKey] = features;
+    this.setState(ol.source.State.READY);
+  }
+  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
+    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+      var tileKey = this.getTileKeyZXY_(z, x, y);
+      if (!(tileKey in tiles)) {
+        tileCoord[0] = z;
+        tileCoord[1] = x;
+        tileCoord[2] = y;
+        tileCoordTransform(tileCoord, projection, tileCoord);
+        var url = tileUrlFunction(tileCoord, 1, projection);
+        if (goog.isDef(url)) {
+          tiles[tileKey] = [];
+          this.loadFeaturesFromURL(url, goog.partial(success, tileKey),
+              goog.nullFunction, this);
+        }
+      }
+    }
+  }
 };
-goog.inherits(ol.source.MapQuest, ol.source.XYZ);
 
 
 /**
- * @const
- * @type {ol.Attribution}
+ * @inheritDoc
  */
-ol.source.MapQuest.TILE_ATTRIBUTION = new ol.Attribution({
-  html: 'Tiles Courtesy of ' +
-      '<a href="http://www.mapquest.com/" target="_blank">MapQuest</a>'
-});
+ol.source.TileVector.prototype.removeFeature = goog.abstractMethod;
 
 
 /**
- * @type {Object.<string, {maxZoom: number, attributions: (Array.<ol.Attribution>)}>}
+ * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
  */
-ol.source.MapQuestConfig = {
-  'osm': {
-    maxZoom: 28,
-    attributions: [
-      ol.source.MapQuest.TILE_ATTRIBUTION,
-      ol.source.OSM.DATA_ATTRIBUTION
-    ]
-  },
-  'sat': {
-    maxZoom: 18,
-    attributions: [
-      ol.source.MapQuest.TILE_ATTRIBUTION,
-      new ol.Attribution({
-        html: 'Portions Courtesy NASA/JPL-Caltech and ' +
-            'U.S. Depart. of Agriculture, Farm Service Agency'
-      })
-    ]
-  },
-  'hyb': {
-    maxZoom: 18,
-    attributions: [
-      ol.source.MapQuest.TILE_ATTRIBUTION,
-      ol.source.OSM.DATA_ATTRIBUTION
-    ]
-  }
+ol.source.TileVector.prototype.setTileUrlFunction = function(tileUrlFunction) {
+  this.tileUrlFunction_ = tileUrlFunction;
+  this.changed();
 };
 
-goog.provide('ol.source.OSMXML');
-
-goog.require('ol.format.OSMXML');
-goog.require('ol.source.StaticVector');
-
-
 
 /**
- * @classdesc
- * Static vector source in OSMXML format
- *
- * @constructor
- * @extends {ol.source.StaticVector}
- * @fires ol.source.VectorEvent
- * @param {olx.source.OSMXMLOptions=} opt_options Options.
- * @api
+ * @param {string} url URL.
  */
-ol.source.OSMXML = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+ol.source.TileVector.prototype.setUrl = function(url) {
+  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(
+      ol.TileUrlFunction.expandUrl(url)));
+};
 
-  goog.base(this, {
-    attributions: options.attributions,
-    doc: options.doc,
-    format: new ol.format.OSMXML(),
-    logo: options.logo,
-    node: options.node,
-    projection: options.projection,
-    reprojectTo: options.reprojectTo,
-    text: options.text,
-    url: options.url,
-    urls: options.urls
-  });
 
+/**
+ * @param {Array.<string>} urls URLs.
+ */
+ol.source.TileVector.prototype.setUrls = function(urls) {
+  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(urls));
 };
-goog.inherits(ol.source.OSMXML, ol.source.StaticVector);
 
-// FIXME cache expiration
+// 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.ServerVector');
+goog.provide('ol.source.TileWMS');
 
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.math');
 goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.uri.utils');
+goog.require('ol');
+goog.require('ol.TileCoord');
+goog.require('ol.TileUrlFunction');
 goog.require('ol.extent');
-goog.require('ol.loadingstrategy');
-goog.require('ol.source.FormatVector');
-goog.require('ol.structs.RBush');
+goog.require('ol.proj');
+goog.require('ol.source.TileImage');
+goog.require('ol.source.wms');
+goog.require('ol.source.wms.ServerType');
+goog.require('ol.tilecoord');
 
 
 
 /**
  * @classdesc
- * A vector source in one of the supported formats, using a custom function to
- * read in the data from a remote server.
+ * Layer source for tile data from WMS servers.
  *
  * @constructor
- * @extends {ol.source.FormatVector}
- * @param {olx.source.ServerVectorOptions} options Options.
- * @api
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options.
+ * @api stable
  */
-ol.source.ServerVector = function(options) {
+ol.source.TileWMS = function(opt_options) {
+
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  var params = goog.isDef(options.params) ? options.params : {};
+
+  var transparent = goog.object.get(params, 'TRANSPARENT', true);
 
   goog.base(this, {
     attributions: options.attributions,
-    format: options.format,
+    crossOrigin: options.crossOrigin,
     logo: options.logo,
-    projection: options.projection
+    opaque: !transparent,
+    projection: options.projection,
+    tileGrid: options.tileGrid,
+    tileLoadFunction: options.tileLoadFunction,
+    tileUrlFunction: goog.bind(this.tileUrlFunction_, this)
   });
 
+  var urls = options.urls;
+  if (!goog.isDef(urls) && goog.isDef(options.url)) {
+    urls = ol.TileUrlFunction.expandUrl(options.url);
+  }
+
+  /**
+   * @private
+   * @type {!Array.<string>}
+   */
+  this.urls_ = goog.isDefAndNotNull(urls) ? urls : [];
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.gutter_ = goog.isDef(options.gutter) ? options.gutter : 0;
+
+  /**
+   * @private
+   * @type {Object}
+   */
+  this.params_ = params;
+
   /**
    * @private
-   * @type {ol.structs.RBush.<{extent: ol.Extent}>}
+   * @type {boolean}
    */
-  this.loadedExtents_ = new ol.structs.RBush();
+  this.v13_ = true;
 
   /**
    * @private
-   * @type {function(this: ol.source.ServerVector, ol.Extent, number,
-   *                 ol.proj.Projection): string}
+   * @type {ol.source.wms.ServerType|undefined}
    */
-  this.loader_ = options.loader;
+  this.serverType_ =
+      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
 
   /**
    * @private
-   * @type {function(ol.Extent, number): Array.<ol.Extent>}
+   * @type {boolean}
    */
-  this.strategy_ = goog.isDef(options.strategy) ?
-      options.strategy : ol.loadingstrategy.bbox;
+  this.hidpi_ = goog.isDef(options.hidpi) ? options.hidpi : true;
 
   /**
    * @private
-   * @type {Object.<number|string, boolean>}
+   * @type {string}
    */
-  this.loadedFeatures_ = {};
+  this.coordKeyPrefix_ = '';
+  this.resetCoordKeyPrefix_();
+
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.tmpExtent_ = ol.extent.createEmpty();
+
+  this.updateV13_();
 
 };
-goog.inherits(ol.source.ServerVector, ol.source.FormatVector);
+goog.inherits(ol.source.TileWMS, ol.source.TileImage);
 
 
 /**
- * @inheritDoc
+ * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
+ * projection. Return `undefined` if the GetFeatureInfo URL cannot be
+ * constructed.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.ProjectionLike} projection Projection.
+ * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
+ *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
+ *     in the `LAYERS` parameter will be used. `VERSION` should not be
+ *     specified here.
+ * @return {string|undefined} GetFeatureInfo URL.
+ * @api stable
  */
-ol.source.ServerVector.prototype.addFeaturesInternal = function(features) {
-  /** @type {Array.<ol.Feature>} */
-  var notLoadedFeatures = [];
-  var i, ii;
-  for (i = 0, ii = features.length; i < ii; ++i) {
-    var feature = features[i];
-    var featureId = feature.getId();
-    if (!goog.isDef(featureId)) {
-      notLoadedFeatures.push(feature);
-    } else if (!(featureId in this.loadedFeatures_)) {
-      notLoadedFeatures.push(feature);
-      this.loadedFeatures_[featureId] = true;
-    }
+ol.source.TileWMS.prototype.getGetFeatureInfoUrl =
+    function(coordinate, resolution, projection, params) {
+
+  goog.asserts.assert(!('VERSION' in params));
+
+  var projectionObj = ol.proj.get(projection);
+
+  var tileGrid = this.getTileGrid();
+  if (goog.isNull(tileGrid)) {
+    tileGrid = this.getTileGridForProjection(projectionObj);
   }
-  goog.base(this, 'addFeaturesInternal', notLoadedFeatures);
+
+  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 = tileGrid.getTileSize(tileCoord[0]);
+
+  var gutter = this.gutter_;
+  if (gutter !== 0) {
+    tileSize += 2 * gutter;
+    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': goog.object.get(this.params_, 'LAYERS')
+  };
+  goog.object.extend(baseParams, this.params_, params);
+
+  var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution);
+  var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution);
+
+  goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x);
+  goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y);
+
+  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
+      1, projectionObj, baseParams);
 };
 
 
 /**
  * @inheritDoc
  */
-ol.source.ServerVector.prototype.clear = function() {
-  goog.object.clear(this.loadedFeatures_);
-  this.loadedExtents_.clear();
-  goog.base(this, 'clear');
+ol.source.TileWMS.prototype.getGutter = function() {
+  return this.gutter_;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.source.ServerVector.prototype.loadFeatures =
-    function(extent, resolution, projection) {
-  var loadedExtents = this.loadedExtents_;
-  var extentsToLoad = this.strategy_(extent, resolution);
-  var i, ii;
-  for (i = 0, ii = extentsToLoad.length; i < ii; ++i) {
-    var extentToLoad = extentsToLoad[i];
-    var alreadyLoaded = loadedExtents.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);
-      loadedExtents.insert(extentToLoad, {extent: extentToLoad.slice()});
-    }
-  }
+ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
+  return this.coordKeyPrefix_ + goog.base(this, 'getKeyZXY', z, x, y);
 };
 
 
 /**
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {Array.<ol.Feature>} Features.
- * @api
+ * 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.ServerVector.prototype.readFeatures;
-
-goog.provide('ol.source.Stamen');
-
-goog.require('goog.asserts');
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.source.OSM');
-goog.require('ol.source.XYZ');
+ol.source.TileWMS.prototype.getParams = function() {
+  return this.params_;
+};
 
 
 /**
- * @type {Object.<string, {extension: string, opaque: boolean}>}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {number} 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.StamenLayerConfig = {
-  'terrain': {
-    extension: 'jpg',
-    opaque: true
-  },
-  'terrain-background': {
-    extension: 'jpg',
-    opaque: true
-  },
-  'terrain-labels': {
-    extension: 'png',
-    opaque: false
-  },
-  'terrain-lines': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-background': {
-    extension: 'png',
-    opaque: true
-  },
-  'toner': {
-    extension: 'png',
-    opaque: true
-  },
-  'toner-hybrid': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-labels': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-lines': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-lite': {
-    extension: 'png',
-    opaque: true
-  },
-  'watercolor': {
-    extension: 'jpg',
-    opaque: true
+ol.source.TileWMS.prototype.getRequestUrl_ =
+    function(tileCoord, tileSize, tileExtent,
+        pixelRatio, projection, params) {
+
+  var urls = this.urls_;
+  if (goog.array.isEmpty(urls)) {
+    return undefined;
+  }
+
+  goog.object.set(params, 'WIDTH', tileSize);
+  goog.object.set(params, 'HEIGHT', tileSize);
+
+  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
+
+  if (!('STYLES' in this.params_)) {
+    /* jshint -W053 */
+    goog.object.set(params, 'STYLES', new String(''));
+    /* jshint +W053 */
+  }
+
+  if (pixelRatio != 1) {
+    switch (this.serverType_) {
+      case ol.source.wms.ServerType.GEOSERVER:
+        var dpi = (90 * pixelRatio + 0.5) | 0;
+        goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi);
+        break;
+      case ol.source.wms.ServerType.MAPSERVER:
+        goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio);
+        break;
+      case ol.source.wms.ServerType.CARMENTA_SERVER:
+      case ol.source.wms.ServerType.QGIS:
+        goog.object.set(params, 'DPI', 90 * pixelRatio);
+        break;
+      default:
+        goog.asserts.fail();
+        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;
+  }
+  goog.object.set(params, 'BBOX', bbox.join(','));
+
+  var url;
+  if (urls.length == 1) {
+    url = urls[0];
+  } else {
+    var index = goog.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
+    url = urls[index];
   }
+  return goog.uri.utils.appendParamsFromMap(url, params);
 };
 
 
 /**
- * @type {Object.<string, {minZoom: number, maxZoom: number}>}
+ * @param {number} z Z.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {number} Size.
  */
-ol.source.StamenProviderConfig = {
-  'terrain': {
-    minZoom: 4,
-    maxZoom: 18
-  },
-  'toner': {
-    minZoom: 0,
-    maxZoom: 20
-  },
-  'watercolor': {
-    minZoom: 3,
-    maxZoom: 16
+ol.source.TileWMS.prototype.getTilePixelSize =
+    function(z, pixelRatio, projection) {
+  var tileSize = goog.base(this, 'getTilePixelSize', z, pixelRatio, projection);
+  if (pixelRatio == 1 || !this.hidpi_ || !goog.isDef(this.serverType_)) {
+    return tileSize;
+  } else {
+    return (tileSize * pixelRatio + 0.5) | 0;
   }
 };
 
 
-
 /**
- * @classdesc
- * Layer source for the Stamen tile server.
- *
- * @constructor
- * @extends {ol.source.XYZ}
- * @param {olx.source.StamenOptions} options Stamen options.
+ * Return the URLs used for this WMS source.
+ * @return {!Array.<string>} URLs.
  * @api stable
  */
-ol.source.Stamen = function(options) {
-
-  var i = options.layer.indexOf('-');
-  var provider = i == -1 ? options.layer : options.layer.slice(0, i);
-  goog.asserts.assert(provider in ol.source.StamenProviderConfig);
-  var providerConfig = ol.source.StamenProviderConfig[provider];
-
-  goog.asserts.assert(options.layer in ol.source.StamenLayerConfig);
-  var layerConfig = ol.source.StamenLayerConfig[options.layer];
-
-  var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
-  var url = goog.isDef(options.url) ? options.url :
-      protocol + '//{a-d}.tile.stamen.com/' + options.layer + '/{z}/{x}/{y}.' +
-      layerConfig.extension;
-
-  goog.base(this, {
-    attributions: ol.source.Stamen.ATTRIBUTIONS,
-    crossOrigin: 'anonymous',
-    maxZoom: providerConfig.maxZoom,
-    // FIXME uncomment the following when tilegrid supports minZoom
-    //minZoom: providerConfig.minZoom,
-    opaque: layerConfig.opaque,
-    tileLoadFunction: options.tileLoadFunction,
-    url: url
-  });
-
+ol.source.TileWMS.prototype.getUrls = function() {
+  return this.urls_;
 };
-goog.inherits(ol.source.Stamen, ol.source.XYZ);
 
 
 /**
- * @const
- * @type {Array.<ol.Attribution>}
+ * @private
  */
-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.DATA_ATTRIBUTION
-];
+ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
+  var i = 0;
+  var res = [];
 
-goog.provide('ol.source.TileDebug');
+  var j, jj;
+  for (j = 0, jj = this.urls_.length; j < jj; ++j) {
+    res[i++] = this.urls_[j];
+  }
 
-goog.require('ol.Tile');
-goog.require('ol.TileCache');
-goog.require('ol.TileCoord');
-goog.require('ol.TileState');
-goog.require('ol.dom');
-goog.require('ol.source.Tile');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.TileGrid');
+  var key;
+  for (key in this.params_) {
+    res[i++] = key + '-' + this.params_[key];
+  }
 
+  this.coordKeyPrefix_ = res.join('#');
+};
 
 
 /**
- * @constructor
- * @extends {ol.Tile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @private
+ * @param {string|undefined} url URL.
+ * @api stable
  */
-ol.DebugTile_ = function(tileCoord, tileGrid) {
-
-  goog.base(this, tileCoord, ol.TileState.LOADED);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.tileSize_ = tileGrid.getTileSize(tileCoord[0]);
-
-  /**
-   * @private
-   * @type {Object.<number, HTMLCanvasElement>}
-   */
-  this.canvasByContext_ = {};
-
+ol.source.TileWMS.prototype.setUrl = function(url) {
+  var urls = goog.isDef(url) ? ol.TileUrlFunction.expandUrl(url) : null;
+  this.setUrls(urls);
 };
-goog.inherits(ol.DebugTile_, ol.Tile);
 
 
 /**
- * @inheritDoc
+ * @param {Array.<string>|undefined} urls URLs.
+ * @api stable
  */
-ol.DebugTile_.prototype.getImage = function(opt_context) {
-  var key = goog.isDef(opt_context) ? goog.getUid(opt_context) : -1;
-  if (key in this.canvasByContext_) {
-    return this.canvasByContext_[key];
-  } else {
-
-    var tileSize = this.tileSize_;
-    var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
+ol.source.TileWMS.prototype.setUrls = function(urls) {
+  this.urls_ = goog.isDefAndNotNull(urls) ? urls : [];
+  this.resetCoordKeyPrefix_();
+  this.changed();
+};
 
-    context.strokeStyle = 'black';
-    context.strokeRect(0.5, 0.5, tileSize + 0.5, tileSize + 0.5);
 
-    context.fillStyle = 'black';
-    context.textAlign = 'center';
-    context.textBaseline = 'middle';
-    context.font = '24px sans-serif';
-    context.fillText(ol.tilecoord.toString(this.tileCoord),
-        tileSize / 2, tileSize / 2);
+/**
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {string|undefined} Tile URL.
+ * @private
+ */
+ol.source.TileWMS.prototype.tileUrlFunction_ =
+    function(tileCoord, pixelRatio, projection) {
 
-    this.canvasByContext_[key] = context.canvas;
-    return context.canvas;
+  var tileGrid = this.getTileGrid();
+  if (goog.isNull(tileGrid)) {
+    tileGrid = this.getTileGridForProjection(projection);
+  }
 
+  if (tileGrid.getResolutions().length <= tileCoord[0]) {
+    return undefined;
   }
-};
 
+  if (pixelRatio != 1 && (!this.hidpi_ || !goog.isDef(this.serverType_))) {
+    pixelRatio = 1;
+  }
 
+  var tileResolution = tileGrid.getResolution(tileCoord[0]);
+  var tileExtent = tileGrid.getTileCoordExtent(
+      tileCoord, this.tmpExtent_);
+  var tileSize = tileGrid.getTileSize(tileCoord[0]);
 
-/**
- * @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) {
+  var gutter = this.gutter_;
+  if (gutter !== 0) {
+    tileSize += 2 * gutter;
+    tileExtent = ol.extent.buffer(tileExtent,
+        tileResolution * gutter, tileExtent);
+  }
 
-  goog.base(this, {
-    opaque: false,
-    projection: options.projection,
-    tileGrid: options.tileGrid
-  });
+  if (pixelRatio != 1) {
+    tileSize = (tileSize * pixelRatio + 0.5) | 0;
+  }
 
-  /**
-   * @private
-   * @type {ol.TileCache}
-   */
-  this.tileCache_ = new ol.TileCache();
+  var baseParams = {
+    'SERVICE': 'WMS',
+    'VERSION': ol.DEFAULT_WMS_VERSION,
+    'REQUEST': 'GetMap',
+    'FORMAT': 'image/png',
+    'TRANSPARENT': true
+  };
+  goog.object.extend(baseParams, this.params_);
 
+  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
+      pixelRatio, projection, baseParams);
 };
-goog.inherits(ol.source.TileDebug, ol.source.Tile);
 
 
 /**
- * @inheritDoc
+ * Update the user-provided params.
+ * @param {Object} params Params.
+ * @api stable
  */
-ol.source.TileDebug.prototype.canExpireCache = function() {
-  return this.tileCache_.canExpireCache();
+ol.source.TileWMS.prototype.updateParams = function(params) {
+  goog.object.extend(this.params_, params);
+  this.resetCoordKeyPrefix_();
+  this.updateV13_();
+  this.changed();
 };
 
 
 /**
- * @inheritDoc
+ * @private
  */
-ol.source.TileDebug.prototype.expireCache = function(usedTiles) {
-  this.tileCache_.expireCache(usedTiles);
+ol.source.TileWMS.prototype.updateV13_ = function() {
+  var version =
+      goog.object.get(this.params_, 'VERSION', ol.DEFAULT_WMS_VERSION);
+  this.v13_ = goog.string.compareVersions(version, '1.3') >= 0;
 };
 
+goog.provide('ol.source.TopoJSON');
+
+goog.require('ol.format.TopoJSON');
+goog.require('ol.source.StaticVector');
 
-/**
- * @inheritDoc
- */
-ol.source.TileDebug.prototype.getTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache_.containsKey(tileCoordKey)) {
-    return /** @type {!ol.DebugTile_} */ (this.tileCache_.get(tileCoordKey));
-  } else {
-    var tile = new ol.DebugTile_([z, x, y], this.tileGrid);
-    this.tileCache_.set(tileCoordKey, tile);
-    return tile;
-  }
-};
 
-// FIXME add some error checking
-// FIXME check order of async callbacks
 
 /**
- * @see http://mapbox.com/developers/api/
+ * @classdesc
+ * Static vector source in TopoJSON format
+ *
+ * @constructor
+ * @extends {ol.source.StaticVector}
+ * @fires ol.source.VectorEvent
+ * @param {olx.source.TopoJSONOptions=} opt_options Options.
+ * @api
  */
+ol.source.TopoJSON = function(opt_options) {
 
-goog.provide('ol.source.TileJSON');
-goog.provide('ol.tilejson');
+  var options = goog.isDef(opt_options) ? opt_options : {};
+
+  goog.base(this, {
+    attributions: options.attributions,
+    extent: options.extent,
+    format: new ol.format.TopoJSON({
+      defaultDataProjection: options.defaultProjection
+    }),
+    logo: options.logo,
+    object: options.object,
+    projection: options.projection,
+    text: options.text,
+    url: options.url
+  });
+
+};
+goog.inherits(ol.source.TopoJSON, ol.source.StaticVector);
+
+goog.provide('ol.tilegrid.WMTS');
 
+goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.net.Jsonp');
-goog.require('ol.Attribution');
-goog.require('ol.TileRange');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.extent');
 goog.require('ol.proj');
-goog.require('ol.source.State');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilegrid.XYZ');
+goog.require('ol.tilegrid.TileGrid');
 
 
 
 /**
  * @classdesc
- * Layer source for tile data in TileJSON format.
+ * Set the grid pattern for sources accessing WMTS tiled-image servers.
  *
  * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.TileJSONOptions} options TileJSON options.
- * @api stable
+ * @extends {ol.tilegrid.TileGrid}
+ * @param {olx.tilegrid.WMTSOptions} options WMTS options.
+ * @struct
+ * @api
  */
-ol.source.TileJSON = function(options) {
+ol.tilegrid.WMTS = function(options) {
+
+  goog.asserts.assert(
+      options.resolutions.length == options.matrixIds.length);
+
+  /**
+   * @private
+   * @type {!Array.<string>}
+   */
+  this.matrixIds_ = options.matrixIds;
+  // FIXME: should the matrixIds become optionnal?
 
   goog.base(this, {
-    crossOrigin: options.crossOrigin,
-    projection: ol.proj.get('EPSG:3857'),
-    state: ol.source.State.LOADING,
-    tileLoadFunction: options.tileLoadFunction
+    origin: options.origin,
+    origins: options.origins,
+    resolutions: options.resolutions,
+    tileSize: options.tileSize,
+    tileSizes: options.tileSizes
   });
 
-  var request = new goog.net.Jsonp(options.url);
-  request.send(undefined, goog.bind(this.handleTileJSONResponse, this));
+};
+goog.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
 
+
+/**
+ * @param {number} z Z.
+ * @return {string} MatrixId..
+ */
+ol.tilegrid.WMTS.prototype.getMatrixId = function(z) {
+  goog.asserts.assert(0 <= z && z < this.matrixIds_.length);
+  return this.matrixIds_[z];
 };
-goog.inherits(ol.source.TileJSON, ol.source.TileImage);
 
 
 /**
- * @protected
- * @param {TileJSON} tileJSON Tile JSON.
+ * @return {Array.<string>} MatrixIds.
+ * @api
  */
-ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) {
+ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
+  return this.matrixIds_;
+};
 
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
 
-  var sourceProjection = this.getProjection();
-  var extent;
-  if (goog.isDef(tileJSON.bounds)) {
-    var transform = ol.proj.getTransformFromProjections(
-        epsg4326Projection, sourceProjection);
-    extent = ol.extent.applyTransform(tileJSON.bounds, transform);
-  }
+/**
+ * @param {Object} matrixSet An object representing a matrixSet in the
+ *     capabilities document.
+ * @return {ol.tilegrid.WMTS} WMTS tileGrid instance.
+ */
+ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet =
+    function(matrixSet) {
 
-  if (goog.isDef(tileJSON.scheme)) {
-    goog.asserts.assert(tileJSON.scheme == 'xyz');
-  }
-  var minZoom = tileJSON.minzoom || 0;
-  var maxZoom = tileJSON.maxzoom || 22;
-  var tileGrid = new ol.tilegrid.XYZ({
-    extent: ol.tilegrid.extentFromProjection(sourceProjection),
-    maxZoom: maxZoom,
-    minZoom: minZoom
-  });
-  this.tileGrid = tileGrid;
+  /** @type {!Array.<number>} */
+  var resolutions = [];
+  /** @type {!Array.<string>} */
+  var matrixIds = [];
+  /** @type {!Array.<ol.Coordinate>} */
+  var origins = [];
+  /** @type {!Array.<number>} */
+  var tileSizes = [];
 
-  this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
-      tileGrid.createTileCoordTransform({
-        extent: extent
-      }),
-      ol.TileUrlFunction.createFromTemplates(tileJSON.tiles));
+  var supportedCRSPropName = 'supportedCRS';
+  var matrixIdsPropName = 'matrixIds';
+  var identifierPropName = 'identifier';
+  var scaleDenominatorPropName = 'scaleDenominator';
+  var topLeftCornerPropName = 'topLeftCorner';
+  var tileWidthPropName = 'tileWidth';
+  var tileHeightPropName = 'tileHeight';
 
-  if (goog.isDef(tileJSON.attribution)) {
-    var attributionExtent = goog.isDef(extent) ?
-        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
-      })
-    ]);
-  }
+  var projection = ol.proj.get(matrixSet[supportedCRSPropName]);
+  var metersPerUnit = projection.getMetersPerUnit();
 
-  this.setState(ol.source.State.READY);
+  goog.array.sort(matrixSet[matrixIdsPropName], function(a, b) {
+    return b[scaleDenominatorPropName] - a[scaleDenominatorPropName];
+  });
+
+  goog.array.forEach(matrixSet[matrixIdsPropName],
+      function(elt, index, array) {
+        matrixIds.push(elt[identifierPropName]);
+        origins.push(elt[topLeftCornerPropName]);
+        resolutions.push(elt[scaleDenominatorPropName] * 0.28E-3 /
+            metersPerUnit);
+        var tileWidth = elt[tileWidthPropName];
+        var tileHeight = elt[tileHeightPropName];
+        goog.asserts.assert(tileWidth == tileHeight);
+        tileSizes.push(tileWidth);
+      });
 
+  return new ol.tilegrid.WMTS({
+    origins: origins,
+    resolutions: resolutions,
+    matrixIds: matrixIds,
+    tileSizes: tileSizes
+  });
 };
 
-goog.provide('ol.source.TileVector');
+goog.provide('ol.source.WMTS');
+goog.provide('ol.source.WMTSRequestEncoding');
 
 goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.math');
 goog.require('goog.object');
-goog.require('ol.TileCoord');
+goog.require('goog.uri.utils');
 goog.require('ol.TileUrlFunction');
-goog.require('ol.source.FormatVector');
-goog.require('ol.source.State');
-goog.require('ol.tilegrid.TileGrid');
+goog.require('ol.TileUrlFunctionType');
+goog.require('ol.extent');
+goog.require('ol.proj');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.WMTS');
+
+
+/**
+ * Request encoding. One of 'KVP', 'REST'.
+ * @enum {string}
+ * @api
+ */
+ol.source.WMTSRequestEncoding = {
+  KVP: 'KVP',  // see spec §8
+  REST: 'REST' // see spec §10
+};
 
 
 
 /**
  * @classdesc
- * A vector source in one of the supported formats, where the data is divided
- * into tiles in a fixed grid pattern.
+ * Layer source for tile data from WMTS servers.
  *
  * @constructor
- * @extends {ol.source.FormatVector}
- * @param {olx.source.TileVectorOptions} options Options.
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.WMTSOptions} options WMTS options.
  * @api
  */
-ol.source.TileVector = function(options) {
-
-  goog.base(this, {
-    attributions: options.attributions,
-    format: options.format,
-    logo: options.logo,
-    projection: options.projection
-  });
+ol.source.WMTS = function(options) {
 
-  /**
-   * @private
-   * @type {ol.tilegrid.TileGrid}
-   */
-  this.tileGrid_ = options.tileGrid;
+  // TODO: add support for TileMatrixLimits
 
-  /**
-   * @private
-   * @type {ol.TileUrlFunctionType}
-   */
-  this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction;
+  var version = goog.isDef(options.version) ? options.version : '1.0.0';
+  var format = goog.isDef(options.format) ? options.format : 'image/jpeg';
 
   /**
    * @private
-   * @type {ol.TileCoordTransformType}
+   * @type {Object}
    */
-  this.tileCoordTransform_ = this.tileGrid_.createTileCoordTransform();
+  this.dimensions_ = goog.isDef(options.dimensions) ? options.dimensions : {};
 
   /**
    * @private
-   * @type {Object.<string, Array.<ol.Feature>>}
+   * @type {string}
    */
-  this.tiles_ = {};
+  this.coordKeyPrefix_ = '';
+  this.resetCoordKeyPrefix_();
 
-  if (goog.isDef(options.tileUrlFunction)) {
-    this.setTileUrlFunction(options.tileUrlFunction);
-  } else if (goog.isDef(options.urls)) {
-    this.setUrls(options.urls);
-  } else if (goog.isDef(options.url)) {
-    this.setUrl(options.url);
-  }
+  // FIXME: should we guess this requestEncoding from options.url(s)
+  //        structure? that would mean KVP only if a template is not provided.
+  var requestEncoding = goog.isDef(options.requestEncoding) ?
+      /** @type {ol.source.WMTSRequestEncoding} */ (options.requestEncoding) :
+      ol.source.WMTSRequestEncoding.KVP;
 
-};
-goog.inherits(ol.source.TileVector, ol.source.FormatVector);
+  // FIXME: should we create a default tileGrid?
+  // we could issue a getCapabilities xhr to retrieve missing configuration
+  var tileGrid = options.tileGrid;
 
+  var context = {
+    'Layer': options.layer,
+    'Style': options.style,
+    'TileMatrixSet': options.matrixSet
+  };
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.addFeature = goog.abstractMethod;
+  if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
+    goog.object.extend(context, {
+      'Service': 'WMTS',
+      'Request': 'GetTile',
+      'Version': version,
+      'Format': format
+    });
+  }
 
+  var dimensions = this.dimensions_;
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.addFeatures = goog.abstractMethod;
+  /**
+   * @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
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.clear = function() {
-  goog.object.clear(this.tiles_);
-};
+    template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ?
+        goog.uri.utils.appendParamsFromMap(template, context) :
+        template.replace(/\{(\w+?)\}/g, function(m, p) {
+          return (p in context) ? context[p] : 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 (goog.isNull(tileCoord)) {
+            return undefined;
+          } else {
+            var localContext = {
+              'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
+              'TileCol': tileCoord[1],
+              'TileRow': tileCoord[2]
+            };
+            goog.object.extend(localContext, dimensions);
+            var url = template;
+            if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
+              url = goog.uri.utils.appendParamsFromMap(url, localContext);
+            } else {
+              url = url.replace(/\{(\w+?)\}/g, function(m, p) {
+                return localContext[p];
+              });
+            }
+            return url;
+          }
+        });
+  }
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.forEachFeature = goog.abstractMethod;
+  var tileUrlFunction = ol.TileUrlFunction.nullTileUrlFunction;
+  var urls = options.urls;
+  if (!goog.isDef(urls) && goog.isDef(options.url)) {
+    urls = ol.TileUrlFunction.expandUrl(options.url);
+  }
+  if (goog.isDef(urls)) {
+    tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions(
+        goog.array.map(urls, createFromWMTSTemplate));
+  }
 
+  var tmpExtent = ol.extent.createEmpty();
+  var tmpTileCoord = [0, 0, 0];
+  tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
+      /**
+       * @param {ol.TileCoord} tileCoord Tile coordinate.
+       * @param {ol.proj.Projection} projection Projection.
+       * @param {ol.TileCoord=} opt_tileCoord Tile coordinate.
+       * @return {ol.TileCoord} Tile coordinate.
+       */
+      function(tileCoord, projection, opt_tileCoord) {
+        goog.asserts.assert(!goog.isNull(tileGrid));
+        if (tileGrid.getResolutions().length <= tileCoord[0]) {
+          return null;
+        }
+        var x = tileCoord[1];
+        var y = -tileCoord[2] - 1;
+        var tileExtent = tileGrid.getTileCoordExtent(tileCoord, tmpExtent);
+        var extent = projection.getExtent();
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.forEachFeatureInExtent = goog.abstractMethod;
+        if (!goog.isNull(extent) && projection.isGlobal()) {
+          var numCols = Math.ceil(
+              ol.extent.getWidth(extent) /
+              ol.extent.getWidth(tileExtent));
+          x = goog.math.modulo(x, numCols);
+          tmpTileCoord[0] = tileCoord[0];
+          tmpTileCoord[1] = x;
+          tmpTileCoord[2] = tileCoord[2];
+          tileExtent = tileGrid.getTileCoordExtent(tmpTileCoord, tmpExtent);
+        }
+        if (!ol.extent.intersects(tileExtent, extent) ||
+            ol.extent.touches(tileExtent, extent)) {
+          return null;
+        }
+        return ol.tilecoord.createOrUpdate(tileCoord[0], x, y, opt_tileCoord);
+      },
+      tileUrlFunction);
 
+  goog.base(this, {
+    attributions: options.attributions,
+    crossOrigin: options.crossOrigin,
+    logo: options.logo,
+    projection: options.projection,
+    tileClass: options.tileClass,
+    tileGrid: tileGrid,
+    tileLoadFunction: options.tileLoadFunction,
+    tilePixelRatio: options.tilePixelRatio,
+    tileUrlFunction: tileUrlFunction
+  });
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.forEachFeatureInExtentAtResolution =
-    function(extent, resolution, f, opt_this) {
-  var tileGrid = this.tileGrid_;
-  var tiles = this.tiles_;
-  var z = tileGrid.getZForResolution(resolution);
-  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
-  var x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      var tileKey = this.getTileKeyZXY_(z, x, y);
-      var features = tiles[tileKey];
-      if (goog.isDef(features)) {
-        var i, ii;
-        for (i = 0, ii = features.length; i < ii; ++i) {
-          var result = f.call(opt_this, features[i]);
-          if (result) {
-            return result;
-          }
-        }
-      }
-    }
-  }
-  return undefined;
 };
+goog.inherits(ol.source.WMTS, ol.source.TileImage);
 
 
 /**
- * @inheritDoc
+ * 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.TileVector.prototype.getClosestFeatureToCoordinate =
-    goog.abstractMethod;
+ol.source.WMTS.prototype.getDimensions = function() {
+  return this.dimensions_;
+};
 
 
 /**
  * @inheritDoc
  */
-ol.source.TileVector.prototype.getExtent = goog.abstractMethod;
+ol.source.WMTS.prototype.getKeyZXY = function(z, x, y) {
+  return this.coordKeyPrefix_ + goog.base(this, 'getKeyZXY', z, x, y);
+};
 
 
 /**
- * @inheritDoc
+ * @private
  */
-ol.source.TileVector.prototype.getFeatures = function() {
-  var tiles = this.tiles_;
-  var features = [];
-  var tileKey;
-  for (tileKey in tiles) {
-    goog.array.extend(features, tiles[tileKey]);
+ol.source.WMTS.prototype.resetCoordKeyPrefix_ = function() {
+  var i = 0;
+  var res = [];
+  for (var key in this.dimensions_) {
+    res[i++] = key + '-' + this.dimensions_[key];
   }
-  return features;
+  this.coordKeyPrefix_ = res.join('/');
 };
 
 
 /**
- * @inheritDoc
+ * Update the dimensions.
+ * @param {Object} dimensions Dimensions.
+ * @api
  */
-ol.source.TileVector.prototype.getFeaturesInExtent = goog.abstractMethod;
+ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
+  goog.object.extend(this.dimensions_, dimensions);
+  this.resetCoordKeyPrefix_();
+  this.changed();
+};
 
 
 /**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @private
- * @return {string} Tile key.
+ * @param {Object} wmtsCap An object representing the capabilities document.
+ * @param {string} layer The layer identifier.
+ * @return {olx.source.WMTSOptions} WMTS source options object.
  */
-ol.source.TileVector.prototype.getTileKeyZXY_ = function(z, x, y) {
-  return z + '/' + x + '/' + y;
-};
+ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, layer) {
 
+  /* jshint -W069 */
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.loadFeatures =
-    function(extent, resolution, projection) {
-  var tileCoordTransform = this.tileCoordTransform_;
-  var tileGrid = this.tileGrid_;
-  var tileUrlFunction = this.tileUrlFunction_;
-  var tiles = this.tiles_;
-  var z = tileGrid.getZForResolution(resolution);
-  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
-  var tileCoord = [z, 0, 0];
-  var x, y;
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      var tileKey = this.getTileKeyZXY_(z, x, y);
-      if (!(tileKey in tiles)) {
-        tileCoord[0] = z;
-        tileCoord[1] = x;
-        tileCoord[2] = y;
-        tileCoordTransform(tileCoord, projection, tileCoord);
-        var url = tileUrlFunction(tileCoord, 1, projection);
-        if (goog.isDef(url)) {
-          tiles[tileKey] = [];
-          this.loadFeaturesFromURL(url, goog.partial(
-              /**
-               * @param {string} tileKey Tile key.
-               * @param {Array.<ol.Feature>} features Features.
-               * @this {ol.source.TileVector}
-               */
-              function(tileKey, features) {
-                tiles[tileKey] = features;
-                this.setState(ol.source.State.READY);
-              }, tileKey), this);
-        }
-      }
-    }
+  // TODO: add support for TileMatrixLimits
+
+  var layers = wmtsCap['contents']['layers'];
+  var l = goog.array.find(layers, function(elt, index, array) {
+    return elt['identifier'] == layer;
+  });
+  goog.asserts.assert(!goog.isNull(l));
+  goog.asserts.assert(l['tileMatrixSetLinks'].length > 0);
+  var matrixSet = /** @type {string} */
+      (l['tileMatrixSetLinks'][0]['tileMatrixSet']);
+  var format = /** @type {string} */ (l['formats'][0]);
+  var idx = goog.array.findIndex(l['styles'], function(elt, index, array) {
+    return elt['isDefault'];
+  });
+  if (idx < 0) {
+    idx = 0;
   }
-};
+  var style = /** @type {string} */ (l['styles'][idx]['identifier']);
 
+  var dimensions = {};
+  goog.array.forEach(l['dimensions'], function(elt, index, array) {
+    var key = elt['identifier'];
+    var value = elt['default'];
+    if (goog.isDef(value)) {
+      goog.asserts.assert(goog.array.contains(elt['values'], value));
+    } else {
+      value = elt['values'][0];
+    }
+    goog.asserts.assert(goog.isDef(value));
+    dimensions[key] = value;
+  });
 
-/**
- * @inheritDoc
- */
-ol.source.TileVector.prototype.removeFeature = goog.abstractMethod;
+  var matrixSets = wmtsCap['contents']['tileMatrixSets'];
+  goog.asserts.assert(matrixSet in matrixSets);
+  var matrixSetObj = matrixSets[matrixSet];
 
+  var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet(
+      matrixSetObj);
 
-/**
- * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
- */
-ol.source.TileVector.prototype.setTileUrlFunction = function(tileUrlFunction) {
-  this.tileUrlFunction_ = tileUrlFunction;
-  this.dispatchChangeEvent();
-};
+  var projection = ol.proj.get(matrixSetObj['supportedCRS']);
 
+  var gets = wmtsCap['operationsMetadata']['GetTile']['dcp']['http']['get'];
+  var encodings = goog.object.getKeys(
+      gets[0]['constraints']['GetEncoding']['allowedValues']);
+  goog.asserts.assert(encodings.length > 0);
 
-/**
- * @param {string} url URL.
- */
-ol.source.TileVector.prototype.setUrl = function(url) {
-  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(
-      ol.TileUrlFunction.expandUrl(url)));
-};
+  var urls;
+  var requestEncoding;
+  switch (encodings[0]) {
+    case 'REST':
+    case 'RESTful':
+      // The OGC documentation is not clear if we should use REST or RESTful,
+      // ArcGis use RESTful, and OpenLayers use REST.
+      requestEncoding = ol.source.WMTSRequestEncoding.REST;
+      goog.asserts.assert(l['resourceUrls'].hasOwnProperty('tile'));
+      goog.asserts.assert(l['resourceUrls']['tile'].hasOwnProperty(format));
+      urls = /** @type {Array.<string>} */
+          (l['resourceUrls']['tile'][format]);
+      break;
+    case 'KVP':
+      requestEncoding = ol.source.WMTSRequestEncoding.KVP;
+      urls = [];
+      goog.array.forEach(gets, function(elt, index, array) {
+        if (elt['constraints']['GetEncoding']['allowedValues'].hasOwnProperty(
+            ol.source.WMTSRequestEncoding.KVP)) {
+          urls.push(/** @type {string} */ (elt['url']));
+        }
+      });
+      goog.asserts.assert(urls.length > 0);
+      break;
+    default:
+      goog.asserts.fail();
+  }
+
+  return {
+    urls: urls,
+    layer: layer,
+    matrixSet: matrixSet,
+    format: format,
+    projection: projection,
+    requestEncoding: requestEncoding,
+    tileGrid: tileGrid,
+    style: style,
+    dimensions: dimensions
+  };
 
+  /* jshint +W069 */
 
-/**
- * @param {Array.<string>} urls URLs.
- */
-ol.source.TileVector.prototype.setUrls = function(urls) {
-  this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates(urls));
 };
 
-// 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.provide('ol.tilegrid.Zoomify');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
 goog.require('goog.math');
-goog.require('goog.object');
-goog.require('goog.string');
-goog.require('goog.uri.utils');
-goog.require('ol');
 goog.require('ol.TileCoord');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.extent');
 goog.require('ol.proj');
-goog.require('ol.source.TileImage');
-goog.require('ol.source.wms');
-goog.require('ol.source.wms.ServerType');
 goog.require('ol.tilecoord');
+goog.require('ol.tilegrid.TileGrid');
 
 
 
 /**
  * @classdesc
- * Layer source for tile data from WMS servers.
+ * Set the grid pattern for sources accessing Zoomify tiled-image servers.
  *
  * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options.
- * @api stable
+ * @extends {ol.tilegrid.TileGrid}
+ * @param {olx.tilegrid.ZoomifyOptions=} opt_options Options.
+ * @api
  */
-ol.source.TileWMS = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
-
-  var params = goog.isDef(options.params) ? options.params : {};
-
-  var transparent = goog.object.get(params, 'TRANSPARENT', true);
-
+ol.tilegrid.Zoomify = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : options;
   goog.base(this, {
-    attributions: options.attributions,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    opaque: !transparent,
-    projection: options.projection,
-    tileGrid: options.tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    tileUrlFunction: goog.bind(this.tileUrlFunction_, this)
+    origin: [0, 0],
+    resolutions: options.resolutions
   });
 
-  var urls = options.urls;
-  if (!goog.isDef(urls) && goog.isDef(options.url)) {
-    urls = ol.TileUrlFunction.expandUrl(options.url);
-  }
-
-  /**
-   * @private
-   * @type {!Array.<string>}
-   */
-  this.urls_ = goog.isDefAndNotNull(urls) ? urls : [];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.gutter_ = goog.isDef(options.gutter) ? options.gutter : 0;
-
-  /**
-   * @private
-   * @type {Object}
-   */
-  this.params_ = params;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.v13_ = true;
+};
+goog.inherits(ol.tilegrid.Zoomify, ol.tilegrid.TileGrid);
 
-  /**
-   * @private
-   * @type {ol.source.wms.ServerType|undefined}
-   */
-  this.serverType_ =
-      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.hidpi_ = goog.isDef(options.hidpi) ? options.hidpi : true;
+/**
+ * @inheritDoc
+ */
+ol.tilegrid.Zoomify.prototype.createTileCoordTransform = function(opt_options) {
+  var options = goog.isDef(opt_options) ? opt_options : {};
+  var minZ = this.minZoom;
+  var maxZ = this.maxZoom;
+  /** @type {Array.<ol.TileRange>} */
+  var tileRangeByZ = null;
+  if (goog.isDef(options.extent)) {
+    tileRangeByZ = new Array(maxZ + 1);
+    var z;
+    for (z = 0; z <= maxZ; ++z) {
+      if (z < minZ) {
+        tileRangeByZ[z] = null;
+      } else {
+        tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z);
+      }
+    }
+  }
+  return (
+      /**
+       * @param {ol.TileCoord} tileCoord Tile coordinate.
+       * @param {ol.proj.Projection} projection Projection.
+       * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate.
+       * @return {ol.TileCoord} Tile coordinate.
+       */
+      function(tileCoord, projection, opt_tileCoord) {
+        var z = tileCoord[0];
+        if (z < minZ || maxZ < z) {
+          return null;
+        }
+        var n = Math.pow(2, z);
+        var x = tileCoord[1];
+        if (x < 0 || n <= x) {
+          return null;
+        }
+        var y = tileCoord[2];
+        if (y < -n || -1 < y) {
+          return null;
+        }
+        if (!goog.isNull(tileRangeByZ)) {
+          if (!tileRangeByZ[z].containsXY(x, -y - 1)) {
+            return null;
+          }
+        }
+        return ol.tilecoord.createOrUpdate(z, x, -y - 1, opt_tileCoord);
+      });
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.coordKeyPrefix_ = '';
-  this.resetCoordKeyPrefix_();
+goog.provide('ol.source.Zoomify');
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.tmpExtent_ = ol.extent.createEmpty();
+goog.require('goog.asserts');
+goog.require('ol');
+goog.require('ol.ImageTile');
+goog.require('ol.TileCoord');
+goog.require('ol.TileState');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.dom');
+goog.require('ol.proj');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilegrid.Zoomify');
 
-  this.updateV13_();
 
+/**
+ * @enum {string}
+ */
+ol.source.ZoomifyTierSizeCalculation = {
+  DEFAULT: 'default',
+  TRUNCATED: 'truncated'
 };
-goog.inherits(ol.source.TileWMS, ol.source.TileImage);
+
 
 
 /**
- * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
- * projection. Return `undefined` if the GetFeatureInfo URL cannot be
- * constructed.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {ol.proj.ProjectionLike} projection Projection.
- * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
- *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
- *     in the `LAYERS` parameter will be used. `VERSION` should not be
- *     specified here.
- * @return {string|undefined} GetFeatureInfo URL.
+ * @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.TileWMS.prototype.getGetFeatureInfoUrl =
-    function(coordinate, resolution, projection, params) {
-
-  goog.asserts.assert(!('VERSION' in params));
+ol.source.Zoomify = function(opt_options) {
 
-  var projectionObj = ol.proj.get(projection);
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  var tileGrid = this.getTileGrid();
-  if (goog.isNull(tileGrid)) {
-    tileGrid = this.getTileGridForProjection(projectionObj);
-  }
+  var size = options.size;
+  var tierSizeCalculation = goog.isDef(options.tierSizeCalculation) ?
+      options.tierSizeCalculation :
+      ol.source.ZoomifyTierSizeCalculation.DEFAULT;
 
-  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(
-      coordinate, resolution);
+  var imageWidth = size[0];
+  var imageHeight = size[1];
+  var tierSizeInTiles = [];
+  var tileSize = ol.DEFAULT_TILE_SIZE;
 
-  if (tileGrid.getResolutions().length <= tileCoord[0]) {
-    return undefined;
+  switch (tierSizeCalculation) {
+    case ol.source.ZoomifyTierSizeCalculation.DEFAULT:
+      while (imageWidth > tileSize || imageHeight > tileSize) {
+        tierSizeInTiles.push([
+          Math.ceil(imageWidth / tileSize),
+          Math.ceil(imageHeight / tileSize)
+        ]);
+        tileSize += tileSize;
+      }
+      break;
+    case ol.source.ZoomifyTierSizeCalculation.TRUNCATED:
+      var width = imageWidth;
+      var height = imageHeight;
+      while (width > tileSize || height > tileSize) {
+        tierSizeInTiles.push([
+          Math.ceil(width / tileSize),
+          Math.ceil(height / tileSize)
+        ]);
+        width >>= 1;
+        height >>= 1;
+      }
+      break;
+    default:
+      goog.asserts.fail();
+      break;
   }
 
-  var tileResolution = tileGrid.getResolution(tileCoord[0]);
-  var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_);
-  var tileSize = tileGrid.getTileSize(tileCoord[0]);
+  tierSizeInTiles.push([1, 1]);
+  tierSizeInTiles.reverse();
 
-  var gutter = this.gutter_;
-  if (gutter !== 0) {
-    tileSize += 2 * gutter;
-    tileExtent = ol.extent.buffer(tileExtent,
-        tileResolution * gutter, tileExtent);
+  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 baseParams = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetFeatureInfo',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true,
-    'QUERY_LAYERS': goog.object.get(this.params_, 'LAYERS')
-  };
-  goog.object.extend(baseParams, this.params_, params);
-
-  var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution);
-  var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution);
-
-  goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x);
-  goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y);
-
-  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
-      1, projectionObj, baseParams);
-};
-
+  var tileGrid = new ol.tilegrid.Zoomify({
+    resolutions: resolutions
+  });
 
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.getGutter = function() {
-  return this.gutter_;
-};
+  var url = options.url;
+  var tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
+      tileGrid.createTileCoordTransform({extent: [0, 0, size[0], size[1]]}),
+      /**
+       * @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(tileCoord, pixelRatio, projection) {
+        if (goog.isNull(tileCoord)) {
+          return undefined;
+        } else {
+          var tileCoordZ = tileCoord[0];
+          var tileCoordX = tileCoord[1];
+          var tileCoordY = tileCoord[2];
+          var tileIndex =
+              tileCoordX +
+              tileCoordY * tierSizeInTiles[tileCoordZ][0] +
+              tileCountUpToTier[tileCoordZ];
+          var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0;
+          return url + 'TileGroup' + tileGroup + '/' +
+              tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg';
+        }
+      });
 
+  goog.base(this, {
+    attributions: options.attributions,
+    crossOrigin: options.crossOrigin,
+    logo: options.logo,
+    tileClass: ol.source.ZoomifyTile_,
+    tileGrid: tileGrid,
+    tileUrlFunction: tileUrlFunction
+  });
 
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
-  return this.coordKeyPrefix_ + goog.base(this, 'getKeyZXY', z, x, y);
 };
+goog.inherits(ol.source.Zoomify, 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 stable
- */
-ol.source.TileWMS.prototype.getParams = function() {
-  return this.params_;
-};
-
 
 /**
+ * @constructor
+ * @extends {ol.ImageTile}
  * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {number} 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.
+ * @param {ol.TileState} state State.
+ * @param {string} src Image source URI.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
  * @private
  */
-ol.source.TileWMS.prototype.getRequestUrl_ =
-    function(tileCoord, tileSize, tileExtent,
-        pixelRatio, projection, params) {
-
-  var urls = this.urls_;
-  if (goog.array.isEmpty(urls)) {
-    return undefined;
-  }
-
-  goog.object.set(params, 'WIDTH', tileSize);
-  goog.object.set(params, 'HEIGHT', tileSize);
+ol.source.ZoomifyTile_ = function(
+    tileCoord, state, src, crossOrigin, tileLoadFunction) {
 
-  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
+  goog.base(this, tileCoord, state, src, crossOrigin, tileLoadFunction);
 
-  if (!('STYLES' in this.params_)) {
-    /* jshint -W053 */
-    goog.object.set(params, 'STYLES', new String(''));
-    /* jshint +W053 */
-  }
+  /**
+   * @private
+   * @type {Object.<string,
+   *                HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+   */
+  this.zoomifyImageByContext_ = {};
 
-  if (pixelRatio != 1) {
-    switch (this.serverType_) {
-      case ol.source.wms.ServerType.GEOSERVER:
-        var dpi = (90 * pixelRatio + 0.5) | 0;
-        goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi);
-        break;
-      case ol.source.wms.ServerType.MAPSERVER:
-        goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio);
-        break;
-      case ol.source.wms.ServerType.CARMENTA_SERVER:
-      case ol.source.wms.ServerType.QGIS:
-        goog.object.set(params, 'DPI', 90 * pixelRatio);
-        break;
-      default:
-        goog.asserts.fail();
-        break;
-    }
-  }
+};
+goog.inherits(ol.source.ZoomifyTile_, ol.ImageTile);
 
-  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;
-  }
-  goog.object.set(params, 'BBOX', bbox.join(','));
 
-  var url;
-  if (urls.length == 1) {
-    url = urls[0];
+/**
+ * @inheritDoc
+ */
+ol.source.ZoomifyTile_.prototype.getImage = function(opt_context) {
+  var tileSize = ol.DEFAULT_TILE_SIZE;
+  var key = goog.isDef(opt_context) ? goog.getUid(opt_context).toString() : '';
+  if (key in this.zoomifyImageByContext_) {
+    return this.zoomifyImageByContext_[key];
   } else {
-    var index = goog.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
-    url = urls[index];
+    var image = goog.base(this, 'getImage', opt_context);
+    if (this.state == ol.TileState.LOADED) {
+      if (image.width == tileSize && image.height == tileSize) {
+        this.zoomifyImageByContext_[key] = image;
+        return image;
+      } else {
+        var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
+        context.drawImage(image, 0, 0);
+        this.zoomifyImageByContext_[key] = context.canvas;
+        return context.canvas;
+      }
+    } else {
+      return image;
+    }
   }
-  return goog.uri.utils.appendParamsFromMap(url, params);
 };
 
+goog.provide('ol.style.Atlas');
+goog.provide('ol.style.AtlasManager');
 
-/**
- * @param {number} z Z.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {number} Size.
- */
-ol.source.TileWMS.prototype.getTilePixelSize =
-    function(z, pixelRatio, projection) {
-  var tileSize = goog.base(this, 'getTilePixelSize', z, pixelRatio, projection);
-  if (pixelRatio == 1 || !this.hidpi_ || !goog.isDef(this.serverType_)) {
-    return tileSize;
-  } else {
-    return (tileSize * pixelRatio + 0.5) | 0;
-  }
-};
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.object');
+goog.require('ol');
 
 
 /**
- * Return the URLs used for this WMS source.
- * @return {!Array.<string>} URLs.
- * @api stable
+ * Provides information for an image inside an atlas manager.
+ * `offsetX` and `offsetY` is the position of the image inside
+ * the atlas image `image`.
+ * `hitOffsetX` and `hitOffsetY` ist the position of the hit-detection image
+ * inside the hit-detection atlas image `hitImage` (only when a hit-detection
+ * image was created for this image).
+ * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement,
+ *    hitOffsetX: number, hitOffsetY: number, hitImage: HTMLCanvasElement}}
  */
-ol.source.TileWMS.prototype.getUrls = function() {
-  return this.urls_;
-};
+ol.style.AtlasManagerInfo;
+
 
 
 /**
- * @private
+ * 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.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
-  var i = 0;
-  var res = [];
+ol.style.AtlasManager = function(opt_options) {
 
-  var j, jj;
-  for (j = 0, jj = this.urls_.length; j < jj; ++j) {
-    res[i++] = this.urls_[j];
-  }
+  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  var key;
-  for (key in this.params_) {
-    res[i++] = key + '-' + this.params_[key];
-  }
+  /**
+   * The size in pixels of the latest atlas image.
+   * @private
+   * @type {number}
+   */
+  this.currentSize_ = goog.isDef(options.initialSize) ?
+      options.initialSize : ol.INITIAL_ATLAS_SIZE;
 
-  this.coordKeyPrefix_ = res.join('#');
-};
+  /**
+   * The maximum size in pixels of atlas images.
+   * @private
+   * @type {number}
+   */
+  this.maxSize_ = goog.isDef(options.maxSize) ?
+      options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
+          ol.MAX_ATLAS_SIZE : goog.isDef(ol.WEBGL_MAX_TEXTURE_SIZE) ?
+              ol.WEBGL_MAX_TEXTURE_SIZE : 2048;
 
+  /**
+   * The size in pixels between images.
+   * @private
+   * @type {number}
+   */
+  this.space_ = goog.isDef(options.space) ? options.space : 1;
 
-/**
- * @param {string|undefined} url URL.
- * @api stable
- */
-ol.source.TileWMS.prototype.setUrl = function(url) {
-  var urls = goog.isDef(url) ? ol.TileUrlFunction.expandUrl(url) : null;
-  this.setUrls(urls);
-};
+  /**
+   * @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_;
 
-/**
- * @param {Array.<string>|undefined} urls URLs.
- * @api stable
- */
-ol.source.TileWMS.prototype.setUrls = function(urls) {
-  this.urls_ = goog.isDefAndNotNull(urls) ? urls : [];
-  this.resetCoordKeyPrefix_();
-  this.dispatchChangeEvent();
+  /**
+   * @private
+   * @type {Array.<ol.style.Atlas>}
+   */
+  this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
 };
 
 
 /**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {string|undefined} Tile URL.
- * @private
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
+ *    entry, or `null` if the entry is not part of the atlas manager.
  */
-ol.source.TileWMS.prototype.tileUrlFunction_ =
-    function(tileCoord, pixelRatio, projection) {
-
-  var tileGrid = this.getTileGrid();
-  if (goog.isNull(tileGrid)) {
-    tileGrid = this.getTileGridForProjection(projection);
-  }
-
-  if (tileGrid.getResolutions().length <= tileCoord[0]) {
-    return undefined;
-  }
-
-  if (pixelRatio != 1 && (!this.hidpi_ || !goog.isDef(this.serverType_))) {
-    pixelRatio = 1;
-  }
-
-  var tileResolution = tileGrid.getResolution(tileCoord[0]);
-  var tileExtent = tileGrid.getTileCoordExtent(
-      tileCoord, this.tmpExtent_);
-  var tileSize = tileGrid.getTileSize(tileCoord[0]);
-
-  var gutter = this.gutter_;
-  if (gutter !== 0) {
-    tileSize += 2 * gutter;
-    tileExtent = ol.extent.buffer(tileExtent,
-        tileResolution * gutter, tileExtent);
-  }
+ol.style.AtlasManager.prototype.getInfo = function(id) {
+  /** @type {?ol.style.AtlasInfo} */
+  var info = this.getInfo_(this.atlases_, id);
 
-  if (pixelRatio != 1) {
-    tileSize = (tileSize * pixelRatio + 0.5) | 0;
+  if (goog.isNull(info)) {
+    return null;
   }
+  /** @type {?ol.style.AtlasInfo} */
+  var hitInfo = this.getInfo_(this.hitAtlases_, id);
 
-  var baseParams = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetMap',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true
-  };
-  goog.object.extend(baseParams, this.params_);
-
-  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
-      pixelRatio, projection, baseParams);
+  return this.mergeInfos_(info, hitInfo);
 };
 
 
 /**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
+ * @private
+ * @param {Array.<ol.style.Atlas>} atlases The atlases to search.
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
+ *    or `null` if the entry is not part of the atlases.
  */
-ol.source.TileWMS.prototype.updateParams = function(params) {
-  goog.object.extend(this.params_, params);
-  this.resetCoordKeyPrefix_();
-  this.updateV13_();
-  this.dispatchChangeEvent();
+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 (!goog.isNull(info)) {
+      return info;
+    }
+  }
+  return null;
 };
 
 
 /**
  * @private
+ * @param {ol.style.AtlasInfo} info The info for the real image.
+ * @param {?ol.style.AtlasInfo} hitInfo The info for the hit-detection
+ *    image.
+ * @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
+ *    entry, or `null` if the entry is not part of the atlases.
  */
-ol.source.TileWMS.prototype.updateV13_ = function() {
-  var version =
-      goog.object.get(this.params_, 'VERSION', ol.DEFAULT_WMS_VERSION);
-  this.v13_ = goog.string.compareVersions(version, '1.3') >= 0;
+ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
+  return /** @type {ol.style.AtlasManagerInfo} */ ({
+    offsetX: info.offsetX,
+    offsetY: info.offsetY,
+    image: info.image,
+    hitOffsetX: goog.isNull(hitInfo) ? undefined : hitInfo.offsetX,
+    hitOffsetY: goog.isNull(hitInfo) ? undefined : hitInfo.offsetY,
+    hitImage: goog.isNull(hitInfo) ? undefined : hitInfo.image
+  });
 };
 
-goog.provide('ol.source.TopoJSON');
-
-goog.require('ol.format.TopoJSON');
-goog.require('ol.source.StaticVector');
-
-
 
 /**
- * @classdesc
- * Static vector source in TopoJSON format
+ * Add an image to the atlas manager.
  *
- * @constructor
- * @extends {ol.source.StaticVector}
- * @fires ol.source.VectorEvent
- * @param {olx.source.TopoJSONOptions=} opt_options Options.
- * @api
+ * If an entry for the given id already exists, the entry will
+ * be overridden (but the space on the atlas graphic will not be freed).
+ *
+ * If `renderHitCallback` is provided, the image (or the hit-detection version
+ * of the image) will be rendered into a separate hit-detection atlas image.
+ *
+ * @param {string} id The identifier of the entry to add.
+ * @param {number} width The width.
+ * @param {number} height The height.
+ * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
+ *    Called to render the new image onto an atlas image.
+ * @param {function(CanvasRenderingContext2D, number, number)=}
+ *    opt_renderHitCallback Called to render a hit-detection image onto a hit
+ *    detection atlas image.
+ * @param {Object=} opt_this Value to use as `this` when executing
+ *    `renderCallback` and `renderHitCallback`.
+ * @return {?ol.style.AtlasManagerInfo}  The position and atlas image for the
+ *    entry, or `null` if the image is too big.
  */
-ol.source.TopoJSON = function(opt_options) {
-
-  var options = goog.isDef(opt_options) ? opt_options : {};
+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;
+  }
 
-  goog.base(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    format: new ol.format.TopoJSON({
-      defaultDataProjection: options.defaultProjection
-    }),
-    logo: options.logo,
-    object: options.object,
-    projection: options.projection,
-    text: options.text,
-    url: options.url
-  });
+  /** @type {?ol.style.AtlasInfo} */
+  var info = this.add_(false,
+      id, width, height, renderCallback, opt_this);
+  if (goog.isNull(info)) {
+    return null;
+  }
 
+  /** @type {?ol.style.AtlasInfo} */
+  var hitInfo = null;
+  if (goog.isDef(opt_renderHitCallback)) {
+    hitInfo = this.add_(true,
+        id, width, height, opt_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.style.AtlasInfo}  The position and atlas image for the entry,
+ *    or `null` if the image is too big.
+ */
+ol.style.AtlasManager.prototype.add_ =
+    function(isHitAtlas, id, width, height,
+        renderCallback, opt_this) {
+  var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_;
+  var atlas, info, i, ii;
+  for (i = 0, ii = atlases.length; i < ii; ++i) {
+    atlas = atlases[i];
+    info = atlas.add(id, width, height, renderCallback, opt_this);
+    if (!goog.isNull(info)) {
+      return info;
+    } else if (goog.isNull(info) && i === ii - 1) {
+      // the entry could not be added to one of the existing atlases,
+      // create a new atlas that is twice as big and try to add to this one.
+      var size;
+      if (isHitAtlas) {
+        size = Math.min(this.currentHitSize_ * 2, this.maxSize_);
+        this.currentHitSize_ = size;
+      } else {
+        size = Math.min(this.currentSize_ * 2, this.maxSize_);
+        this.currentSize_ = size;
+      }
+      atlas = new ol.style.Atlas(size, this.space_);
+      atlases.push(atlas);
+      // run the loop another time
+      ++ii;
+    }
+  }
+  goog.asserts.fail();
 };
-goog.inherits(ol.source.TopoJSON, ol.source.StaticVector);
 
-goog.provide('ol.tilegrid.WMTS');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('ol.proj');
-goog.require('ol.tilegrid.TileGrid');
+/**
+ * Provides information for an image inside an atlas.
+ * `offsetX` and `offsetY` are the position of the image inside
+ * the atlas image `image`.
+ * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement}}
+ */
+ol.style.AtlasInfo;
 
 
 
 /**
- * @classdesc
- * Set the grid pattern for sources accessing WMTS tiled-image servers.
+ * This class facilitates the creation of image atlases.
+ *
+ * Images added to an atlas will be rendered onto a single
+ * atlas canvas. The distribution of images on the canvas is
+ * managed with the bin packing algorithm described in:
+ * http://www.blackpawn.com/texts/lightmaps/
  *
  * @constructor
- * @extends {ol.tilegrid.TileGrid}
- * @param {olx.tilegrid.WMTSOptions} options WMTS options.
  * @struct
- * @api
+ * @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.tilegrid.WMTS = function(options) {
+ol.style.Atlas = function(size, space) {
 
-  goog.asserts.assert(
-      options.resolutions.length == options.matrixIds.length);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.space_ = space;
 
   /**
    * @private
-   * @type {!Array.<string>}
+   * @type {Array.<ol.style.Atlas.Block>}
    */
-  this.matrixIds_ = options.matrixIds;
-  // FIXME: should the matrixIds become optionnal?
+  this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
 
-  goog.base(this, {
-    origin: options.origin,
-    origins: options.origins,
-    resolutions: options.resolutions,
-    tileSize: options.tileSize,
-    tileSizes: options.tileSizes
-  });
+  /**
+   * @private
+   * @type {Object.<string, ol.style.AtlasInfo>}
+   */
+  this.entries_ = {};
+
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = /** @type {HTMLCanvasElement} */
+      (goog.dom.createElement(goog.dom.TagName.CANVAS));
+  this.canvas_.width = size;
+  this.canvas_.height = size;
 
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context_ = /** @type {CanvasRenderingContext2D} */
+      (this.canvas_.getContext('2d'));
 };
-goog.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
 
 
 /**
- * @param {number} z Z.
- * @return {string} MatrixId..
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.style.AtlasInfo}
  */
-ol.tilegrid.WMTS.prototype.getMatrixId = function(z) {
-  goog.asserts.assert(0 <= z && z < this.matrixIds_.length);
-  return this.matrixIds_[z];
+ol.style.Atlas.prototype.get = function(id) {
+  return /** @type {?ol.style.AtlasInfo} */ (
+      goog.object.get(this.entries_, id, null));
 };
 
 
 /**
- * @return {Array.<string>} MatrixIds.
- * @api
+ * @param {string} id The identifier of the entry to add.
+ * @param {number} width The width.
+ * @param {number} height The height.
+ * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
+ *    Called to render the new image onto an atlas image.
+ * @param {Object=} opt_this Value to use as `this` when executing
+ *    `renderCallback`.
+ * @return {?ol.style.AtlasInfo} The position and atlas image for the entry.
  */
-ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
-  return this.matrixIds_;
+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;
 };
 
 
 /**
- * @param {Object} matrixSet An object representing a matrixSet in the
- *     capabilities document.
- * @return {ol.tilegrid.WMTS} WMTS tileGrid instance.
+ * @private
+ * @param {number} index The index of the block.
+ * @param {ol.style.Atlas.Block} block The block to split.
+ * @param {number} width The width of the entry to insert.
+ * @param {number} height The height of the entry to insert.
  */
-ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet =
-    function(matrixSet) {
-
-  /** @type {!Array.<number>} */
-  var resolutions = [];
-  /** @type {!Array.<string>} */
-  var matrixIds = [];
-  /** @type {!Array.<ol.Coordinate>} */
-  var origins = [];
-  /** @type {!Array.<number>} */
-  var tileSizes = [];
-
-  var supportedCRSPropName = 'supportedCRS';
-  var matrixIdsPropName = 'matrixIds';
-  var identifierPropName = 'identifier';
-  var scaleDenominatorPropName = 'scaleDenominator';
-  var topLeftCornerPropName = 'topLeftCorner';
-  var tileWidthPropName = 'tileWidth';
-  var tileHeightPropName = 'tileHeight';
+ol.style.Atlas.prototype.split_ =
+    function(index, block, width, height) {
+  var deltaWidth = block.width - width;
+  var deltaHeight = block.height - height;
 
-  var projection = ol.proj.get(matrixSet[supportedCRSPropName]);
-  var metersPerUnit = projection.getMetersPerUnit();
+  /** @type {ol.style.Atlas.Block} */
+  var newBlock1;
+  /** @type {ol.style.Atlas.Block} */
+  var newBlock2;
 
-  goog.array.sort(matrixSet[matrixIdsPropName], function(a, b) {
-    return b[scaleDenominatorPropName] - a[scaleDenominatorPropName];
-  });
+  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
+    };
 
-  goog.array.forEach(matrixSet[matrixIdsPropName],
-      function(elt, index, array) {
-        matrixIds.push(elt[identifierPropName]);
-        origins.push(elt[topLeftCornerPropName]);
-        resolutions.push(elt[scaleDenominatorPropName] * 0.28E-3 /
-            metersPerUnit);
-        var tileWidth = elt[tileWidthPropName];
-        var tileHeight = elt[tileHeightPropName];
-        goog.asserts.assert(tileWidth == tileHeight);
-        tileSizes.push(tileWidth);
-      });
+    // 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
+    };
 
-  return new ol.tilegrid.WMTS({
-    origins: origins,
-    resolutions: resolutions,
-    matrixIds: matrixIds,
-    tileSizes: tileSizes
-  });
+    // 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);
+  }
 };
 
-goog.provide('ol.source.WMTS');
-goog.provide('ol.source.WMTSRequestEncoding');
 
-goog.require('goog.array');
-goog.require('goog.asserts');
-goog.require('goog.math');
-goog.require('goog.object');
-goog.require('goog.uri.utils');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.TileUrlFunctionType');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.WMTS');
+/**
+ * Remove the old block and insert new blocks at the same array position.
+ * The new blocks are inserted at the same position, so that splitted
+ * blocks (that are potentially smaller) are filled first.
+ * @private
+ * @param {number} index The index of the block to remove.
+ * @param {ol.style.Atlas.Block} newBlock1 The 1st block to add.
+ * @param {ol.style.Atlas.Block} newBlock2 The 2nd block to add.
+ */
+ol.style.Atlas.prototype.updateBlocks_ =
+    function(index, newBlock1, newBlock2) {
+  var args = [index, 1];
+  if (newBlock1.width > 0 && newBlock1.height > 0) {
+    args.push(newBlock1);
+  }
+  if (newBlock2.width > 0 && newBlock2.height > 0) {
+    args.push(newBlock2);
+  }
+  this.emptyBlocks_.splice.apply(this.emptyBlocks_, args);
+};
 
 
 /**
- * Request encoding. One of 'KVP', 'REST'.
- * @enum {string}
- * @api
+ * @typedef {{x: number, y: number, width: number, height: number}}
  */
-ol.source.WMTSRequestEncoding = {
-  KVP: 'KVP',  // see spec §8
-  REST: 'REST' // see spec §10
-};
+ol.style.Atlas.Block;
+
+goog.provide('ol.style.RegularShape');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('ol.color');
+goog.require('ol.has');
+goog.require('ol.render.canvas');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Image');
+goog.require('ol.style.ImageState');
+goog.require('ol.style.Stroke');
 
 
 
 /**
  * @classdesc
- * Layer source for tile data from WMTS servers.
+ * Set regular shape style for vector features. The resulting shape will be
+ * a regular polygon when `radius` is provided, or a star when `radius1` and
+ * `radius2` are provided.
  *
  * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.WMTSOptions} options WMTS options.
+ * @param {olx.style.RegularShapeOptions} options Options.
+ * @extends {ol.style.Image}
+ * @implements {ol.structs.IHasChecksum}
  * @api
  */
-ol.source.WMTS = function(options) {
-
-  // TODO: add support for TileMatrixLimits
+ol.style.RegularShape = function(options) {
 
-  var version = goog.isDef(options.version) ? options.version : '1.0.0';
-  var format = goog.isDef(options.format) ? options.format : 'image/jpeg';
+  goog.asserts.assert(goog.isDef(options.radius) ||
+      goog.isDef(options.radius1));
 
   /**
    * @private
-   * @type {Object}
+   * @type {Array.<string>}
    */
-  this.dimensions_ = goog.isDef(options.dimensions) ? options.dimensions : {};
+  this.checksums_ = null;
 
   /**
    * @private
-   * @type {string}
+   * @type {HTMLCanvasElement}
    */
-  this.coordKeyPrefix_ = '';
-  this.resetCoordKeyPrefix_();
-
-  // FIXME: should we guess this requestEncoding from options.url(s)
-  //        structure? that would mean KVP only if a template is not provided.
-  var requestEncoding = goog.isDef(options.requestEncoding) ?
-      options.requestEncoding : ol.source.WMTSRequestEncoding.KVP;
+  this.canvas_ = null;
 
-  // FIXME: should we create a default tileGrid?
-  // we could issue a getCapabilities xhr to retrieve missing configuration
-  var tileGrid = options.tileGrid;
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.hitDetectionCanvas_ = null;
 
-  var context = {
-    'Layer': options.layer,
-    'style': options.style,
-    'Style': options.style,
-    'TileMatrixSet': options.matrixSet
-  };
+  /**
+   * @private
+   * @type {ol.style.Fill}
+   */
+  this.fill_ = goog.isDef(options.fill) ? options.fill : null;
 
-  if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
-    goog.object.extend(context, {
-      'Service': 'WMTS',
-      'Request': 'GetTile',
-      'Version': version,
-      'Format': format
-    });
-  }
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.origin_ = [0, 0];
 
-  var dimensions = this.dimensions_;
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.hitDetectionOrigin_ = [0, 0];
 
   /**
-   * @param {string} template Template.
-   * @return {ol.TileUrlFunctionType} Tile URL function.
+   * @private
+   * @type {number}
    */
-  function createFromWMTSTemplate(template) {
+  this.points_ = options.points;
 
-    // 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
+  /**
+   * @private
+   * @type {number}
+   */
+  this.radius_ = /** @type {number} */ (goog.isDef(options.radius) ?
+      options.radius : options.radius1);
 
-    template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ?
-        goog.uri.utils.appendParamsFromMap(template, context) :
-        template.replace(/\{(\w+?)\}/g, function(m, p) {
-          return (p in context) ? context[p] : m;
-        });
+  /**
+   * @private
+   * @type {number}
+   */
+  this.radius2_ =
+      goog.isDef(options.radius2) ? options.radius2 : this.radius_;
 
-    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 (goog.isNull(tileCoord)) {
-            return undefined;
-          } else {
-            var localContext = {
-              'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
-              'TileCol': tileCoord[1],
-              'TileRow': tileCoord[2]
-            };
-            goog.object.extend(localContext, dimensions);
-            var url = template;
-            if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
-              url = goog.uri.utils.appendParamsFromMap(url, localContext);
-            } else {
-              url = url.replace(/\{(\w+?)\}/g, function(m, p) {
-                return localContext[p];
-              });
-            }
-            return url;
-          }
-        });
-  }
+  /**
+   * @private
+   * @type {number}
+   */
+  this.angle_ = goog.isDef(options.angle) ? options.angle : 0;
 
-  var tileUrlFunction = ol.TileUrlFunction.nullTileUrlFunction;
-  var urls = options.urls;
-  if (!goog.isDef(urls) && goog.isDef(options.url)) {
-    urls = ol.TileUrlFunction.expandUrl(options.url);
-  }
-  if (goog.isDef(urls)) {
-    tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions(
-        goog.array.map(urls, createFromWMTSTemplate));
-  }
+  /**
+   * @private
+   * @type {ol.style.Stroke}
+   */
+  this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
 
-  var tmpExtent = ol.extent.createEmpty();
-  var tmpTileCoord = [0, 0, 0];
-  tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
-      /**
-       * @param {ol.TileCoord} tileCoord Tile coordinate.
-       * @param {ol.proj.Projection} projection Projection.
-       * @param {ol.TileCoord=} opt_tileCoord Tile coordinate.
-       * @return {ol.TileCoord} Tile coordinate.
-       */
-      function(tileCoord, projection, opt_tileCoord) {
-        goog.asserts.assert(!goog.isNull(tileGrid));
-        if (tileGrid.getResolutions().length <= tileCoord[0]) {
-          return null;
-        }
-        var x = tileCoord[1];
-        var y = -tileCoord[2] - 1;
-        var tileExtent = tileGrid.getTileCoordExtent(tileCoord, tmpExtent);
-        var extent = projection.getExtent();
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.anchor_ = null;
 
-        if (!goog.isNull(extent) && projection.isGlobal()) {
-          var numCols = Math.ceil(
-              ol.extent.getWidth(extent) /
-              ol.extent.getWidth(tileExtent));
-          x = goog.math.modulo(x, numCols);
-          tmpTileCoord[0] = tileCoord[0];
-          tmpTileCoord[1] = x;
-          tmpTileCoord[2] = tileCoord[2];
-          tileExtent = tileGrid.getTileCoordExtent(tmpTileCoord, tmpExtent);
-        }
-        if (!ol.extent.intersects(tileExtent, extent) ||
-            ol.extent.touches(tileExtent, extent)) {
-          return null;
-        }
-        return ol.tilecoord.createOrUpdate(tileCoord[0], x, y, opt_tileCoord);
-      },
-      tileUrlFunction);
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.size_ = null;
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.imageSize_ = null;
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.hitDetectionImageSize_ = null;
+
+  this.render_(options.atlasManager);
+
+  /**
+   * @type {boolean}
+   */
+  var snapToPixel = goog.isDef(options.snapToPixel) ?
+      options.snapToPixel : true;
 
   goog.base(this, {
-    attributions: options.attributions,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    projection: options.projection,
-    tileGrid: tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    tilePixelRatio: options.tilePixelRatio,
-    tileUrlFunction: tileUrlFunction
+    opacity: 1,
+    rotateWithView: false,
+    rotation: goog.isDef(options.rotation) ? options.rotation : 0,
+    scale: 1,
+    snapToPixel: snapToPixel
   });
 
 };
-goog.inherits(ol.source.WMTS, ol.source.TileImage);
+goog.inherits(ol.style.RegularShape, ol.style.Image);
 
 
 /**
- * Get the dimensions, i.e. those passed to the constructor through the
- * "dimensions" option, and possibly updated using the updateDimensions
- * method.
- * @return {Object} Dimensions.
+ * @inheritDoc
  * @api
  */
-ol.source.WMTS.prototype.getDimensions = function() {
-  return this.dimensions_;
+ol.style.RegularShape.prototype.getAnchor = function() {
+  return this.anchor_;
 };
 
 
 /**
- * @inheritDoc
+ * @return {number} Shape's rotation in radians.
+ * @api
  */
-ol.source.WMTS.prototype.getKeyZXY = function(z, x, y) {
-  return this.coordKeyPrefix_ + goog.base(this, 'getKeyZXY', z, x, y);
+ol.style.RegularShape.prototype.getAngle = function() {
+  return this.angle_;
 };
 
 
 /**
- * @private
+ * @return {ol.style.Fill} Fill style.
+ * @api
  */
-ol.source.WMTS.prototype.resetCoordKeyPrefix_ = function() {
-  var i = 0;
-  var res = [];
-  for (var key in this.dimensions_) {
-    res[i++] = key + '-' + this.dimensions_[key];
-  }
-  this.coordKeyPrefix_ = res.join('/');
+ol.style.RegularShape.prototype.getFill = function() {
+  return this.fill_;
 };
 
 
 /**
- * Update the dimensions.
- * @param {Object} dimensions Dimensions.
- * @api
+ * @inheritDoc
  */
-ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
-  goog.object.extend(this.dimensions_, dimensions);
-  this.resetCoordKeyPrefix_();
-  this.dispatchChangeEvent();
+ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) {
+  return this.hitDetectionCanvas_;
 };
 
 
 /**
- * @param {Object} wmtsCap An object representing the capabilities document.
- * @param {string} layer The layer identifier.
- * @return {olx.source.WMTSOptions} WMTS source options object.
+ * @inheritDoc
+ * @api
  */
-ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, layer) {
+ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
+  return this.canvas_;
+};
 
-  /* jshint -W069 */
 
-  // TODO: add support for TileMatrixLimits
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getImageSize = function() {
+  return this.imageSize_;
+};
 
-  var layers = wmtsCap['contents']['layers'];
-  var l = goog.array.find(layers, function(elt, index, array) {
-    return elt['identifier'] == layer;
-  });
-  goog.asserts.assert(!goog.isNull(l));
-  goog.asserts.assert(l['tileMatrixSetLinks'].length > 0);
-  var matrixSet = /** @type {string} */
-      (l['tileMatrixSetLinks'][0]['tileMatrixSet']);
-  var format = /** @type {string} */ (l['formats'][0]);
-  var idx = goog.array.findIndex(l['styles'], function(elt, index, array) {
-    return elt['isDefault'];
-  });
-  if (idx < 0) {
-    idx = 0;
-  }
-  var style = /** @type {string} */ (l['styles'][idx]['identifier']);
 
-  var dimensions = {};
-  goog.array.forEach(l['dimensions'], function(elt, index, array) {
-    var key = elt['identifier'];
-    var value = elt['default'];
-    if (goog.isDef(value)) {
-      goog.asserts.assert(goog.array.contains(elt['values'], value));
-    } else {
-      value = elt['values'][0];
-    }
-    goog.asserts.assert(goog.isDef(value));
-    dimensions[key] = value;
-  });
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
+  return this.hitDetectionImageSize_;
+};
 
-  var matrixSets = wmtsCap['contents']['tileMatrixSets'];
-  goog.asserts.assert(matrixSet in matrixSets);
-  var matrixSetObj = matrixSets[matrixSet];
 
-  var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet(
-      matrixSetObj);
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getImageState = function() {
+  return ol.style.ImageState.LOADED;
+};
 
-  var projection = ol.proj.get(matrixSetObj['supportedCRS']);
 
-  var gets = wmtsCap['operationsMetadata']['GetTile']['dcp']['http']['get'];
-  var encodings = goog.object.getKeys(
-      gets[0]['constraints']['GetEncoding']['allowedValues']);
-  goog.asserts.assert(encodings.length > 0);
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.RegularShape.prototype.getOrigin = function() {
+  return this.origin_;
+};
 
-  var urls;
-  var requestEncoding;
-  switch (encodings[0]) {
-    case 'REST':
-    case 'RESTful':
-      // The OGC documentation is not clear if we should use REST or RESTful,
-      // ArcGis use RESTful, and OpenLayers use REST.
-      requestEncoding = ol.source.WMTSRequestEncoding.REST;
-      goog.asserts.assert(l['resourceUrls'].hasOwnProperty('tile'));
-      goog.asserts.assert(l['resourceUrls']['tile'].hasOwnProperty(format));
-      urls = /** @type {Array.<string>} */
-          (l['resourceUrls']['tile'][format]);
-      break;
-    case 'KVP':
-      requestEncoding = ol.source.WMTSRequestEncoding.KVP;
-      urls = [];
-      goog.array.forEach(gets, function(elt, index, array) {
-        if (elt['constraints']['GetEncoding']['allowedValues'].hasOwnProperty(
-            ol.source.WMTSRequestEncoding.KVP)) {
-          urls.push(/** @type {string} */ (elt['url']));
-        }
-      });
-      goog.asserts.assert(urls.length > 0);
-      break;
-    default:
-      goog.asserts.fail();
-  }
 
-  return {
-    urls: urls,
-    layer: layer,
-    matrixSet: matrixSet,
-    format: format,
-    projection: projection,
-    requestEncoding: requestEncoding,
-    tileGrid: tileGrid,
-    style: style,
-    dimensions: dimensions
-  };
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.getHitDetectionOrigin = function() {
+  return this.hitDetectionOrigin_;
+};
 
-  /* jshint +W069 */
 
+/**
+ * @return {number} Number of points for stars and regular polygons.
+ * @api
+ */
+ol.style.RegularShape.prototype.getPoints = function() {
+  return this.points_;
 };
 
-goog.provide('ol.tilegrid.Zoomify');
-
-goog.require('goog.math');
-goog.require('ol.TileCoord');
-goog.require('ol.proj');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid.TileGrid');
 
+/**
+ * @return {number} Radius.
+ * @api
+ */
+ol.style.RegularShape.prototype.getRadius = function() {
+  return this.radius_;
+};
 
 
 /**
- * @classdesc
- * Set the grid pattern for sources accessing Zoomify tiled-image servers.
- *
- * @constructor
- * @extends {ol.tilegrid.TileGrid}
- * @param {olx.tilegrid.ZoomifyOptions=} opt_options Options.
+ * @return {number} Radius2.
  * @api
  */
-ol.tilegrid.Zoomify = function(opt_options) {
-  var options = goog.isDef(opt_options) ? opt_options : options;
-  goog.base(this, {
-    origin: [0, 0],
-    resolutions: options.resolutions
-  });
-
+ol.style.RegularShape.prototype.getRadius2 = function() {
+  return this.radius2_;
 };
-goog.inherits(ol.tilegrid.Zoomify, ol.tilegrid.TileGrid);
 
 
 /**
  * @inheritDoc
+ * @api
  */
-ol.tilegrid.Zoomify.prototype.createTileCoordTransform = function(opt_options) {
-  var options = goog.isDef(opt_options) ? opt_options : {};
-  var minZ = this.minZoom;
-  var maxZ = this.maxZoom;
-  /** @type {Array.<ol.TileRange>} */
-  var tileRangeByZ = null;
-  if (goog.isDef(options.extent)) {
-    tileRangeByZ = new Array(maxZ + 1);
-    var z;
-    for (z = 0; z <= maxZ; ++z) {
-      if (z < minZ) {
-        tileRangeByZ[z] = null;
-      } else {
-        tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z);
-      }
-    }
-  }
-  return (
-      /**
-       * @param {ol.TileCoord} tileCoord Tile coordinate.
-       * @param {ol.proj.Projection} projection Projection.
-       * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate.
-       * @return {ol.TileCoord} Tile coordinate.
-       */
-      function(tileCoord, projection, opt_tileCoord) {
-        var z = tileCoord[0];
-        if (z < minZ || maxZ < z) {
-          return null;
-        }
-        var n = Math.pow(2, z);
-        var x = tileCoord[1];
-        if (x < 0 || n <= x) {
-          return null;
-        }
-        var y = tileCoord[2];
-        if (y < -n || -1 < y) {
-          return null;
-        }
-        if (!goog.isNull(tileRangeByZ)) {
-          if (!tileRangeByZ[z].containsXY(x, -y - 1)) {
-            return null;
-          }
-        }
-        return ol.tilecoord.createOrUpdate(z, x, -y - 1, opt_tileCoord);
-      });
+ol.style.RegularShape.prototype.getSize = function() {
+  return this.size_;
 };
 
-goog.provide('ol.source.Zoomify');
 
-goog.require('goog.asserts');
-goog.require('ol');
-goog.require('ol.ImageTile');
-goog.require('ol.TileCoord');
-goog.require('ol.TileState');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.dom');
-goog.require('ol.proj');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilegrid.Zoomify');
+/**
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.RegularShape.prototype.getStroke = function() {
+  return this.stroke_;
+};
 
 
 /**
- * @enum {string}
+ * @inheritDoc
  */
-ol.source.ZoomifyTierSizeCalculation = {
-  DEFAULT: 'default',
-  TRUNCATED: 'truncated'
-};
+ol.style.RegularShape.prototype.listenImageChange = goog.nullFunction;
 
 
+/**
+ * @inheritDoc
+ */
+ol.style.RegularShape.prototype.load = goog.nullFunction;
+
 
 /**
- * @classdesc
- * Layer source for tile data in Zoomify format.
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.ZoomifyOptions=} opt_options Options.
- * @api stable
+ * @inheritDoc
  */
-ol.source.Zoomify = function(opt_options) {
+ol.style.RegularShape.prototype.unlistenImageChange = goog.nullFunction;
 
-  var options = goog.isDef(opt_options) ? opt_options : {};
 
-  var size = options.size;
-  var tierSizeCalculation = goog.isDef(options.tierSizeCalculation) ?
-      options.tierSizeCalculation :
-      ol.source.ZoomifyTierSizeCalculation.DEFAULT;
+/**
+ * @typedef {{strokeStyle: (string|undefined), strokeWidth: number,
+ *   size: number, lineDash: Array.<number>}}
+ */
+ol.style.RegularShape.RenderOptions;
 
-  var imageWidth = size[0];
-  var imageHeight = size[1];
-  var tierSizeInTiles = [];
-  var tileSize = ol.DEFAULT_TILE_SIZE;
 
-  switch (tierSizeCalculation) {
-    case ol.source.ZoomifyTierSizeCalculation.DEFAULT:
-      while (imageWidth > tileSize || imageHeight > tileSize) {
-        tierSizeInTiles.push([
-          Math.ceil(imageWidth / tileSize),
-          Math.ceil(imageHeight / tileSize)
-        ]);
-        tileSize += tileSize;
-      }
-      break;
-    case ol.source.ZoomifyTierSizeCalculation.TRUNCATED:
-      var width = imageWidth;
-      var height = imageHeight;
-      while (width > tileSize || height > tileSize) {
-        tierSizeInTiles.push([
-          Math.ceil(width / tileSize),
-          Math.ceil(height / tileSize)
-        ]);
-        width >>= 1;
-        height >>= 1;
-      }
-      break;
-    default:
-      goog.asserts.fail();
-      break;
+/**
+ * @private
+ * @param {ol.style.AtlasManager|undefined} atlasManager
+ */
+ol.style.RegularShape.prototype.render_ = function(atlasManager) {
+  var imageSize;
+  var lineDash = null;
+  var strokeStyle;
+  var strokeWidth = 0;
+
+  if (!goog.isNull(this.stroke_)) {
+    strokeStyle = ol.color.asString(this.stroke_.getColor());
+    strokeWidth = this.stroke_.getWidth();
+    if (!goog.isDef(strokeWidth)) {
+      strokeWidth = ol.render.canvas.defaultLineWidth;
+    }
+    lineDash = this.stroke_.getLineDash();
+    if (!ol.has.CANVAS_LINE_DASH) {
+      lineDash = null;
+    }
   }
 
-  tierSizeInTiles.push([1, 1]);
-  tierSizeInTiles.reverse();
+  var size = 2 * (this.radius_ + strokeWidth) + 1;
 
-  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]
-    );
+  /** @type {ol.style.RegularShape.RenderOptions} */
+  var renderOptions = {
+    strokeStyle: strokeStyle,
+    strokeWidth: strokeWidth,
+    size: size,
+    lineDash: lineDash
+  };
+
+  if (!goog.isDef(atlasManager)) {
+    // no atlas manager is used, create a new canvas
+    this.canvas_ = /** @type {HTMLCanvasElement} */
+        (goog.dom.createElement(goog.dom.TagName.CANVAS));
+
+    this.canvas_.height = size;
+    this.canvas_.width = size;
+
+    // canvas.width and height are rounded to the closest integer
+    size = this.canvas_.width;
+    imageSize = size;
+
+    var context = /** @type {CanvasRenderingContext2D} */
+        (this.canvas_.getContext('2d'));
+    this.draw_(renderOptions, context, 0, 0);
+
+    this.createHitDetectionCanvas_(renderOptions);
+  } else {
+    // an atlas manager is used, add the symbol to an atlas
+    size = Math.round(size);
+
+    var hasCustomHitDetectionImage = goog.isNull(this.fill_);
+    var renderHitDetectionCallback;
+    if (hasCustomHitDetectionImage) {
+      // render the hit-detection image into a separate atlas image
+      renderHitDetectionCallback =
+          goog.bind(this.drawHitDetectionCanvas_, this, renderOptions);
+    }
+
+    var id = this.getChecksum();
+    var info = atlasManager.add(
+        id, size, size, goog.bind(this.draw_, this, renderOptions),
+        renderHitDetectionCallback);
+    goog.asserts.assert(!goog.isNull(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.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
+      this.hitDetectionImageSize_ =
+          [info.hitImage.width, info.hitImage.height];
+    } else {
+      this.hitDetectionCanvas_ = this.canvas_;
+      this.hitDetectionOrigin_ = this.origin_;
+      this.hitDetectionImageSize_ = [imageSize, imageSize];
+    }
   }
-  resolutions.reverse();
 
-  var tileGrid = new ol.tilegrid.Zoomify({
-    resolutions: resolutions
-  });
+  this.anchor_ = [size / 2, size / 2];
+  this.size_ = [size, size];
+  this.imageSize_ = [imageSize, imageSize];
+};
 
-  var url = options.url;
-  var tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
-      tileGrid.createTileCoordTransform({extent: [0, 0, size[0], size[1]]}),
-      /**
-       * @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(tileCoord, pixelRatio, projection) {
-        if (goog.isNull(tileCoord)) {
-          return undefined;
-        } else {
-          var tileCoordZ = tileCoord[0];
-          var tileCoordX = tileCoord[1];
-          var tileCoordY = tileCoord[2];
-          var tileIndex =
-              tileCoordX +
-              tileCoordY * tierSizeInTiles[tileCoordZ][0] +
-              tileCountUpToTier[tileCoordZ];
-          var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0;
-          return url + 'TileGroup' + tileGroup + '/' +
-              tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg';
-        }
-      });
 
-  goog.base(this, {
-    attributions: options.attributions,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    tileClass: ol.source.ZoomifyTile_,
-    tileGrid: tileGrid,
-    tileUrlFunction: tileUrlFunction
-  });
+/**
+ * @private
+ * @param {ol.style.Circle.RenderOptions} renderOptions
+ * @param {CanvasRenderingContext2D} context
+ * @param {number} x The origin for the symbol (x).
+ * @param {number} y The origin for the symbol (y).
+ */
+ol.style.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 (!goog.isNull(this.fill_)) {
+    context.fillStyle = ol.color.asString(this.fill_.getColor());
+    context.fill();
+  }
+  if (!goog.isNull(this.stroke_)) {
+    context.strokeStyle = renderOptions.strokeStyle;
+    context.lineWidth = renderOptions.strokeWidth;
+    if (!goog.isNull(renderOptions.lineDash)) {
+      context.setLineDash(renderOptions.lineDash);
+    }
+    context.stroke();
+  }
+  context.closePath();
 };
-goog.inherits(ol.source.Zoomify, ol.source.TileImage);
 
 
+/**
+ * @private
+ * @param {ol.style.RegularShape.RenderOptions} renderOptions
+ */
+ol.style.RegularShape.prototype.createHitDetectionCanvas_ =
+    function(renderOptions) {
+  this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
+  if (!goog.isNull(this.fill_)) {
+    this.hitDetectionCanvas_ = this.canvas_;
+    return;
+  }
+
+  // if no fill style is set, create an extra hit-detection image with a
+  // default fill style
+  this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
+      (goog.dom.createElement(goog.dom.TagName.CANVAS));
+  var canvas = this.hitDetectionCanvas_;
+
+  canvas.height = renderOptions.size;
+  canvas.width = renderOptions.size;
+
+  var context = /** @type {CanvasRenderingContext2D} */
+      (canvas.getContext('2d'));
+  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
+};
+
 
 /**
- * @constructor
- * @extends {ol.ImageTile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.TileState} state State.
- * @param {string} src Image source URI.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
  * @private
+ * @param {ol.style.RegularShape.RenderOptions} renderOptions
+ * @param {CanvasRenderingContext2D} context
+ * @param {number} x The origin for the symbol (x).
+ * @param {number} y The origin for the symbol (y).
  */
-ol.source.ZoomifyTile_ = function(
-    tileCoord, state, src, crossOrigin, tileLoadFunction) {
+ol.style.RegularShape.prototype.drawHitDetectionCanvas_ =
+    function(renderOptions, context, x, y) {
+  // reset transform
+  context.setTransform(1, 0, 0, 1, 0, 0);
 
-  goog.base(this, tileCoord, state, src, crossOrigin, tileLoadFunction);
+  // then move to (x, y)
+  context.translate(x, y);
 
-  /**
-   * @private
-   * @type {Object.<string,
-   *                HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
-   */
-  this.zoomifyImageByContext_ = {};
+  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 (!goog.isNull(this.stroke_)) {
+    context.strokeStyle = renderOptions.strokeStyle;
+    context.lineWidth = renderOptions.strokeWidth;
+    if (!goog.isNull(renderOptions.lineDash)) {
+      context.setLineDash(renderOptions.lineDash);
+    }
+    context.stroke();
+  }
+  context.closePath();
 };
-goog.inherits(ol.source.ZoomifyTile_, ol.ImageTile);
 
 
 /**
  * @inheritDoc
  */
-ol.source.ZoomifyTile_.prototype.getImage = function(opt_context) {
-  var tileSize = ol.DEFAULT_TILE_SIZE;
-  var key = goog.isDef(opt_context) ? goog.getUid(opt_context).toString() : '';
-  if (key in this.zoomifyImageByContext_) {
-    return this.zoomifyImageByContext_[key];
-  } else {
-    var image = goog.base(this, 'getImage', opt_context);
-    if (this.state == ol.TileState.LOADED) {
-      if (image.width == tileSize && image.height == tileSize) {
-        this.zoomifyImageByContext_[key] = image;
-        return image;
-      } else {
-        var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
-        context.drawImage(image, 0, 0);
-        this.zoomifyImageByContext_[key] = context.canvas;
-        return context.canvas;
-      }
-    } else {
-      return image;
-    }
+ol.style.RegularShape.prototype.getChecksum = function() {
+  var strokeChecksum = !goog.isNull(this.stroke_) ?
+      this.stroke_.getChecksum() : '-';
+  var fillChecksum = !goog.isNull(this.fill_) ?
+      this.fill_.getChecksum() : '-';
+
+  var recalculate = goog.isNull(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 +
+        (goog.isDef(this.radius_) ? this.radius_.toString() : '-') +
+        (goog.isDef(this.radius2_) ? this.radius2_.toString() : '-') +
+        (goog.isDef(this.angle_) ? this.angle_.toString() : '-') +
+        (goog.isDef(this.points_) ? 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.
@@ -99378,1350 +112585,10 @@ goog.addDependency('demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'
 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']);
 
-// Copyright 2014 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// This file has been auto-generated by GenJsDeps, please do not edit.
-
-goog.addDependency('../../third_party/closure/goog/caja/string/html/htmlparser.js', ['goog.string.html.HtmlParser', 'goog.string.html.HtmlParser.EFlags', 'goog.string.html.HtmlParser.Elements', 'goog.string.html.HtmlParser.Entities', 'goog.string.html.HtmlSaxHandler'], []);
-goog.addDependency('../../third_party/closure/goog/caja/string/html/htmlsanitizer.js', ['goog.string.html.HtmlSanitizer', 'goog.string.html.HtmlSanitizer.AttributeType', 'goog.string.html.HtmlSanitizer.Attributes', 'goog.string.html.htmlSanitize'], ['goog.string.StringBuffer', 'goog.string.html.HtmlParser', 'goog.string.html.HtmlParser.EFlags', 'goog.string.html.HtmlParser.Elements', 'goog.string.html.HtmlSaxHandler']);
-goog.addDependency('../../third_party/closure/goog/dojo/dom/query.js', ['goog.dom.query'], ['goog.array', 'goog.dom', 'goog.functions', 'goog.string', 'goog.userAgent']);
-goog.addDependency('../../third_party/closure/goog/jpeg_encoder/jpeg_encoder_basic.js', ['goog.crypt.JpegEncoder'], ['goog.crypt.base64']);
-goog.addDependency('../../third_party/closure/goog/loremipsum/text/loremipsum.js', ['goog.text.LoremIpsum'], ['goog.array', 'goog.math', 'goog.string', 'goog.structs.Map', 'goog.structs.Set']);
-goog.addDependency('../../third_party/closure/goog/mochikit/async/deferred.js', ['goog.async.Deferred', 'goog.async.Deferred.AlreadyCalledError', 'goog.async.Deferred.CanceledError'], ['goog.Promise', 'goog.Thenable', 'goog.array', 'goog.asserts', 'goog.debug.Error']);
-goog.addDependency('../../third_party/closure/goog/mochikit/async/deferredlist.js', ['goog.async.DeferredList'], ['goog.async.Deferred']);
-goog.addDependency('../../third_party/closure/goog/osapi/osapi.js', ['goog.osapi'], []);
-goog.addDependency('../../third_party/closure/goog/svgpan/svgpan.js', ['svgpan.SvgPan'], ['goog.Disposable', 'goog.events', 'goog.events.EventType', 'goog.events.MouseWheelHandler']);
-goog.addDependency('a11y/aria/announcer.js', ['goog.a11y.aria.Announcer'], ['goog.Disposable', 'goog.a11y.aria', 'goog.a11y.aria.LivePriority', 'goog.a11y.aria.State', 'goog.dom', 'goog.object']);
-goog.addDependency('a11y/aria/announcer_test.js', ['goog.a11y.aria.AnnouncerTest'], ['goog.a11y.aria', 'goog.a11y.aria.Announcer', 'goog.a11y.aria.LivePriority', 'goog.a11y.aria.State', 'goog.array', 'goog.dom', 'goog.dom.iframe', 'goog.testing.jsunit']);
-goog.addDependency('a11y/aria/aria.js', ['goog.a11y.aria'], ['goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.a11y.aria.datatables', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.object']);
-goog.addDependency('a11y/aria/aria_test.js', ['goog.a11y.ariaTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit']);
-goog.addDependency('a11y/aria/attributes.js', ['goog.a11y.aria.AutoCompleteValues', 'goog.a11y.aria.CheckedValues', 'goog.a11y.aria.DropEffectValues', 'goog.a11y.aria.ExpandedValues', 'goog.a11y.aria.GrabbedValues', 'goog.a11y.aria.InvalidValues', 'goog.a11y.aria.LivePriority', 'goog.a11y.aria.OrientationValues', 'goog.a11y.aria.PressedValues', 'goog.a11y.aria.RelevantValues', 'goog.a11y.aria.SelectedValues', 'goog.a11y.aria.SortValues', 'goog.a11y.aria.State'], []);
-goog.addDependency('a11y/aria/datatables.js', ['goog.a11y.aria.datatables'], ['goog.a11y.aria.State', 'goog.object']);
-goog.addDependency('a11y/aria/roles.js', ['goog.a11y.aria.Role'], []);
-goog.addDependency('array/array.js', ['goog.array', 'goog.array.ArrayLike'], ['goog.asserts']);
-goog.addDependency('array/array_test.js', ['goog.arrayTest'], ['goog.array', 'goog.dom', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('asserts/asserts.js', ['goog.asserts', 'goog.asserts.AssertionError'], ['goog.debug.Error', 'goog.dom.NodeType', 'goog.string']);
-goog.addDependency('asserts/asserts_test.js', ['goog.assertsTest'], ['goog.asserts', 'goog.asserts.AssertionError', 'goog.dom', 'goog.string', 'goog.testing.jsunit']);
-goog.addDependency('async/animationdelay.js', ['goog.async.AnimationDelay'], ['goog.Disposable', 'goog.events', 'goog.functions']);
-goog.addDependency('async/animationdelay_test.js', ['goog.async.AnimationDelayTest'], ['goog.async.AnimationDelay', 'goog.testing.AsyncTestCase', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('async/conditionaldelay.js', ['goog.async.ConditionalDelay'], ['goog.Disposable', 'goog.async.Delay']);
-goog.addDependency('async/conditionaldelay_test.js', ['goog.async.ConditionalDelayTest'], ['goog.async.ConditionalDelay', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('async/delay.js', ['goog.Delay', 'goog.async.Delay'], ['goog.Disposable', 'goog.Timer']);
-goog.addDependency('async/delay_test.js', ['goog.async.DelayTest'], ['goog.async.Delay', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('async/nexttick.js', ['goog.async.nextTick', 'goog.async.throwException'], ['goog.debug.entryPointRegistry', 'goog.functions']);
-goog.addDependency('async/nexttick_test.js', ['goog.async.nextTickTest'], ['goog.async.nextTick', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.testing.AsyncTestCase', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('async/run.js', ['goog.async.run'], ['goog.async.nextTick', 'goog.async.throwException', 'goog.testing.watchers']);
-goog.addDependency('async/run_test.js', ['goog.async.runTest'], ['goog.async.run', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('async/throttle.js', ['goog.Throttle', 'goog.async.Throttle'], ['goog.Disposable', 'goog.Timer']);
-goog.addDependency('async/throttle_test.js', ['goog.async.ThrottleTest'], ['goog.async.Throttle', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('base.js', ['goog'], []);
-goog.addDependency('base_test.js', ['an.existing.path', 'dup.base', 'far.out', 'goog.baseTest', 'goog.explicit', 'goog.implicit.explicit', 'goog.test', 'goog.test.name', 'goog.test.name.space', 'goog.xy', 'goog.xy.z', 'ns', 'testDep.bar'], ['goog.Timer', 'goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('color/alpha.js', ['goog.color.alpha'], ['goog.color']);
-goog.addDependency('color/alpha_test.js', ['goog.color.alphaTest'], ['goog.array', 'goog.color', 'goog.color.alpha', 'goog.testing.jsunit']);
-goog.addDependency('color/color.js', ['goog.color', 'goog.color.Hsl', 'goog.color.Hsv', 'goog.color.Rgb'], ['goog.color.names', 'goog.math']);
-goog.addDependency('color/color_test.js', ['goog.colorTest'], ['goog.array', 'goog.color', 'goog.color.names', 'goog.testing.jsunit']);
-goog.addDependency('color/names.js', ['goog.color.names'], []);
-goog.addDependency('crypt/aes.js', ['goog.crypt.Aes'], ['goog.asserts', 'goog.crypt.BlockCipher']);
-goog.addDependency('crypt/aes_test.js', ['goog.crypt.AesTest'], ['goog.crypt', 'goog.crypt.Aes', 'goog.testing.jsunit']);
-goog.addDependency('crypt/arc4.js', ['goog.crypt.Arc4'], ['goog.asserts']);
-goog.addDependency('crypt/arc4_test.js', ['goog.crypt.Arc4Test'], ['goog.array', 'goog.crypt.Arc4', 'goog.testing.jsunit']);
-goog.addDependency('crypt/base64.js', ['goog.crypt.base64'], ['goog.crypt', 'goog.userAgent']);
-goog.addDependency('crypt/base64_test.js', ['goog.crypt.base64Test'], ['goog.crypt', 'goog.crypt.base64', 'goog.testing.jsunit']);
-goog.addDependency('crypt/basen.js', ['goog.crypt.baseN'], []);
-goog.addDependency('crypt/basen_test.js', ['goog.crypt.baseNTest'], ['goog.crypt.baseN', 'goog.testing.jsunit']);
-goog.addDependency('crypt/blobhasher.js', ['goog.crypt.BlobHasher', 'goog.crypt.BlobHasher.EventType'], ['goog.asserts', 'goog.crypt', 'goog.crypt.Hash', 'goog.events.EventTarget', 'goog.fs', 'goog.log']);
-goog.addDependency('crypt/blobhasher_test.js', ['goog.crypt.BlobHasherTest'], ['goog.crypt', 'goog.crypt.BlobHasher', 'goog.crypt.Md5', 'goog.events', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('crypt/blockcipher.js', ['goog.crypt.BlockCipher'], []);
-goog.addDependency('crypt/bytestring_perf.js', ['goog.crypt.byteArrayToStringPerf'], ['goog.array', 'goog.dom', 'goog.testing.PerformanceTable']);
-goog.addDependency('crypt/cbc.js', ['goog.crypt.Cbc'], ['goog.array', 'goog.asserts', 'goog.crypt']);
-goog.addDependency('crypt/cbc_test.js', ['goog.crypt.CbcTest'], ['goog.crypt', 'goog.crypt.Aes', 'goog.crypt.Cbc', 'goog.testing.jsunit']);
-goog.addDependency('crypt/crypt.js', ['goog.crypt'], ['goog.array', 'goog.asserts']);
-goog.addDependency('crypt/crypt_test.js', ['goog.cryptTest'], ['goog.crypt', 'goog.string', 'goog.testing.jsunit']);
-goog.addDependency('crypt/hash.js', ['goog.crypt.Hash'], []);
-goog.addDependency('crypt/hash32.js', ['goog.crypt.hash32'], ['goog.crypt']);
-goog.addDependency('crypt/hash32_test.js', ['goog.crypt.hash32Test'], ['goog.crypt.hash32', 'goog.testing.TestCase', 'goog.testing.jsunit']);
-goog.addDependency('crypt/hashtester.js', ['goog.crypt.hashTester'], ['goog.array', 'goog.crypt', 'goog.testing.PerformanceTable', 'goog.testing.PseudoRandom', 'goog.testing.asserts']);
-goog.addDependency('crypt/hmac.js', ['goog.crypt.Hmac'], ['goog.crypt.Hash']);
-goog.addDependency('crypt/hmac_test.js', ['goog.crypt.HmacTest'], ['goog.crypt.Hmac', 'goog.crypt.Sha1', 'goog.crypt.hashTester', 'goog.testing.jsunit']);
-goog.addDependency('crypt/md5.js', ['goog.crypt.Md5'], ['goog.crypt.Hash']);
-goog.addDependency('crypt/md5_test.js', ['goog.crypt.Md5Test'], ['goog.crypt', 'goog.crypt.Md5', 'goog.crypt.hashTester', 'goog.testing.jsunit']);
-goog.addDependency('crypt/pbkdf2.js', ['goog.crypt.pbkdf2'], ['goog.asserts', 'goog.crypt', 'goog.crypt.Hmac', 'goog.crypt.Sha1']);
-goog.addDependency('crypt/pbkdf2_test.js', ['goog.crypt.pbkdf2Test'], ['goog.crypt', 'goog.crypt.pbkdf2', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('crypt/sha1.js', ['goog.crypt.Sha1'], ['goog.crypt.Hash']);
-goog.addDependency('crypt/sha1_test.js', ['goog.crypt.Sha1Test'], ['goog.crypt', 'goog.crypt.Sha1', 'goog.crypt.hashTester', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('crypt/sha2.js', ['goog.crypt.Sha2'], ['goog.array', 'goog.asserts', 'goog.crypt.Hash']);
-goog.addDependency('crypt/sha224.js', ['goog.crypt.Sha224'], ['goog.crypt.Sha2']);
-goog.addDependency('crypt/sha224_test.js', ['goog.crypt.Sha224Test'], ['goog.crypt', 'goog.crypt.Sha224', 'goog.crypt.hashTester', 'goog.testing.jsunit']);
-goog.addDependency('crypt/sha256.js', ['goog.crypt.Sha256'], ['goog.crypt.Sha2']);
-goog.addDependency('crypt/sha256_test.js', ['goog.crypt.Sha256Test'], ['goog.crypt', 'goog.crypt.Sha256', 'goog.crypt.hashTester', 'goog.testing.jsunit']);
-goog.addDependency('crypt/sha2_64bit.js', ['goog.crypt.Sha2_64bit'], ['goog.array', 'goog.asserts', 'goog.crypt.Hash', 'goog.math.Long']);
-goog.addDependency('crypt/sha2_64bit_test.js', ['goog.crypt.Sha2_64bit_test'], ['goog.array', 'goog.crypt', 'goog.crypt.Sha384', 'goog.crypt.Sha512', 'goog.crypt.Sha512_256', 'goog.crypt.hashTester', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('crypt/sha384.js', ['goog.crypt.Sha384'], ['goog.crypt.Sha2_64bit']);
-goog.addDependency('crypt/sha512.js', ['goog.crypt.Sha512'], ['goog.crypt.Sha2_64bit']);
-goog.addDependency('crypt/sha512_256.js', ['goog.crypt.Sha512_256'], ['goog.crypt.Sha2_64bit']);
-goog.addDependency('cssom/cssom.js', ['goog.cssom', 'goog.cssom.CssRuleType'], ['goog.array', 'goog.dom']);
-goog.addDependency('cssom/cssom_test.js', ['goog.cssomTest'], ['goog.array', 'goog.cssom', 'goog.cssom.CssRuleType', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('cssom/iframe/style.js', ['goog.cssom.iframe.style'], ['goog.asserts', 'goog.cssom', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.string', 'goog.style', 'goog.userAgent']);
-goog.addDependency('cssom/iframe/style_test.js', ['goog.cssom.iframe.styleTest'], ['goog.cssom', 'goog.cssom.iframe.style', 'goog.dom', 'goog.dom.DomHelper', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('datasource/datamanager.js', ['goog.ds.DataManager'], ['goog.ds.BasicNodeList', 'goog.ds.DataNode', 'goog.ds.Expr', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.Map']);
-goog.addDependency('datasource/datasource.js', ['goog.ds.BaseDataNode', 'goog.ds.BasicNodeList', 'goog.ds.DataNode', 'goog.ds.DataNodeList', 'goog.ds.EmptyNodeList', 'goog.ds.LoadState', 'goog.ds.SortedNodeList', 'goog.ds.Util', 'goog.ds.logger'], ['goog.array', 'goog.log']);
-goog.addDependency('datasource/datasource_test.js', ['goog.ds.JsDataSourceTest'], ['goog.dom.xml', 'goog.ds.DataManager', 'goog.ds.JsDataSource', 'goog.ds.SortedNodeList', 'goog.ds.XmlDataSource', 'goog.testing.jsunit']);
-goog.addDependency('datasource/expr.js', ['goog.ds.Expr'], ['goog.ds.BasicNodeList', 'goog.ds.EmptyNodeList', 'goog.string']);
-goog.addDependency('datasource/expr_test.js', ['goog.ds.ExprTest'], ['goog.ds.DataManager', 'goog.ds.Expr', 'goog.ds.JsDataSource', 'goog.testing.jsunit']);
-goog.addDependency('datasource/fastdatanode.js', ['goog.ds.AbstractFastDataNode', 'goog.ds.FastDataNode', 'goog.ds.FastListNode', 'goog.ds.PrimitiveFastDataNode'], ['goog.ds.DataManager', 'goog.ds.EmptyNodeList', 'goog.string']);
-goog.addDependency('datasource/fastdatanode_test.js', ['goog.ds.FastDataNodeTest'], ['goog.array', 'goog.ds.DataManager', 'goog.ds.Expr', 'goog.ds.FastDataNode', 'goog.testing.jsunit']);
-goog.addDependency('datasource/jsdatasource.js', ['goog.ds.JsDataSource', 'goog.ds.JsPropertyDataSource'], ['goog.ds.BaseDataNode', 'goog.ds.BasicNodeList', 'goog.ds.DataManager', 'goog.ds.EmptyNodeList', 'goog.ds.LoadState']);
-goog.addDependency('datasource/jsondatasource.js', ['goog.ds.JsonDataSource'], ['goog.Uri', 'goog.dom', 'goog.ds.DataManager', 'goog.ds.JsDataSource', 'goog.ds.LoadState', 'goog.ds.logger']);
-goog.addDependency('datasource/jsxmlhttpdatasource.js', ['goog.ds.JsXmlHttpDataSource'], ['goog.Uri', 'goog.ds.DataManager', 'goog.ds.FastDataNode', 'goog.ds.LoadState', 'goog.ds.logger', 'goog.events', 'goog.log', 'goog.net.EventType', 'goog.net.XhrIo']);
-goog.addDependency('datasource/jsxmlhttpdatasource_test.js', ['goog.ds.JsXmlHttpDataSourceTest'], ['goog.ds.JsXmlHttpDataSource', 'goog.testing.TestQueue', 'goog.testing.jsunit', 'goog.testing.net.XhrIo']);
-goog.addDependency('datasource/xmldatasource.js', ['goog.ds.XmlDataSource', 'goog.ds.XmlHttpDataSource'], ['goog.Uri', 'goog.dom.NodeType', 'goog.dom.xml', 'goog.ds.BasicNodeList', 'goog.ds.DataManager', 'goog.ds.LoadState', 'goog.ds.logger', 'goog.net.XhrIo', 'goog.string']);
-goog.addDependency('date/date.js', ['goog.date', 'goog.date.Date', 'goog.date.DateTime', 'goog.date.Interval', 'goog.date.month', 'goog.date.weekDay'], ['goog.asserts', 'goog.date.DateLike', 'goog.i18n.DateTimeSymbols', 'goog.string']);
-goog.addDependency('date/date_test.js', ['goog.dateTest'], ['goog.array', 'goog.date', 'goog.date.Date', 'goog.date.DateTime', 'goog.date.Interval', 'goog.date.month', 'goog.date.weekDay', 'goog.i18n.DateTimeSymbols', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.platform', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('date/datelike.js', ['goog.date.DateLike'], []);
-goog.addDependency('date/daterange.js', ['goog.date.DateRange', 'goog.date.DateRange.Iterator', 'goog.date.DateRange.StandardDateRangeKeys'], ['goog.date.Date', 'goog.date.Interval', 'goog.iter.Iterator', 'goog.iter.StopIteration']);
-goog.addDependency('date/daterange_test.js', ['goog.date.DateRangeTest'], ['goog.date.Date', 'goog.date.DateRange', 'goog.date.Interval', 'goog.i18n.DateTimeSymbols', 'goog.testing.jsunit']);
-goog.addDependency('date/duration.js', ['goog.date.duration'], ['goog.i18n.DateTimeFormat', 'goog.i18n.MessageFormat']);
-goog.addDependency('date/duration_test.js', ['goog.date.durationTest'], ['goog.date.duration', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_bn', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_fa', 'goog.testing.jsunit']);
-goog.addDependency('date/relative.js', ['goog.date.relative', 'goog.date.relative.TimeDeltaFormatter', 'goog.date.relative.Unit'], ['goog.i18n.DateTimeFormat']);
-goog.addDependency('date/relative_test.js', ['goog.date.relativeTest'], ['goog.date.DateTime', 'goog.date.relative', 'goog.i18n.DateTimeFormat', 'goog.testing.jsunit']);
-goog.addDependency('date/relativewithplurals.js', ['goog.date.relativeWithPlurals'], ['goog.date.relative', 'goog.date.relative.Unit', 'goog.i18n.MessageFormat']);
-goog.addDependency('date/relativewithplurals_test.js', ['goog.date.relativeWithPluralsTest'], ['goog.date.relative', 'goog.date.relativeTest', 'goog.date.relativeWithPlurals', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_bn', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_bn', 'goog.i18n.NumberFormatSymbols_en', 'goog.i18n.NumberFormatSymbols_fa']);
-goog.addDependency('date/utcdatetime.js', ['goog.date.UtcDateTime'], ['goog.date', 'goog.date.Date', 'goog.date.DateTime', 'goog.date.Interval']);
-goog.addDependency('date/utcdatetime_test.js', ['goog.date.UtcDateTimeTest'], ['goog.date.Interval', 'goog.date.UtcDateTime', 'goog.date.month', 'goog.date.weekDay', 'goog.testing.jsunit']);
-goog.addDependency('db/cursor.js', ['goog.db.Cursor'], ['goog.async.Deferred', 'goog.db.Error', 'goog.debug', 'goog.events.EventTarget']);
-goog.addDependency('db/db.js', ['goog.db'], ['goog.async.Deferred', 'goog.db.Error', 'goog.db.IndexedDb', 'goog.db.Transaction']);
-goog.addDependency('db/db_test.js', ['goog.dbTest'], ['goog.Disposable', 'goog.array', 'goog.async.Deferred', 'goog.async.DeferredList', 'goog.db', 'goog.db.Cursor', 'goog.db.Error', 'goog.db.IndexedDb', 'goog.db.KeyRange', 'goog.db.Transaction', 'goog.events', 'goog.object', 'goog.testing.AsyncTestCase', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('db/error.js', ['goog.db.Error', 'goog.db.Error.ErrorCode', 'goog.db.Error.ErrorName', 'goog.db.Error.VersionChangeBlockedError'], ['goog.debug.Error']);
-goog.addDependency('db/index.js', ['goog.db.Index'], ['goog.async.Deferred', 'goog.db.Cursor', 'goog.db.Error', 'goog.debug']);
-goog.addDependency('db/indexeddb.js', ['goog.db.IndexedDb'], ['goog.async.Deferred', 'goog.db.Error', 'goog.db.ObjectStore', 'goog.db.Transaction', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget']);
-goog.addDependency('db/keyrange.js', ['goog.db.KeyRange'], []);
-goog.addDependency('db/objectstore.js', ['goog.db.ObjectStore'], ['goog.async.Deferred', 'goog.db.Cursor', 'goog.db.Error', 'goog.db.Index', 'goog.debug', 'goog.events']);
-goog.addDependency('db/old_db_test.js', ['goog.oldDbTest'], ['goog.async.Deferred', 'goog.db', 'goog.db.Cursor', 'goog.db.Error', 'goog.db.IndexedDb', 'goog.db.KeyRange', 'goog.db.Transaction', 'goog.events', 'goog.testing.AsyncTestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('db/transaction.js', ['goog.db.Transaction', 'goog.db.Transaction.TransactionMode'], ['goog.async.Deferred', 'goog.db.Error', 'goog.db.ObjectStore', 'goog.events.EventHandler', 'goog.events.EventTarget']);
-goog.addDependency('debug/console.js', ['goog.debug.Console'], ['goog.debug.LogManager', 'goog.debug.Logger.Level', 'goog.debug.TextFormatter']);
-goog.addDependency('debug/console_test.js', ['goog.debug.ConsoleTest'], ['goog.debug.Console', 'goog.debug.LogRecord', 'goog.debug.Logger', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('debug/debug.js', ['goog.debug'], ['goog.array', 'goog.string', 'goog.structs.Set', 'goog.userAgent']);
-goog.addDependency('debug/debug_test.js', ['goog.debugTest'], ['goog.debug', 'goog.structs.Set', 'goog.testing.jsunit']);
-goog.addDependency('debug/debugwindow.js', ['goog.debug.DebugWindow'], ['goog.debug.HtmlFormatter', 'goog.debug.LogManager', 'goog.debug.Logger', 'goog.structs.CircularBuffer', 'goog.userAgent']);
-goog.addDependency('debug/debugwindow_test.js', ['goog.debug.DebugWindowTest'], ['goog.debug.DebugWindow', 'goog.testing.jsunit']);
-goog.addDependency('debug/devcss/devcss.js', ['goog.debug.DevCss', 'goog.debug.DevCss.UserAgent'], ['goog.asserts', 'goog.cssom', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.string', 'goog.userAgent']);
-goog.addDependency('debug/devcss/devcss_test.js', ['goog.debug.DevCssTest'], ['goog.debug.DevCss', 'goog.style', 'goog.testing.jsunit']);
-goog.addDependency('debug/devcss/devcssrunner.js', ['goog.debug.devCssRunner'], ['goog.debug.DevCss']);
-goog.addDependency('debug/divconsole.js', ['goog.debug.DivConsole'], ['goog.debug.HtmlFormatter', 'goog.debug.LogManager', 'goog.style']);
-goog.addDependency('debug/enhanceerror_test.js', ['goog.debugEnhanceErrorTest'], ['goog.debug', 'goog.testing.jsunit']);
-goog.addDependency('debug/entrypointregistry.js', ['goog.debug.EntryPointMonitor', 'goog.debug.entryPointRegistry'], ['goog.asserts']);
-goog.addDependency('debug/entrypointregistry_test.js', ['goog.debug.entryPointRegistryTest'], ['goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.testing.jsunit']);
-goog.addDependency('debug/error.js', ['goog.debug.Error'], []);
-goog.addDependency('debug/error_test.js', ['goog.debug.ErrorTest'], ['goog.debug.Error', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']);
-goog.addDependency('debug/errorhandler.js', ['goog.debug.ErrorHandler', 'goog.debug.ErrorHandler.ProtectedFunctionError'], ['goog.asserts', 'goog.debug', 'goog.debug.EntryPointMonitor', 'goog.debug.Trace']);
-goog.addDependency('debug/errorhandler_async_test.js', ['goog.debug.ErrorHandlerAsyncTest'], ['goog.debug.ErrorHandler', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('debug/errorhandler_test.js', ['goog.debug.ErrorHandlerTest'], ['goog.debug.ErrorHandler', 'goog.testing.MockControl', 'goog.testing.jsunit']);
-goog.addDependency('debug/errorhandlerweakdep.js', ['goog.debug.errorHandlerWeakDep'], []);
-goog.addDependency('debug/errorreporter.js', ['goog.debug.ErrorReporter', 'goog.debug.ErrorReporter.ExceptionEvent'], ['goog.asserts', 'goog.debug', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.log', 'goog.net.XhrIo', 'goog.object', 'goog.string', 'goog.uri.utils', 'goog.userAgent']);
-goog.addDependency('debug/errorreporter_test.js', ['goog.debug.ErrorReporterTest'], ['goog.debug.ErrorReporter', 'goog.events', 'goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('debug/fancywindow.js', ['goog.debug.FancyWindow'], ['goog.array', 'goog.debug.DebugWindow', 'goog.debug.LogManager', 'goog.debug.Logger', 'goog.dom.DomHelper', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.object', 'goog.string', 'goog.userAgent']);
-goog.addDependency('debug/formatter.js', ['goog.debug.Formatter', 'goog.debug.HtmlFormatter', 'goog.debug.TextFormatter'], ['goog.debug.RelativeTimeProvider', 'goog.string']);
-goog.addDependency('debug/fpsdisplay.js', ['goog.debug.FpsDisplay'], ['goog.asserts', 'goog.async.AnimationDelay', 'goog.ui.Component']);
-goog.addDependency('debug/fpsdisplay_test.js', ['goog.debug.FpsDisplayTest'], ['goog.debug.FpsDisplay', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('debug/gcdiagnostics.js', ['goog.debug.GcDiagnostics'], ['goog.debug.Trace', 'goog.log', 'goog.userAgent']);
-goog.addDependency('debug/logbuffer.js', ['goog.debug.LogBuffer'], ['goog.asserts', 'goog.debug.LogRecord']);
-goog.addDependency('debug/logbuffer_test.js', ['goog.debug.LogBufferTest'], ['goog.debug.LogBuffer', 'goog.debug.Logger', 'goog.testing.jsunit']);
-goog.addDependency('debug/logger.js', ['goog.debug.LogManager', 'goog.debug.Loggable', 'goog.debug.Logger', 'goog.debug.Logger.Level'], ['goog.array', 'goog.asserts', 'goog.debug', 'goog.debug.LogBuffer', 'goog.debug.LogRecord']);
-goog.addDependency('debug/logger_test.js', ['goog.debug.LoggerTest'], ['goog.debug.LogManager', 'goog.debug.Logger', 'goog.testing.jsunit']);
-goog.addDependency('debug/logrecord.js', ['goog.debug.LogRecord'], []);
-goog.addDependency('debug/logrecordserializer.js', ['goog.debug.logRecordSerializer'], ['goog.debug.LogRecord', 'goog.debug.Logger.Level', 'goog.json', 'goog.object']);
-goog.addDependency('debug/logrecordserializer_test.js', ['goog.debug.logRecordSerializerTest'], ['goog.debug.LogRecord', 'goog.debug.Logger', 'goog.debug.logRecordSerializer', 'goog.testing.jsunit']);
-goog.addDependency('debug/relativetimeprovider.js', ['goog.debug.RelativeTimeProvider'], []);
-goog.addDependency('debug/tracer.js', ['goog.debug.Trace'], ['goog.array', 'goog.iter', 'goog.log', 'goog.structs.Map', 'goog.structs.SimplePool']);
-goog.addDependency('debug/tracer_test.js', ['goog.debug.TraceTest'], ['goog.debug.Trace', 'goog.testing.jsunit']);
-goog.addDependency('defineclass_test.js', ['goog.defineClassTest'], ['goog.testing.jsunit']);
-goog.addDependency('disposable/disposable.js', ['goog.Disposable', 'goog.dispose', 'goog.disposeAll'], ['goog.disposable.IDisposable']);
-goog.addDependency('disposable/disposable_test.js', ['goog.DisposableTest'], ['goog.Disposable', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('disposable/idisposable.js', ['goog.disposable.IDisposable'], []);
-goog.addDependency('dom/abstractmultirange.js', ['goog.dom.AbstractMultiRange'], ['goog.array', 'goog.dom', 'goog.dom.AbstractRange']);
-goog.addDependency('dom/abstractrange.js', ['goog.dom.AbstractRange', 'goog.dom.RangeIterator', 'goog.dom.RangeType'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.SavedCaretRange', 'goog.dom.TagIterator', 'goog.userAgent']);
-goog.addDependency('dom/abstractrange_test.js', ['goog.dom.AbstractRangeTest'], ['goog.dom', 'goog.dom.AbstractRange', 'goog.dom.Range', 'goog.testing.jsunit']);
-goog.addDependency('dom/animationframe/animationframe.js', ['goog.dom.animationFrame', 'goog.dom.animationFrame.Spec', 'goog.dom.animationFrame.State'], ['goog.dom.animationFrame.polyfill']);
-goog.addDependency('dom/animationframe/polyfill.js', ['goog.dom.animationFrame.polyfill'], []);
-goog.addDependency('dom/annotate.js', ['goog.dom.annotate', 'goog.dom.annotate.AnnotateFn'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.safe', 'goog.html.SafeHtml']);
-goog.addDependency('dom/annotate_test.js', ['goog.dom.annotateTest'], ['goog.dom', 'goog.dom.annotate', 'goog.html.SafeHtml', 'goog.testing.jsunit']);
-goog.addDependency('dom/browserfeature.js', ['goog.dom.BrowserFeature'], ['goog.userAgent']);
-goog.addDependency('dom/browserrange/abstractrange.js', ['goog.dom.browserrange.AbstractRange'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeEndpoint', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.iter', 'goog.math.Coordinate', 'goog.string', 'goog.string.StringBuffer', 'goog.userAgent']);
-goog.addDependency('dom/browserrange/browserrange.js', ['goog.dom.browserrange', 'goog.dom.browserrange.Error'], ['goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.NodeType', 'goog.dom.browserrange.GeckoRange', 'goog.dom.browserrange.IeRange', 'goog.dom.browserrange.OperaRange', 'goog.dom.browserrange.W3cRange', 'goog.dom.browserrange.WebKitRange', 'goog.userAgent']);
-goog.addDependency('dom/browserrange/browserrange_test.js', ['goog.dom.browserrangeTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.RangeEndpoint', 'goog.dom.TagName', 'goog.dom.browserrange', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/browserrange/geckorange.js', ['goog.dom.browserrange.GeckoRange'], ['goog.dom.browserrange.W3cRange']);
-goog.addDependency('dom/browserrange/ierange.js', ['goog.dom.browserrange.IeRange'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeEndpoint', 'goog.dom.TagName', 'goog.dom.browserrange.AbstractRange', 'goog.log', 'goog.string']);
-goog.addDependency('dom/browserrange/operarange.js', ['goog.dom.browserrange.OperaRange'], ['goog.dom.browserrange.W3cRange']);
-goog.addDependency('dom/browserrange/w3crange.js', ['goog.dom.browserrange.W3cRange'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeEndpoint', 'goog.dom.browserrange.AbstractRange', 'goog.string']);
-goog.addDependency('dom/browserrange/webkitrange.js', ['goog.dom.browserrange.WebKitRange'], ['goog.dom.RangeEndpoint', 'goog.dom.browserrange.W3cRange', 'goog.userAgent']);
-goog.addDependency('dom/bufferedviewportsizemonitor.js', ['goog.dom.BufferedViewportSizeMonitor'], ['goog.asserts', 'goog.async.Delay', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType']);
-goog.addDependency('dom/bufferedviewportsizemonitor_test.js', ['goog.dom.BufferedViewportSizeMonitorTest'], ['goog.dom.BufferedViewportSizeMonitor', 'goog.dom.ViewportSizeMonitor', 'goog.events', 'goog.events.EventType', 'goog.math.Size', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit']);
-goog.addDependency('dom/classes.js', ['goog.dom.classes'], ['goog.array']);
-goog.addDependency('dom/classes_test.js', ['goog.dom.classes_test'], ['goog.dom', 'goog.dom.classes', 'goog.testing.jsunit']);
-goog.addDependency('dom/classlist.js', ['goog.dom.classlist'], ['goog.array']);
-goog.addDependency('dom/classlist_test.js', ['goog.dom.classlist_test'], ['goog.dom', 'goog.dom.classlist', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit']);
-goog.addDependency('dom/controlrange.js', ['goog.dom.ControlRange', 'goog.dom.ControlRangeIterator'], ['goog.array', 'goog.dom', 'goog.dom.AbstractMultiRange', 'goog.dom.AbstractRange', 'goog.dom.RangeIterator', 'goog.dom.RangeType', 'goog.dom.SavedRange', 'goog.dom.TagWalkType', 'goog.dom.TextRange', 'goog.iter.StopIteration', 'goog.userAgent']);
-goog.addDependency('dom/controlrange_test.js', ['goog.dom.ControlRangeTest'], ['goog.dom', 'goog.dom.ControlRange', 'goog.dom.RangeType', 'goog.dom.TagName', 'goog.dom.TextRange', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/dataset.js', ['goog.dom.dataset'], ['goog.string']);
-goog.addDependency('dom/dataset_test.js', ['goog.dom.datasetTest'], ['goog.dom', 'goog.dom.dataset', 'goog.testing.jsunit']);
-goog.addDependency('dom/dom.js', ['goog.dom', 'goog.dom.Appendable', 'goog.dom.DomHelper'], ['goog.array', 'goog.asserts', 'goog.dom.BrowserFeature', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.functions', 'goog.math.Coordinate', 'goog.math.Size', 'goog.object', 'goog.string', 'goog.userAgent']);
-goog.addDependency('dom/dom_test.js', ['goog.dom.dom_test'], ['goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.DomHelper', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.functions', 'goog.object', 'goog.string.Unicode', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('dom/fontsizemonitor.js', ['goog.dom.FontSizeMonitor', 'goog.dom.FontSizeMonitor.EventType'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.userAgent']);
-goog.addDependency('dom/fontsizemonitor_test.js', ['goog.dom.FontSizeMonitorTest'], ['goog.dom', 'goog.dom.FontSizeMonitor', 'goog.events', 'goog.events.Event', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/forms.js', ['goog.dom.forms'], ['goog.structs.Map']);
-goog.addDependency('dom/forms_test.js', ['goog.dom.formsTest'], ['goog.dom', 'goog.dom.forms', 'goog.testing.jsunit']);
-goog.addDependency('dom/fullscreen.js', ['goog.dom.fullscreen', 'goog.dom.fullscreen.EventType'], ['goog.dom', 'goog.userAgent']);
-goog.addDependency('dom/iframe.js', ['goog.dom.iframe'], ['goog.dom', 'goog.userAgent']);
-goog.addDependency('dom/iframe_test.js', ['goog.dom.iframeTest'], ['goog.dom', 'goog.dom.iframe', 'goog.testing.jsunit']);
-goog.addDependency('dom/iter.js', ['goog.dom.iter.AncestorIterator', 'goog.dom.iter.ChildIterator', 'goog.dom.iter.SiblingIterator'], ['goog.iter.Iterator', 'goog.iter.StopIteration']);
-goog.addDependency('dom/iter_test.js', ['goog.dom.iterTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.iter.AncestorIterator', 'goog.dom.iter.ChildIterator', 'goog.dom.iter.SiblingIterator', 'goog.testing.dom', 'goog.testing.jsunit']);
-goog.addDependency('dom/multirange.js', ['goog.dom.MultiRange', 'goog.dom.MultiRangeIterator'], ['goog.array', 'goog.dom.AbstractMultiRange', 'goog.dom.AbstractRange', 'goog.dom.RangeIterator', 'goog.dom.RangeType', 'goog.dom.SavedRange', 'goog.dom.TextRange', 'goog.iter.StopIteration', 'goog.log']);
-goog.addDependency('dom/multirange_test.js', ['goog.dom.MultiRangeTest'], ['goog.dom', 'goog.dom.MultiRange', 'goog.dom.Range', 'goog.iter', 'goog.testing.jsunit']);
-goog.addDependency('dom/nodeiterator.js', ['goog.dom.NodeIterator'], ['goog.dom.TagIterator']);
-goog.addDependency('dom/nodeiterator_test.js', ['goog.dom.NodeIteratorTest'], ['goog.dom', 'goog.dom.NodeIterator', 'goog.testing.dom', 'goog.testing.jsunit']);
-goog.addDependency('dom/nodeoffset.js', ['goog.dom.NodeOffset'], ['goog.Disposable', 'goog.dom.TagName']);
-goog.addDependency('dom/nodeoffset_test.js', ['goog.dom.NodeOffsetTest'], ['goog.dom', 'goog.dom.NodeOffset', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.testing.jsunit']);
-goog.addDependency('dom/nodetype.js', ['goog.dom.NodeType'], []);
-goog.addDependency('dom/pattern/abstractpattern.js', ['goog.dom.pattern.AbstractPattern'], ['goog.dom.pattern.MatchType']);
-goog.addDependency('dom/pattern/allchildren.js', ['goog.dom.pattern.AllChildren'], ['goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']);
-goog.addDependency('dom/pattern/callback/callback.js', ['goog.dom.pattern.callback'], ['goog.dom', 'goog.dom.TagWalkType', 'goog.iter']);
-goog.addDependency('dom/pattern/callback/counter.js', ['goog.dom.pattern.callback.Counter'], []);
-goog.addDependency('dom/pattern/callback/test.js', ['goog.dom.pattern.callback.Test'], ['goog.iter.StopIteration']);
-goog.addDependency('dom/pattern/childmatches.js', ['goog.dom.pattern.ChildMatches'], ['goog.dom.pattern.AllChildren', 'goog.dom.pattern.MatchType']);
-goog.addDependency('dom/pattern/endtag.js', ['goog.dom.pattern.EndTag'], ['goog.dom.TagWalkType', 'goog.dom.pattern.Tag']);
-goog.addDependency('dom/pattern/fulltag.js', ['goog.dom.pattern.FullTag'], ['goog.dom.pattern.MatchType', 'goog.dom.pattern.StartTag', 'goog.dom.pattern.Tag']);
-goog.addDependency('dom/pattern/matcher.js', ['goog.dom.pattern.Matcher'], ['goog.dom.TagIterator', 'goog.dom.pattern.MatchType', 'goog.iter']);
-goog.addDependency('dom/pattern/matcher_test.js', ['goog.dom.pattern.matcherTest'], ['goog.dom', 'goog.dom.pattern.EndTag', 'goog.dom.pattern.FullTag', 'goog.dom.pattern.Matcher', 'goog.dom.pattern.Repeat', 'goog.dom.pattern.Sequence', 'goog.dom.pattern.StartTag', 'goog.dom.pattern.callback.Counter', 'goog.dom.pattern.callback.Test', 'goog.iter.StopIteration', 'goog.testing.jsunit']);
-goog.addDependency('dom/pattern/nodetype.js', ['goog.dom.pattern.NodeType'], ['goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']);
-goog.addDependency('dom/pattern/pattern.js', ['goog.dom.pattern', 'goog.dom.pattern.MatchType'], []);
-goog.addDependency('dom/pattern/pattern_test.js', ['goog.dom.patternTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagWalkType', 'goog.dom.pattern.AllChildren', 'goog.dom.pattern.ChildMatches', 'goog.dom.pattern.EndTag', 'goog.dom.pattern.FullTag', 'goog.dom.pattern.MatchType', 'goog.dom.pattern.NodeType', 'goog.dom.pattern.Repeat', 'goog.dom.pattern.Sequence', 'goog.dom.pattern.StartTag', 'goog.dom.pattern.Text', 'goog.testing.jsunit']);
-goog.addDependency('dom/pattern/repeat.js', ['goog.dom.pattern.Repeat'], ['goog.dom.NodeType', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']);
-goog.addDependency('dom/pattern/sequence.js', ['goog.dom.pattern.Sequence'], ['goog.dom.NodeType', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']);
-goog.addDependency('dom/pattern/starttag.js', ['goog.dom.pattern.StartTag'], ['goog.dom.TagWalkType', 'goog.dom.pattern.Tag']);
-goog.addDependency('dom/pattern/tag.js', ['goog.dom.pattern.Tag'], ['goog.dom.pattern', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType', 'goog.object']);
-goog.addDependency('dom/pattern/text.js', ['goog.dom.pattern.Text'], ['goog.dom.NodeType', 'goog.dom.pattern', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']);
-goog.addDependency('dom/range.js', ['goog.dom.Range'], ['goog.dom', 'goog.dom.AbstractRange', 'goog.dom.BrowserFeature', 'goog.dom.ControlRange', 'goog.dom.MultiRange', 'goog.dom.NodeType', 'goog.dom.TextRange', 'goog.userAgent']);
-goog.addDependency('dom/range_test.js', ['goog.dom.RangeTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.RangeType', 'goog.dom.TagName', 'goog.dom.TextRange', 'goog.dom.browserrange', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/rangeendpoint.js', ['goog.dom.RangeEndpoint'], []);
-goog.addDependency('dom/safe.js', ['goog.dom.safe'], ['goog.html.SafeHtml', 'goog.html.SafeUrl']);
-goog.addDependency('dom/safe_test.js', ['goog.dom.safeTest'], ['goog.dom.safe', 'goog.html.SafeUrl', 'goog.html.testing', 'goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('dom/savedcaretrange.js', ['goog.dom.SavedCaretRange'], ['goog.array', 'goog.dom', 'goog.dom.SavedRange', 'goog.dom.TagName', 'goog.string']);
-goog.addDependency('dom/savedcaretrange_test.js', ['goog.dom.SavedCaretRangeTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.SavedCaretRange', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/savedrange.js', ['goog.dom.SavedRange'], ['goog.Disposable', 'goog.log']);
-goog.addDependency('dom/savedrange_test.js', ['goog.dom.SavedRangeTest'], ['goog.dom', 'goog.dom.Range', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/selection.js', ['goog.dom.selection'], ['goog.string', 'goog.userAgent']);
-goog.addDependency('dom/selection_test.js', ['goog.dom.selectionTest'], ['goog.dom', 'goog.dom.selection', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/tagiterator.js', ['goog.dom.TagIterator', 'goog.dom.TagWalkType'], ['goog.dom', 'goog.dom.NodeType', 'goog.iter.Iterator', 'goog.iter.StopIteration']);
-goog.addDependency('dom/tagiterator_test.js', ['goog.dom.TagIteratorTest'], ['goog.dom', 'goog.dom.TagIterator', 'goog.dom.TagWalkType', 'goog.iter', 'goog.iter.StopIteration', 'goog.testing.dom', 'goog.testing.jsunit']);
-goog.addDependency('dom/tagname.js', ['goog.dom.TagName'], []);
-goog.addDependency('dom/tagname_test.js', ['goog.dom.TagNameTest'], ['goog.dom.TagName', 'goog.object', 'goog.testing.jsunit']);
-goog.addDependency('dom/tags.js', ['goog.dom.tags'], ['goog.object']);
-goog.addDependency('dom/tags_test.js', ['goog.dom.tagsTest'], ['goog.dom.tags', 'goog.testing.jsunit']);
-goog.addDependency('dom/textrange.js', ['goog.dom.TextRange'], ['goog.array', 'goog.dom', 'goog.dom.AbstractRange', 'goog.dom.RangeType', 'goog.dom.SavedRange', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.dom.browserrange', 'goog.string', 'goog.userAgent']);
-goog.addDependency('dom/textrange_test.js', ['goog.dom.TextRangeTest'], ['goog.dom', 'goog.dom.ControlRange', 'goog.dom.Range', 'goog.dom.TextRange', 'goog.math.Coordinate', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('dom/textrangeiterator.js', ['goog.dom.TextRangeIterator'], ['goog.array', 'goog.dom.NodeType', 'goog.dom.RangeIterator', 'goog.dom.TagName', 'goog.iter.StopIteration']);
-goog.addDependency('dom/textrangeiterator_test.js', ['goog.dom.TextRangeIteratorTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.iter.StopIteration', 'goog.testing.dom', 'goog.testing.jsunit']);
-goog.addDependency('dom/vendor.js', ['goog.dom.vendor'], ['goog.string', 'goog.userAgent']);
-goog.addDependency('dom/vendor_test.js', ['goog.dom.vendorTest'], ['goog.array', 'goog.dom.vendor', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgentTestUtil']);
-goog.addDependency('dom/viewportsizemonitor.js', ['goog.dom.ViewportSizeMonitor'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Size']);
-goog.addDependency('dom/viewportsizemonitor_test.js', ['goog.dom.ViewportSizeMonitorTest'], ['goog.dom.ViewportSizeMonitor', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Size', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('dom/xml.js', ['goog.dom.xml'], ['goog.dom', 'goog.dom.NodeType']);
-goog.addDependency('dom/xml_test.js', ['goog.dom.xmlTest'], ['goog.dom.xml', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/browserfeature.js', ['goog.editor.BrowserFeature'], ['goog.editor.defines', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('editor/browserfeature_test.js', ['goog.editor.BrowserFeatureTest'], ['goog.dom', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit']);
-goog.addDependency('editor/clicktoeditwrapper.js', ['goog.editor.ClickToEditWrapper'], ['goog.Disposable', 'goog.asserts', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field.EventType', 'goog.editor.range', 'goog.events.BrowserEvent.MouseButton', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.log']);
-goog.addDependency('editor/clicktoeditwrapper_test.js', ['goog.editor.ClickToEditWrapperTest'], ['goog.dom', 'goog.dom.Range', 'goog.editor.ClickToEditWrapper', 'goog.editor.SeamlessField', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']);
-goog.addDependency('editor/command.js', ['goog.editor.Command'], []);
-goog.addDependency('editor/contenteditablefield.js', ['goog.editor.ContentEditableField'], ['goog.asserts', 'goog.editor.Field', 'goog.log']);
-goog.addDependency('editor/contenteditablefield_test.js', ['goog.editor.ContentEditableFieldTest'], ['goog.dom', 'goog.editor.ContentEditableField', 'goog.editor.field_test', 'goog.testing.jsunit']);
-goog.addDependency('editor/defines.js', ['goog.editor.defines'], []);
-goog.addDependency('editor/field.js', ['goog.editor.Field', 'goog.editor.Field.EventType'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.array', 'goog.asserts', 'goog.async.Delay', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Plugin', 'goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo', 'goog.editor.node', 'goog.editor.range', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.functions', 'goog.log', 'goog.log.Level', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.userAgent', 'goog.userAgent.product']);
-goog.addDependency('editor/field_test.js', ['goog.editor.field_test'], ['goog.dom', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.range', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.functions', 'goog.testing.LooseMock', 'goog.testing.MockClock', 'goog.testing.dom', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('editor/focus.js', ['goog.editor.focus'], ['goog.dom.selection']);
-goog.addDependency('editor/focus_test.js', ['goog.editor.focusTest'], ['goog.dom.selection', 'goog.editor.BrowserFeature', 'goog.editor.focus', 'goog.testing.jsunit']);
-goog.addDependency('editor/icontent.js', ['goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo'], ['goog.editor.BrowserFeature', 'goog.style', 'goog.userAgent']);
-goog.addDependency('editor/icontent_test.js', ['goog.editor.icontentTest'], ['goog.dom', 'goog.editor.BrowserFeature', 'goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/link.js', ['goog.editor.Link'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.node', 'goog.editor.range', 'goog.string', 'goog.string.Unicode', 'goog.uri.utils', 'goog.uri.utils.ComponentIndex']);
-goog.addDependency('editor/link_test.js', ['goog.editor.LinkTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Link', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/node.js', ['goog.editor.node'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.iter.ChildIterator', 'goog.dom.iter.SiblingIterator', 'goog.iter', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.userAgent']);
-goog.addDependency('editor/node_test.js', ['goog.editor.nodeTest'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.editor.node', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugin.js', ['goog.editor.Plugin'], ['goog.editor.Command', 'goog.events.EventTarget', 'goog.functions', 'goog.log', 'goog.object', 'goog.reflect']);
-goog.addDependency('editor/plugin_test.js', ['goog.editor.PluginTest'], ['goog.editor.Field', 'goog.editor.Plugin', 'goog.functions', 'goog.testing.StrictMock', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/abstractbubbleplugin.js', ['goog.editor.plugins.AbstractBubblePlugin'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.editor.Plugin', 'goog.editor.style', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.actionEventWrapper', 'goog.functions', 'goog.string.Unicode', 'goog.ui.Component', 'goog.ui.editor.Bubble', 'goog.userAgent']);
-goog.addDependency('editor/plugins/abstractbubbleplugin_test.js', ['goog.editor.plugins.AbstractBubblePluginTest'], ['goog.dom', 'goog.editor.plugins.AbstractBubblePlugin', 'goog.events.BrowserEvent', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.functions', 'goog.style', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.ui.editor.Bubble', 'goog.userAgent']);
-goog.addDependency('editor/plugins/abstractdialogplugin.js', ['goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.plugins.AbstractDialogPlugin.EventType'], ['goog.dom', 'goog.dom.Range', 'goog.editor.Field.EventType', 'goog.editor.Plugin', 'goog.editor.range', 'goog.events', 'goog.ui.editor.AbstractDialog.EventType']);
-goog.addDependency('editor/plugins/abstractdialogplugin_test.js', ['goog.editor.plugins.AbstractDialogPluginTest'], ['goog.dom.SavedRange', 'goog.editor.Field', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.events.Event', 'goog.events.EventHandler', 'goog.functions', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.ui.editor.AbstractDialog', 'goog.userAgent']);
-goog.addDependency('editor/plugins/abstracttabhandler.js', ['goog.editor.plugins.AbstractTabHandler'], ['goog.editor.Plugin', 'goog.events.KeyCodes']);
-goog.addDependency('editor/plugins/abstracttabhandler_test.js', ['goog.editor.plugins.AbstractTabHandlerTest'], ['goog.editor.Field', 'goog.editor.plugins.AbstractTabHandler', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.testing.StrictMock', 'goog.testing.editor.FieldMock', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/basictextformatter.js', ['goog.editor.plugins.BasicTextFormatter', 'goog.editor.plugins.BasicTextFormatter.COMMAND'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Link', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.range', 'goog.editor.style', 'goog.iter', 'goog.iter.StopIteration', 'goog.log', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.ui.editor.messages', 'goog.userAgent']);
-goog.addDependency('editor/plugins/basictextformatter_test.js', ['goog.editor.plugins.BasicTextFormatterTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.plugins.BasicTextFormatter', 'goog.object', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.LooseMock', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.userAgent']);
-goog.addDependency('editor/plugins/blockquote.js', ['goog.editor.plugins.Blockquote'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Plugin', 'goog.editor.node', 'goog.functions', 'goog.log']);
-goog.addDependency('editor/plugins/blockquote_test.js', ['goog.editor.plugins.BlockquoteTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.plugins.Blockquote', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/emoticons.js', ['goog.editor.plugins.Emoticons'], ['goog.dom.TagName', 'goog.editor.Plugin', 'goog.editor.range', 'goog.functions', 'goog.ui.emoji.Emoji', 'goog.userAgent']);
-goog.addDependency('editor/plugins/emoticons_test.js', ['goog.editor.plugins.EmoticonsTest'], ['goog.Uri', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.editor.Field', 'goog.editor.plugins.Emoticons', 'goog.testing.jsunit', 'goog.ui.emoji.Emoji', 'goog.userAgent']);
-goog.addDependency('editor/plugins/enterhandler.js', ['goog.editor.plugins.EnterHandler'], ['goog.dom', 'goog.dom.NodeOffset', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.plugins.Blockquote', 'goog.editor.range', 'goog.editor.style', 'goog.events.KeyCodes', 'goog.functions', 'goog.object', 'goog.string', 'goog.userAgent']);
-goog.addDependency('editor/plugins/enterhandler_test.js', ['goog.editor.plugins.EnterHandlerTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.plugins.Blockquote', 'goog.editor.plugins.EnterHandler', 'goog.editor.range', 'goog.events', 'goog.events.KeyCodes', 'goog.testing.ExpectedFailures', 'goog.testing.MockClock', 'goog.testing.dom', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/equationeditorbubble.js', ['goog.editor.plugins.equation.EquationBubble'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.plugins.AbstractBubblePlugin', 'goog.string.Unicode', 'goog.ui.editor.Bubble', 'goog.ui.equation.ImageRenderer']);
-goog.addDependency('editor/plugins/equationeditorplugin.js', ['goog.editor.plugins.EquationEditorPlugin'], ['goog.dom', 'goog.editor.Command', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range', 'goog.events', 'goog.events.EventType', 'goog.functions', 'goog.log', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.EquationEditorDialog', 'goog.ui.equation.ImageRenderer', 'goog.ui.equation.PaletteManager']);
-goog.addDependency('editor/plugins/equationeditorplugin_test.js', ['goog.editor.plugins.EquationEditorPluginTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.editor.plugins.EquationEditorPlugin', 'goog.testing.MockControl', 'goog.testing.MockRange', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.jsunit', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.ui.editor.EquationEditorOkEvent']);
-goog.addDependency('editor/plugins/firststrong.js', ['goog.editor.plugins.FirstStrong'], ['goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.range', 'goog.i18n.bidi', 'goog.i18n.uChar', 'goog.iter', 'goog.userAgent']);
-goog.addDependency('editor/plugins/firststrong_test.js', ['goog.editor.plugins.FirstStrongTest'], ['goog.dom.Range', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.plugins.FirstStrong', 'goog.editor.range', 'goog.events.KeyCodes', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/headerformatter.js', ['goog.editor.plugins.HeaderFormatter'], ['goog.editor.Command', 'goog.editor.Plugin', 'goog.userAgent']);
-goog.addDependency('editor/plugins/headerformatter_test.js', ['goog.editor.plugins.HeaderFormatterTest'], ['goog.dom', 'goog.editor.Command', 'goog.editor.plugins.BasicTextFormatter', 'goog.editor.plugins.HeaderFormatter', 'goog.events.BrowserEvent', 'goog.testing.LooseMock', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/linkbubble.js', ['goog.editor.plugins.LinkBubble', 'goog.editor.plugins.LinkBubble.Action'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.Link', 'goog.editor.plugins.AbstractBubblePlugin', 'goog.editor.range', 'goog.functions', 'goog.string', 'goog.style', 'goog.ui.editor.messages', 'goog.uri.utils', 'goog.window']);
-goog.addDependency('editor/plugins/linkbubble_test.js', ['goog.editor.plugins.LinkBubbleTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.Link', 'goog.editor.plugins.LinkBubble', 'goog.events.BrowserEvent', 'goog.events.Event', 'goog.events.EventType', 'goog.string', 'goog.style', 'goog.testing.FunctionMock', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/linkdialogplugin.js', ['goog.editor.plugins.LinkDialogPlugin'], ['goog.array', 'goog.dom', 'goog.editor.Command', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.events.EventHandler', 'goog.functions', 'goog.ui.editor.AbstractDialog.EventType', 'goog.ui.editor.LinkDialog', 'goog.ui.editor.LinkDialog.EventType', 'goog.ui.editor.LinkDialog.OkEvent', 'goog.uri.utils']);
-goog.addDependency('editor/plugins/linkdialogplugin_test.js', ['goog.ui.editor.LinkDialogTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.Link', 'goog.editor.plugins.LinkDialogPlugin', 'goog.string', 'goog.string.Unicode', 'goog.testing.MockControl', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.editor.dom', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.LinkDialog', 'goog.userAgent']);
-goog.addDependency('editor/plugins/linkshortcutplugin.js', ['goog.editor.plugins.LinkShortcutPlugin'], ['goog.editor.Command', 'goog.editor.Link', 'goog.editor.Plugin', 'goog.string']);
-goog.addDependency('editor/plugins/linkshortcutplugin_test.js', ['goog.editor.plugins.LinkShortcutPluginTest'], ['goog.dom', 'goog.editor.Field', 'goog.editor.plugins.BasicTextFormatter', 'goog.editor.plugins.LinkBubble', 'goog.editor.plugins.LinkShortcutPlugin', 'goog.events.KeyCodes', 'goog.testing.PropertyReplacer', 'goog.testing.dom', 'goog.testing.events', 'goog.testing.jsunit']);
-goog.addDependency('editor/plugins/listtabhandler.js', ['goog.editor.plugins.ListTabHandler'], ['goog.dom.TagName', 'goog.editor.Command', 'goog.editor.plugins.AbstractTabHandler']);
-goog.addDependency('editor/plugins/listtabhandler_test.js', ['goog.editor.plugins.ListTabHandlerTest'], ['goog.dom', 'goog.editor.Command', 'goog.editor.plugins.ListTabHandler', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.functions', 'goog.testing.StrictMock', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit']);
-goog.addDependency('editor/plugins/loremipsum.js', ['goog.editor.plugins.LoremIpsum'], ['goog.asserts', 'goog.dom', 'goog.editor.Command', 'goog.editor.Plugin', 'goog.editor.node', 'goog.functions']);
-goog.addDependency('editor/plugins/loremipsum_test.js', ['goog.editor.plugins.LoremIpsumTest'], ['goog.dom', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.plugins.LoremIpsum', 'goog.string.Unicode', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/removeformatting.js', ['goog.editor.plugins.RemoveFormatting'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.range', 'goog.string', 'goog.userAgent']);
-goog.addDependency('editor/plugins/removeformatting_test.js', ['goog.editor.plugins.RemoveFormattingTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.plugins.RemoveFormatting', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.dom', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/spacestabhandler.js', ['goog.editor.plugins.SpacesTabHandler'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.plugins.AbstractTabHandler', 'goog.editor.range']);
-goog.addDependency('editor/plugins/spacestabhandler_test.js', ['goog.editor.plugins.SpacesTabHandlerTest'], ['goog.dom', 'goog.dom.Range', 'goog.editor.plugins.SpacesTabHandler', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.functions', 'goog.testing.StrictMock', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit']);
-goog.addDependency('editor/plugins/tableeditor.js', ['goog.editor.plugins.TableEditor'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin', 'goog.editor.Table', 'goog.editor.node', 'goog.editor.range', 'goog.object']);
-goog.addDependency('editor/plugins/tableeditor_test.js', ['goog.editor.plugins.TableEditorTest'], ['goog.dom', 'goog.dom.Range', 'goog.editor.plugins.TableEditor', 'goog.object', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.JsUnitException', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/tagonenterhandler.js', ['goog.editor.plugins.TagOnEnterHandler'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.node', 'goog.editor.plugins.EnterHandler', 'goog.editor.range', 'goog.editor.style', 'goog.events.KeyCodes', 'goog.string', 'goog.style', 'goog.userAgent']);
-goog.addDependency('editor/plugins/tagonenterhandler_test.js', ['goog.editor.plugins.TagOnEnterHandlerTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.plugins.TagOnEnterHandler', 'goog.events.KeyCodes', 'goog.string.Unicode', 'goog.testing.dom', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/plugins/undoredo.js', ['goog.editor.plugins.UndoRedo'], ['goog.dom', 'goog.dom.NodeOffset', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field.EventType', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.plugins.UndoRedoManager', 'goog.editor.plugins.UndoRedoState', 'goog.events', 'goog.events.EventHandler', 'goog.log']);
-goog.addDependency('editor/plugins/undoredo_test.js', ['goog.editor.plugins.UndoRedoTest'], ['goog.array', 'goog.dom', 'goog.dom.browserrange', 'goog.editor.Field', 'goog.editor.plugins.LoremIpsum', 'goog.editor.plugins.UndoRedo', 'goog.events', 'goog.functions', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.StrictMock', 'goog.testing.jsunit']);
-goog.addDependency('editor/plugins/undoredomanager.js', ['goog.editor.plugins.UndoRedoManager', 'goog.editor.plugins.UndoRedoManager.EventType'], ['goog.editor.plugins.UndoRedoState', 'goog.events.EventTarget']);
-goog.addDependency('editor/plugins/undoredomanager_test.js', ['goog.editor.plugins.UndoRedoManagerTest'], ['goog.editor.plugins.UndoRedoManager', 'goog.editor.plugins.UndoRedoState', 'goog.events', 'goog.testing.StrictMock', 'goog.testing.jsunit']);
-goog.addDependency('editor/plugins/undoredostate.js', ['goog.editor.plugins.UndoRedoState'], ['goog.events.EventTarget']);
-goog.addDependency('editor/plugins/undoredostate_test.js', ['goog.editor.plugins.UndoRedoStateTest'], ['goog.editor.plugins.UndoRedoState', 'goog.testing.jsunit']);
-goog.addDependency('editor/range.js', ['goog.editor.range', 'goog.editor.range.Point'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.RangeEndpoint', 'goog.dom.SavedCaretRange', 'goog.editor.node', 'goog.editor.style', 'goog.iter', 'goog.userAgent']);
-goog.addDependency('editor/range_test.js', ['goog.editor.rangeTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.range', 'goog.editor.range.Point', 'goog.string', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('editor/seamlessfield.js', ['goog.editor.SeamlessField'], ['goog.cssom.iframe.style', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo', 'goog.editor.node', 'goog.events', 'goog.events.EventType', 'goog.log', 'goog.style']);
-goog.addDependency('editor/seamlessfield_test.js', ['goog.editor.seamlessfield_test'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.SeamlessField', 'goog.events', 'goog.functions', 'goog.style', 'goog.testing.MockClock', 'goog.testing.MockRange', 'goog.testing.jsunit']);
-goog.addDependency('editor/style.js', ['goog.editor.style'], ['goog.dom', 'goog.dom.NodeType', 'goog.editor.BrowserFeature', 'goog.events.EventType', 'goog.object', 'goog.style', 'goog.userAgent']);
-goog.addDependency('editor/style_test.js', ['goog.editor.styleTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.style', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.style', 'goog.testing.LooseMock', 'goog.testing.jsunit', 'goog.testing.mockmatchers']);
-goog.addDependency('editor/table.js', ['goog.editor.Table', 'goog.editor.TableCell', 'goog.editor.TableRow'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.log', 'goog.string.Unicode', 'goog.style']);
-goog.addDependency('editor/table_test.js', ['goog.editor.TableTest'], ['goog.dom', 'goog.editor.Table', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('events/actioneventwrapper.js', ['goog.events.actionEventWrapper'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.EventWrapper', 'goog.events.KeyCodes', 'goog.userAgent']);
-goog.addDependency('events/actioneventwrapper_test.js', ['goog.events.actionEventWrapperTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.events', 'goog.events.EventHandler', 'goog.events.KeyCodes', 'goog.events.actionEventWrapper', 'goog.testing.events', 'goog.testing.jsunit']);
-goog.addDependency('events/actionhandler.js', ['goog.events.ActionEvent', 'goog.events.ActionHandler', 'goog.events.ActionHandler.EventType', 'goog.events.BeforeActionEvent'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.userAgent']);
-goog.addDependency('events/actionhandler_test.js', ['goog.events.ActionHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.ActionHandler', 'goog.testing.events', 'goog.testing.jsunit']);
-goog.addDependency('events/browserevent.js', ['goog.events.BrowserEvent', 'goog.events.BrowserEvent.MouseButton'], ['goog.events.BrowserFeature', 'goog.events.Event', 'goog.events.EventType', 'goog.reflect', 'goog.userAgent']);
-goog.addDependency('events/browserevent_test.js', ['goog.events.BrowserEventTest'], ['goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('events/browserfeature.js', ['goog.events.BrowserFeature'], ['goog.userAgent']);
-goog.addDependency('events/event.js', ['goog.events.Event', 'goog.events.EventLike'], ['goog.Disposable', 'goog.events.EventId']);
-goog.addDependency('events/event_test.js', ['goog.events.EventTest'], ['goog.events.Event', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.testing.jsunit']);
-goog.addDependency('events/eventhandler.js', ['goog.events.EventHandler'], ['goog.Disposable', 'goog.events', 'goog.object']);
-goog.addDependency('events/eventhandler_test.js', ['goog.events.EventHandlerTest'], ['goog.events', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('events/eventid.js', ['goog.events.EventId'], []);
-goog.addDependency('events/events.js', ['goog.events', 'goog.events.CaptureSimulationMode', 'goog.events.Key', 'goog.events.ListenableType'], ['goog.asserts', 'goog.debug.entryPointRegistry', 'goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.events.Listenable', 'goog.events.ListenerMap']);
-goog.addDependency('events/events_test.js', ['goog.eventsTest'], ['goog.asserts.AssertionError', 'goog.debug.EntryPointMonitor', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.dom', 'goog.events', 'goog.events.BrowserFeature', 'goog.events.CaptureSimulationMode', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.Listener', 'goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('events/eventtarget.js', ['goog.events.EventTarget'], ['goog.Disposable', 'goog.asserts', 'goog.events', 'goog.events.Event', 'goog.events.Listenable', 'goog.events.ListenerMap', 'goog.object']);
-goog.addDependency('events/eventtarget_test.js', ['goog.events.EventTargetTest'], ['goog.events.EventTarget', 'goog.events.Listenable', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.testing.jsunit']);
-goog.addDependency('events/eventtarget_via_googevents_test.js', ['goog.events.EventTargetGoogEventsTest'], ['goog.events', 'goog.events.EventTarget', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.object', 'goog.testing', 'goog.testing.jsunit']);
-goog.addDependency('events/eventtarget_via_w3cinterface_test.js', ['goog.events.EventTargetW3CTest'], ['goog.events.EventTarget', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.testing.jsunit']);
-goog.addDependency('events/eventtargettester.js', ['goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType'], ['goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.testing.asserts', 'goog.testing.recordFunction']);
-goog.addDependency('events/eventtype.js', ['goog.events.EventType'], ['goog.userAgent']);
-goog.addDependency('events/eventwrapper.js', ['goog.events.EventWrapper'], []);
-goog.addDependency('events/filedrophandler.js', ['goog.events.FileDropHandler', 'goog.events.FileDropHandler.EventType'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.log']);
-goog.addDependency('events/filedrophandler_test.js', ['goog.events.FileDropHandlerTest'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.FileDropHandler', 'goog.testing.jsunit']);
-goog.addDependency('events/focushandler.js', ['goog.events.FocusHandler', 'goog.events.FocusHandler.EventType'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.userAgent']);
-goog.addDependency('events/imehandler.js', ['goog.events.ImeHandler', 'goog.events.ImeHandler.Event', 'goog.events.ImeHandler.EventType'], ['goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.userAgent']);
-goog.addDependency('events/imehandler_test.js', ['goog.events.ImeHandlerTest'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.ImeHandler', 'goog.events.KeyCodes', 'goog.object', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('events/inputhandler.js', ['goog.events.InputHandler', 'goog.events.InputHandler.EventType'], ['goog.Timer', 'goog.dom', 'goog.events.BrowserEvent', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.KeyCodes', 'goog.userAgent']);
-goog.addDependency('events/inputhandler_test.js', ['goog.events.InputHandlerTest'], ['goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('events/keycodes.js', ['goog.events.KeyCodes'], ['goog.userAgent']);
-goog.addDependency('events/keycodes_test.js', ['goog.events.KeyCodesTest'], ['goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('events/keyhandler.js', ['goog.events.KeyEvent', 'goog.events.KeyHandler', 'goog.events.KeyHandler.EventType'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.userAgent']);
-goog.addDependency('events/keyhandler_test.js', ['goog.events.KeyEventTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('events/keynames.js', ['goog.events.KeyNames'], []);
-goog.addDependency('events/listenable.js', ['goog.events.Listenable', 'goog.events.ListenableKey'], ['goog.events.EventId']);
-goog.addDependency('events/listenable_test.js', ['goog.events.ListenableTest'], ['goog.events.Listenable', 'goog.testing.jsunit']);
-goog.addDependency('events/listener.js', ['goog.events.Listener'], ['goog.events.ListenableKey']);
-goog.addDependency('events/listenermap.js', ['goog.events.ListenerMap'], ['goog.array', 'goog.events.Listener', 'goog.object']);
-goog.addDependency('events/listenermap_test.js', ['goog.events.ListenerMapTest'], ['goog.dispose', 'goog.events', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.events.ListenerMap', 'goog.testing.jsunit']);
-goog.addDependency('events/mousewheelhandler.js', ['goog.events.MouseWheelEvent', 'goog.events.MouseWheelHandler', 'goog.events.MouseWheelHandler.EventType'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.math', 'goog.style', 'goog.userAgent']);
-goog.addDependency('events/mousewheelhandler_test.js', ['goog.events.MouseWheelHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.MouseWheelEvent', 'goog.events.MouseWheelHandler', 'goog.functions', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('events/onlinehandler.js', ['goog.events.OnlineHandler', 'goog.events.OnlineHandler.EventType'], ['goog.Timer', 'goog.events.BrowserFeature', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.net.NetworkStatusMonitor', 'goog.userAgent']);
-goog.addDependency('events/onlinelistener_test.js', ['goog.events.OnlineHandlerTest'], ['goog.events', 'goog.events.BrowserFeature', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.OnlineHandler', 'goog.net.NetworkStatusMonitor', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('events/pastehandler.js', ['goog.events.PasteHandler', 'goog.events.PasteHandler.EventType', 'goog.events.PasteHandler.State'], ['goog.Timer', 'goog.async.ConditionalDelay', 'goog.events.BrowserEvent', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.log', 'goog.userAgent']);
-goog.addDependency('events/pastehandler_test.js', ['goog.events.PasteHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.PasteHandler', 'goog.testing.MockClock', 'goog.testing.MockUserAgent', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('format/emailaddress.js', ['goog.format.EmailAddress'], ['goog.string']);
-goog.addDependency('format/emailaddress_test.js', ['goog.format.EmailAddressTest'], ['goog.array', 'goog.format.EmailAddress', 'goog.testing.jsunit']);
-goog.addDependency('format/format.js', ['goog.format'], ['goog.i18n.GraphemeBreak', 'goog.string', 'goog.userAgent']);
-goog.addDependency('format/format_test.js', ['goog.formatTest'], ['goog.dom', 'goog.format', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('format/htmlprettyprinter.js', ['goog.format.HtmlPrettyPrinter', 'goog.format.HtmlPrettyPrinter.Buffer'], ['goog.object', 'goog.string.StringBuffer']);
-goog.addDependency('format/htmlprettyprinter_test.js', ['goog.format.HtmlPrettyPrinterTest'], ['goog.format.HtmlPrettyPrinter', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('format/internationalizedemailaddress.js', ['goog.format.InternationalizedEmailAddress'], ['goog.format.EmailAddress']);
-goog.addDependency('format/internationalizedemailaddress_test.js', ['goog.format.InternationalizedEmailAddressTest'], ['goog.array', 'goog.format.InternationalizedEmailAddress', 'goog.testing.jsunit']);
-goog.addDependency('format/jsonprettyprinter.js', ['goog.format.JsonPrettyPrinter', 'goog.format.JsonPrettyPrinter.HtmlDelimiters', 'goog.format.JsonPrettyPrinter.TextDelimiters'], ['goog.json', 'goog.json.Serializer', 'goog.string', 'goog.string.StringBuffer', 'goog.string.format']);
-goog.addDependency('format/jsonprettyprinter_test.js', ['goog.format.JsonPrettyPrinterTest'], ['goog.format.JsonPrettyPrinter', 'goog.testing.jsunit']);
-goog.addDependency('fs/entry.js', ['goog.fs.DirectoryEntry', 'goog.fs.DirectoryEntry.Behavior', 'goog.fs.Entry', 'goog.fs.FileEntry'], []);
-goog.addDependency('fs/entryimpl.js', ['goog.fs.DirectoryEntryImpl', 'goog.fs.EntryImpl', 'goog.fs.FileEntryImpl'], ['goog.array', 'goog.async.Deferred', 'goog.fs.DirectoryEntry', 'goog.fs.Entry', 'goog.fs.Error', 'goog.fs.FileEntry', 'goog.fs.FileWriter', 'goog.functions', 'goog.string']);
-goog.addDependency('fs/error.js', ['goog.fs.Error', 'goog.fs.Error.ErrorCode'], ['goog.debug.Error', 'goog.object', 'goog.string']);
-goog.addDependency('fs/filereader.js', ['goog.fs.FileReader', 'goog.fs.FileReader.EventType', 'goog.fs.FileReader.ReadyState'], ['goog.async.Deferred', 'goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.ProgressEvent']);
-goog.addDependency('fs/filesaver.js', ['goog.fs.FileSaver', 'goog.fs.FileSaver.EventType', 'goog.fs.FileSaver.ProgressEvent', 'goog.fs.FileSaver.ReadyState'], ['goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.ProgressEvent']);
-goog.addDependency('fs/filesystem.js', ['goog.fs.FileSystem'], []);
-goog.addDependency('fs/filesystemimpl.js', ['goog.fs.FileSystemImpl'], ['goog.fs.DirectoryEntryImpl', 'goog.fs.FileSystem']);
-goog.addDependency('fs/filewriter.js', ['goog.fs.FileWriter'], ['goog.fs.Error', 'goog.fs.FileSaver']);
-goog.addDependency('fs/fs.js', ['goog.fs'], ['goog.array', 'goog.async.Deferred', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.fs.FileSystemImpl', 'goog.userAgent']);
-goog.addDependency('fs/fs_test.js', ['goog.fsTest'], ['goog.array', 'goog.async.Deferred', 'goog.async.DeferredList', 'goog.dom', 'goog.events', 'goog.fs', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.fs.FileSaver', 'goog.string', 'goog.testing.AsyncTestCase', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('fs/progressevent.js', ['goog.fs.ProgressEvent'], ['goog.events.Event']);
-goog.addDependency('functions/functions.js', ['goog.functions'], []);
-goog.addDependency('functions/functions_test.js', ['goog.functionsTest'], ['goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('fx/abstractdragdrop.js', ['goog.fx.AbstractDragDrop', 'goog.fx.AbstractDragDrop.EventType', 'goog.fx.DragDropEvent', 'goog.fx.DragDropItem'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Box', 'goog.math.Coordinate', 'goog.style']);
-goog.addDependency('fx/abstractdragdrop_test.js', ['goog.fx.AbstractDragDropTest'], ['goog.array', 'goog.events.EventType', 'goog.functions', 'goog.fx.AbstractDragDrop', 'goog.fx.DragDropItem', 'goog.math.Box', 'goog.math.Coordinate', 'goog.style', 'goog.testing.events', 'goog.testing.jsunit']);
-goog.addDependency('fx/anim/anim.js', ['goog.fx.anim', 'goog.fx.anim.Animated'], ['goog.async.AnimationDelay', 'goog.async.Delay', 'goog.object']);
-goog.addDependency('fx/anim/anim_test.js', ['goog.fx.animTest'], ['goog.async.AnimationDelay', 'goog.async.Delay', 'goog.events', 'goog.functions', 'goog.fx.Animation', 'goog.fx.anim', 'goog.object', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('fx/animation.js', ['goog.fx.Animation', 'goog.fx.Animation.EventType', 'goog.fx.Animation.State', 'goog.fx.AnimationEvent'], ['goog.array', 'goog.events.Event', 'goog.fx.Transition', 'goog.fx.Transition.EventType', 'goog.fx.TransitionBase.State', 'goog.fx.anim', 'goog.fx.anim.Animated']);
-goog.addDependency('fx/animation_test.js', ['goog.fx.AnimationTest'], ['goog.events', 'goog.fx.Animation', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('fx/animationqueue.js', ['goog.fx.AnimationParallelQueue', 'goog.fx.AnimationQueue', 'goog.fx.AnimationSerialQueue'], ['goog.array', 'goog.asserts', 'goog.events.EventHandler', 'goog.fx.Transition.EventType', 'goog.fx.TransitionBase', 'goog.fx.TransitionBase.State']);
-goog.addDependency('fx/animationqueue_test.js', ['goog.fx.AnimationQueueTest'], ['goog.events', 'goog.fx.Animation', 'goog.fx.AnimationParallelQueue', 'goog.fx.AnimationSerialQueue', 'goog.fx.Transition', 'goog.fx.anim', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('fx/css3/fx.js', ['goog.fx.css3'], ['goog.fx.css3.Transition']);
-goog.addDependency('fx/css3/transition.js', ['goog.fx.css3.Transition'], ['goog.Timer', 'goog.fx.TransitionBase', 'goog.style', 'goog.style.transition']);
-goog.addDependency('fx/css3/transition_test.js', ['goog.fx.css3.TransitionTest'], ['goog.dispose', 'goog.dom', 'goog.events', 'goog.fx.Transition', 'goog.fx.css3.Transition', 'goog.style.transition', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('fx/cssspriteanimation.js', ['goog.fx.CssSpriteAnimation'], ['goog.fx.Animation']);
-goog.addDependency('fx/cssspriteanimation_test.js', ['goog.fx.CssSpriteAnimationTest'], ['goog.fx.CssSpriteAnimation', 'goog.math.Box', 'goog.math.Size', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('fx/dom.js', ['goog.fx.dom', 'goog.fx.dom.BgColorTransform', 'goog.fx.dom.ColorTransform', 'goog.fx.dom.Fade', 'goog.fx.dom.FadeIn', 'goog.fx.dom.FadeInAndShow', 'goog.fx.dom.FadeOut', 'goog.fx.dom.FadeOutAndHide', 'goog.fx.dom.PredefinedEffect', 'goog.fx.dom.Resize', 'goog.fx.dom.ResizeHeight', 'goog.fx.dom.ResizeWidth', 'goog.fx.dom.Scroll', 'goog.fx.dom.Slide', 'goog.fx.dom.SlideFrom', 'goog.fx.dom.Swipe'], ['goog.color', 'goog.events', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.style', 'goog.style.bidi']);
-goog.addDependency('fx/dragdrop.js', ['goog.fx.DragDrop'], ['goog.fx.AbstractDragDrop', 'goog.fx.DragDropItem']);
-goog.addDependency('fx/dragdropgroup.js', ['goog.fx.DragDropGroup'], ['goog.dom', 'goog.fx.AbstractDragDrop', 'goog.fx.DragDropItem']);
-goog.addDependency('fx/dragdropgroup_test.js', ['goog.fx.DragDropGroupTest'], ['goog.events', 'goog.fx.DragDropGroup', 'goog.testing.jsunit']);
-goog.addDependency('fx/dragger.js', ['goog.fx.DragEvent', 'goog.fx.Dragger', 'goog.fx.Dragger.EventType'], ['goog.dom', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.style', 'goog.style.bidi', 'goog.userAgent']);
-goog.addDependency('fx/dragger_test.js', ['goog.fx.DraggerTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.Event', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Rect', 'goog.style.bidi', 'goog.testing.StrictMock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('fx/draglistgroup.js', ['goog.fx.DragListDirection', 'goog.fx.DragListGroup', 'goog.fx.DragListGroup.EventType', 'goog.fx.DragListGroupEvent'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Coordinate', 'goog.string', 'goog.style']);
-goog.addDependency('fx/draglistgroup_test.js', ['goog.fx.DragListGroupTest'], ['goog.array', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.events.Event', 'goog.events.EventType', 'goog.fx.DragEvent', 'goog.fx.DragListDirection', 'goog.fx.DragListGroup', 'goog.fx.Dragger', 'goog.math.Coordinate', 'goog.object', 'goog.testing.events', 'goog.testing.jsunit']);
-goog.addDependency('fx/dragscrollsupport.js', ['goog.fx.DragScrollSupport'], ['goog.Disposable', 'goog.Timer', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.style']);
-goog.addDependency('fx/dragscrollsupport_test.js', ['goog.fx.DragScrollSupportTest'], ['goog.fx.DragScrollSupport', 'goog.math.Coordinate', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit']);
-goog.addDependency('fx/easing.js', ['goog.fx.easing'], []);
-goog.addDependency('fx/easing_test.js', ['goog.fx.easingTest'], ['goog.fx.easing', 'goog.testing.jsunit']);
-goog.addDependency('fx/fx.js', ['goog.fx'], ['goog.asserts', 'goog.fx.Animation', 'goog.fx.Animation.EventType', 'goog.fx.Animation.State', 'goog.fx.AnimationEvent', 'goog.fx.Transition.EventType', 'goog.fx.easing']);
-goog.addDependency('fx/fx_test.js', ['goog.fxTest'], ['goog.fx.Animation', 'goog.object', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('fx/transition.js', ['goog.fx.Transition', 'goog.fx.Transition.EventType'], []);
-goog.addDependency('fx/transitionbase.js', ['goog.fx.TransitionBase', 'goog.fx.TransitionBase.State'], ['goog.events.EventTarget', 'goog.fx.Transition', 'goog.fx.Transition.EventType']);
-goog.addDependency('graphics/abstractgraphics.js', ['goog.graphics.AbstractGraphics'], ['goog.dom', 'goog.graphics.Path', 'goog.math.Coordinate', 'goog.math.Size', 'goog.style', 'goog.ui.Component']);
-goog.addDependency('graphics/affinetransform.js', ['goog.graphics.AffineTransform'], ['goog.math']);
-goog.addDependency('graphics/canvaselement.js', ['goog.graphics.CanvasEllipseElement', 'goog.graphics.CanvasGroupElement', 'goog.graphics.CanvasImageElement', 'goog.graphics.CanvasPathElement', 'goog.graphics.CanvasRectElement', 'goog.graphics.CanvasTextElement'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.graphics.EllipseElement', 'goog.graphics.GroupElement', 'goog.graphics.ImageElement', 'goog.graphics.Path', 'goog.graphics.PathElement', 'goog.graphics.RectElement', 'goog.graphics.TextElement', 'goog.math', 'goog.string']);
-goog.addDependency('graphics/canvasgraphics.js', ['goog.graphics.CanvasGraphics'], ['goog.events.EventType', 'goog.graphics.AbstractGraphics', 'goog.graphics.CanvasEllipseElement', 'goog.graphics.CanvasGroupElement', 'goog.graphics.CanvasImageElement', 'goog.graphics.CanvasPathElement', 'goog.graphics.CanvasRectElement', 'goog.graphics.CanvasTextElement', 'goog.graphics.SolidFill', 'goog.math.Size', 'goog.style']);
-goog.addDependency('graphics/element.js', ['goog.graphics.Element'], ['goog.asserts', 'goog.events', 'goog.events.EventTarget', 'goog.events.Listenable', 'goog.graphics.AffineTransform', 'goog.math']);
-goog.addDependency('graphics/ellipseelement.js', ['goog.graphics.EllipseElement'], ['goog.graphics.StrokeAndFillElement']);
-goog.addDependency('graphics/ext/coordinates.js', ['goog.graphics.ext.coordinates'], ['goog.string']);
-goog.addDependency('graphics/ext/element.js', ['goog.graphics.ext.Element'], ['goog.events.EventTarget', 'goog.functions', 'goog.graphics.ext.coordinates']);
-goog.addDependency('graphics/ext/ellipse.js', ['goog.graphics.ext.Ellipse'], ['goog.graphics.ext.StrokeAndFillElement']);
-goog.addDependency('graphics/ext/ext.js', ['goog.graphics.ext'], ['goog.graphics.ext.Ellipse', 'goog.graphics.ext.Graphics', 'goog.graphics.ext.Group', 'goog.graphics.ext.Image', 'goog.graphics.ext.Rectangle', 'goog.graphics.ext.Shape', 'goog.graphics.ext.coordinates']);
-goog.addDependency('graphics/ext/graphics.js', ['goog.graphics.ext.Graphics'], ['goog.events.EventType', 'goog.graphics.ext.Group']);
-goog.addDependency('graphics/ext/group.js', ['goog.graphics.ext.Group'], ['goog.array', 'goog.graphics.ext.Element']);
-goog.addDependency('graphics/ext/image.js', ['goog.graphics.ext.Image'], ['goog.graphics.ext.Element']);
-goog.addDependency('graphics/ext/path.js', ['goog.graphics.ext.Path'], ['goog.graphics.AffineTransform', 'goog.graphics.Path', 'goog.math', 'goog.math.Rect']);
-goog.addDependency('graphics/ext/rectangle.js', ['goog.graphics.ext.Rectangle'], ['goog.graphics.ext.StrokeAndFillElement']);
-goog.addDependency('graphics/ext/shape.js', ['goog.graphics.ext.Shape'], ['goog.graphics.ext.Path', 'goog.graphics.ext.StrokeAndFillElement', 'goog.math.Rect']);
-goog.addDependency('graphics/ext/strokeandfillelement.js', ['goog.graphics.ext.StrokeAndFillElement'], ['goog.graphics.ext.Element']);
-goog.addDependency('graphics/fill.js', ['goog.graphics.Fill'], []);
-goog.addDependency('graphics/font.js', ['goog.graphics.Font'], []);
-goog.addDependency('graphics/graphics.js', ['goog.graphics'], ['goog.dom', 'goog.graphics.CanvasGraphics', 'goog.graphics.SvgGraphics', 'goog.graphics.VmlGraphics', 'goog.userAgent']);
-goog.addDependency('graphics/groupelement.js', ['goog.graphics.GroupElement'], ['goog.graphics.Element']);
-goog.addDependency('graphics/imageelement.js', ['goog.graphics.ImageElement'], ['goog.graphics.Element']);
-goog.addDependency('graphics/lineargradient.js', ['goog.graphics.LinearGradient'], ['goog.asserts', 'goog.graphics.Fill']);
-goog.addDependency('graphics/path.js', ['goog.graphics.Path', 'goog.graphics.Path.Segment'], ['goog.array', 'goog.math']);
-goog.addDependency('graphics/pathelement.js', ['goog.graphics.PathElement'], ['goog.graphics.StrokeAndFillElement']);
-goog.addDependency('graphics/paths.js', ['goog.graphics.paths'], ['goog.graphics.Path', 'goog.math.Coordinate']);
-goog.addDependency('graphics/rectelement.js', ['goog.graphics.RectElement'], ['goog.graphics.StrokeAndFillElement']);
-goog.addDependency('graphics/solidfill.js', ['goog.graphics.SolidFill'], ['goog.graphics.Fill']);
-goog.addDependency('graphics/stroke.js', ['goog.graphics.Stroke'], []);
-goog.addDependency('graphics/strokeandfillelement.js', ['goog.graphics.StrokeAndFillElement'], ['goog.graphics.Element']);
-goog.addDependency('graphics/svgelement.js', ['goog.graphics.SvgEllipseElement', 'goog.graphics.SvgGroupElement', 'goog.graphics.SvgImageElement', 'goog.graphics.SvgPathElement', 'goog.graphics.SvgRectElement', 'goog.graphics.SvgTextElement'], ['goog.dom', 'goog.graphics.EllipseElement', 'goog.graphics.GroupElement', 'goog.graphics.ImageElement', 'goog.graphics.PathElement', 'goog.graphics.RectElement', 'goog.graphics.TextElement']);
-goog.addDependency('graphics/svggraphics.js', ['goog.graphics.SvgGraphics'], ['goog.Timer', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.graphics.AbstractGraphics', 'goog.graphics.LinearGradient', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.graphics.Stroke', 'goog.graphics.SvgEllipseElement', 'goog.graphics.SvgGroupElement', 'goog.graphics.SvgImageElement', 'goog.graphics.SvgPathElement', 'goog.graphics.SvgRectElement', 'goog.graphics.SvgTextElement', 'goog.math', 'goog.math.Size', 'goog.style', 'goog.userAgent']);
-goog.addDependency('graphics/textelement.js', ['goog.graphics.TextElement'], ['goog.graphics.StrokeAndFillElement']);
-goog.addDependency('graphics/vmlelement.js', ['goog.graphics.VmlEllipseElement', 'goog.graphics.VmlGroupElement', 'goog.graphics.VmlImageElement', 'goog.graphics.VmlPathElement', 'goog.graphics.VmlRectElement', 'goog.graphics.VmlTextElement'], ['goog.dom', 'goog.graphics.EllipseElement', 'goog.graphics.GroupElement', 'goog.graphics.ImageElement', 'goog.graphics.PathElement', 'goog.graphics.RectElement', 'goog.graphics.TextElement']);
-goog.addDependency('graphics/vmlgraphics.js', ['goog.graphics.VmlGraphics'], ['goog.array', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.graphics.AbstractGraphics', 'goog.graphics.LinearGradient', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.graphics.VmlEllipseElement', 'goog.graphics.VmlGroupElement', 'goog.graphics.VmlImageElement', 'goog.graphics.VmlPathElement', 'goog.graphics.VmlRectElement', 'goog.graphics.VmlTextElement', 'goog.math', 'goog.math.Size', 'goog.string', 'goog.style']);
-goog.addDependency('history/event.js', ['goog.history.Event'], ['goog.events.Event', 'goog.history.EventType']);
-goog.addDependency('history/eventtype.js', ['goog.history.EventType'], []);
-goog.addDependency('history/history.js', ['goog.History', 'goog.History.Event', 'goog.History.EventType'], ['goog.Timer', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.history.Event', 'goog.history.EventType', 'goog.labs.userAgent.device', 'goog.memoize', 'goog.string', 'goog.userAgent']);
-goog.addDependency('history/history_test.js', ['goog.HistoryTest'], ['goog.History', 'goog.dispose', 'goog.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('history/html5history.js', ['goog.history.Html5History', 'goog.history.Html5History.TokenTransformer'], ['goog.asserts', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.history.Event', 'goog.history.EventType']);
-goog.addDependency('history/html5history_test.js', ['goog.history.Html5HistoryTest'], ['goog.history.Html5History', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.mockmatchers']);
-goog.addDependency('html/legacyconversions.js', ['goog.html.legacyconversions'], ['goog.html.SafeHtml', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl']);
-goog.addDependency('html/legacyconversions_test.js', ['goog.html.legacyconversionsTest'], ['goog.html.SafeHtml', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.legacyconversions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('html/safehtml.js', ['goog.html.SafeHtml'], ['goog.array', 'goog.asserts', 'goog.dom.tags', 'goog.html.SafeStyle', 'goog.html.SafeUrl', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.object', 'goog.string', 'goog.string.Const', 'goog.string.TypedString']);
-goog.addDependency('html/safehtml_test.js', ['goog.html.safeHtmlTest'], ['goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.html.SafeUrl', 'goog.html.testing', 'goog.i18n.bidi.Dir', 'goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('html/safestyle.js', ['goog.html.SafeStyle'], ['goog.array', 'goog.asserts', 'goog.string', 'goog.string.Const', 'goog.string.TypedString']);
-goog.addDependency('html/safestyle_test.js', ['goog.html.safeStyleTest'], ['goog.html.SafeStyle', 'goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('html/safeurl.js', ['goog.html.SafeUrl'], ['goog.asserts', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.string.Const', 'goog.string.TypedString']);
-goog.addDependency('html/safeurl_test.js', ['goog.html.safeUrlTest'], ['goog.html.SafeUrl', 'goog.i18n.bidi.Dir', 'goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('html/testing.js', ['goog.html.testing'], ['goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl']);
-goog.addDependency('html/trustedresourceurl.js', ['goog.html.TrustedResourceUrl'], ['goog.asserts', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.string.Const', 'goog.string.TypedString']);
-goog.addDependency('html/trustedresourceurl_test.js', ['goog.html.trustedResourceUrlTest'], ['goog.html.TrustedResourceUrl', 'goog.i18n.bidi.Dir', 'goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('html/uncheckedconversions.js', ['goog.html.uncheckedconversions'], ['goog.asserts', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.string', 'goog.string.Const']);
-goog.addDependency('html/uncheckedconversions_test.js', ['goog.html.uncheckedconversionsTest'], ['goog.html.SafeHtml', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.uncheckedconversions', 'goog.i18n.bidi.Dir', 'goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('html/utils.js', ['goog.html.utils'], ['goog.string']);
-goog.addDependency('html/utils_test.js', ['goog.html.UtilsTest'], ['goog.array', 'goog.dom.TagName', 'goog.html.utils', 'goog.object', 'goog.testing.jsunit']);
-goog.addDependency('i18n/bidi.js', ['goog.i18n.bidi', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.i18n.bidi.Format'], []);
-goog.addDependency('i18n/bidi_test.js', ['goog.i18n.bidiTest'], ['goog.i18n.bidi', 'goog.i18n.bidi.Dir', 'goog.testing.jsunit']);
-goog.addDependency('i18n/bidiformatter.js', ['goog.i18n.BidiFormatter'], ['goog.html.SafeHtml', 'goog.html.legacyconversions', 'goog.i18n.bidi', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.Format']);
-goog.addDependency('i18n/bidiformatter_test.js', ['goog.i18n.BidiFormatterTest'], ['goog.html.SafeHtml', 'goog.i18n.BidiFormatter', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.Format', 'goog.testing.jsunit']);
-goog.addDependency('i18n/charlistdecompressor.js', ['goog.i18n.CharListDecompressor'], ['goog.array', 'goog.i18n.uChar']);
-goog.addDependency('i18n/charlistdecompressor_test.js', ['goog.i18n.CharListDecompressorTest'], ['goog.i18n.CharListDecompressor', 'goog.testing.jsunit']);
-goog.addDependency('i18n/charpickerdata.js', ['goog.i18n.CharPickerData'], []);
-goog.addDependency('i18n/collation.js', ['goog.i18n.collation'], []);
-goog.addDependency('i18n/collation_test.js', ['goog.i18n.collationTest'], ['goog.i18n.collation', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('i18n/compactnumberformatsymbols.js', ['goog.i18n.CompactNumberFormatSymbols', 'goog.i18n.CompactNumberFormatSymbols_af', 'goog.i18n.CompactNumberFormatSymbols_af_ZA', 'goog.i18n.CompactNumberFormatSymbols_am', 'goog.i18n.CompactNumberFormatSymbols_am_ET', 'goog.i18n.CompactNumberFormatSymbols_ar', 'goog.i18n.CompactNumberFormatSymbols_ar_001', 'goog.i18n.CompactNumberFormatSymbols_az', 'goog.i18n.CompactNumberFormatSymbols_az_Cyrl_AZ', 'goog.i18n.CompactNumberFormatSymbols_az_Latn_AZ', 'goog.i18n.CompactNumberFormatSymbols_bg', 'goog.i18n.CompactNumberFormatSymbols_bg_BG', 'goog.i18n.CompactNumberFormatSymbols_bn', 'goog.i18n.CompactNumberFormatSymbols_bn_BD', 'goog.i18n.CompactNumberFormatSymbols_br', 'goog.i18n.CompactNumberFormatSymbols_br_FR', 'goog.i18n.CompactNumberFormatSymbols_ca', 'goog.i18n.CompactNumberFormatSymbols_ca_AD', 'goog.i18n.CompactNumberFormatSymbols_ca_ES', 'goog.i18n.CompactNumberFormatSymbols_ca_ES_VALENCIA', 'goog.i18n.CompactNumberFormatSymbols_ca_FR', 'goog.i18n.CompactNumberFormatSymbols_ca_IT', 'goog.i18n.CompactNumberFormatSymbols_chr', 'goog.i18n.CompactNumberFormatSymbols_chr_US', 'goog.i18n.CompactNumberFormatSymbols_cs', 'goog.i18n.CompactNumberFormatSymbols_cs_CZ', 'goog.i18n.CompactNumberFormatSymbols_cy', 'goog.i18n.CompactNumberFormatSymbols_cy_GB', 'goog.i18n.CompactNumberFormatSymbols_da', 'goog.i18n.CompactNumberFormatSymbols_da_DK', 'goog.i18n.CompactNumberFormatSymbols_da_GL', 'goog.i18n.CompactNumberFormatSymbols_de', 'goog.i18n.CompactNumberFormatSymbols_de_AT', 'goog.i18n.CompactNumberFormatSymbols_de_BE', 'goog.i18n.CompactNumberFormatSymbols_de_CH', 'goog.i18n.CompactNumberFormatSymbols_de_DE', 'goog.i18n.CompactNumberFormatSymbols_de_LU', 'goog.i18n.CompactNumberFormatSymbols_el', 'goog.i18n.CompactNumberFormatSymbols_el_GR', 'goog.i18n.CompactNumberFormatSymbols_en', 'goog.i18n.CompactNumberFormatSymbols_en_001', 'goog.i18n.CompactNumberFormatSymbols_en_AS', 'goog.i18n.CompactNumberFormatSymbols_en_AU', 'goog.i18n.CompactNumberFormatSymbols_en_DG', 'goog.i18n.CompactNumberFormatSymbols_en_FM', 'goog.i18n.CompactNumberFormatSymbols_en_GB', 'goog.i18n.CompactNumberFormatSymbols_en_GU', 'goog.i18n.CompactNumberFormatSymbols_en_IE', 'goog.i18n.CompactNumberFormatSymbols_en_IN', 'goog.i18n.CompactNumberFormatSymbols_en_IO', 'goog.i18n.CompactNumberFormatSymbols_en_MH', 'goog.i18n.CompactNumberFormatSymbols_en_MP', 'goog.i18n.CompactNumberFormatSymbols_en_PR', 'goog.i18n.CompactNumberFormatSymbols_en_PW', 'goog.i18n.CompactNumberFormatSymbols_en_SG', 'goog.i18n.CompactNumberFormatSymbols_en_TC', 'goog.i18n.CompactNumberFormatSymbols_en_UM', 'goog.i18n.CompactNumberFormatSymbols_en_US', 'goog.i18n.CompactNumberFormatSymbols_en_VG', 'goog.i18n.CompactNumberFormatSymbols_en_VI', 'goog.i18n.CompactNumberFormatSymbols_en_ZA', 'goog.i18n.CompactNumberFormatSymbols_en_ZW', 'goog.i18n.CompactNumberFormatSymbols_es', 'goog.i18n.CompactNumberFormatSymbols_es_419', 'goog.i18n.CompactNumberFormatSymbols_es_EA', 'goog.i18n.CompactNumberFormatSymbols_es_ES', 'goog.i18n.CompactNumberFormatSymbols_es_IC', 'goog.i18n.CompactNumberFormatSymbols_et', 'goog.i18n.CompactNumberFormatSymbols_et_EE', 'goog.i18n.CompactNumberFormatSymbols_eu', 'goog.i18n.CompactNumberFormatSymbols_eu_ES', 'goog.i18n.CompactNumberFormatSymbols_fa', 'goog.i18n.CompactNumberFormatSymbols_fa_IR', 'goog.i18n.CompactNumberFormatSymbols_fi', 'goog.i18n.CompactNumberFormatSymbols_fi_FI', 'goog.i18n.CompactNumberFormatSymbols_fil', 'goog.i18n.CompactNumberFormatSymbols_fil_PH', 'goog.i18n.CompactNumberFormatSymbols_fr', 'goog.i18n.CompactNumberFormatSymbols_fr_BL', 'goog.i18n.CompactNumberFormatSymbols_fr_CA', 'goog.i18n.CompactNumberFormatSymbols_fr_FR', 'goog.i18n.CompactNumberFormatSymbols_fr_GF', 'goog.i18n.CompactNumberFormatSymbols_fr_GP', 'goog.i18n.CompactNumberFormatSymbols_fr_MC', 'goog.i18n.CompactNumberFormatSymbols_fr_MF', 'goog.i18n.CompactNumberFormatSymbols_fr_MQ', 'goog.i18n.CompactNumberFormatSymbols_fr_PM', 'goog.i18n.CompactNumberFormatSymbols_fr_RE', 'goog.i18n.CompactNumberFormatSymbols_fr_YT', 'goog.i18n.CompactNumberFormatSymbols_gl', 'goog.i18n.CompactNumberFormatSymbols_gl_ES', 'goog.i18n.CompactNumberFormatSymbols_gsw', 'goog.i18n.CompactNumberFormatSymbols_gsw_CH', 'goog.i18n.CompactNumberFormatSymbols_gsw_LI', 'goog.i18n.CompactNumberFormatSymbols_gu', 'goog.i18n.CompactNumberFormatSymbols_gu_IN', 'goog.i18n.CompactNumberFormatSymbols_haw', 'goog.i18n.CompactNumberFormatSymbols_haw_US', 'goog.i18n.CompactNumberFormatSymbols_he', 'goog.i18n.CompactNumberFormatSymbols_he_IL', 'goog.i18n.CompactNumberFormatSymbols_hi', 'goog.i18n.CompactNumberFormatSymbols_hi_IN', 'goog.i18n.CompactNumberFormatSymbols_hr', 'goog.i18n.CompactNumberFormatSymbols_hr_HR', 'goog.i18n.CompactNumberFormatSymbols_hu', 'goog.i18n.CompactNumberFormatSymbols_hu_HU', 'goog.i18n.CompactNumberFormatSymbols_hy', 'goog.i18n.CompactNumberFormatSymbols_hy_AM', 'goog.i18n.CompactNumberFormatSymbols_id', 'goog.i18n.CompactNumberFormatSymbols_id_ID', 'goog.i18n.CompactNumberFormatSymbols_in', 'goog.i18n.CompactNumberFormatSymbols_is', 'goog.i18n.CompactNumberFormatSymbols_is_IS', 'goog.i18n.CompactNumberFormatSymbols_it', 'goog.i18n.CompactNumberFormatSymbols_it_IT', 'goog.i18n.CompactNumberFormatSymbols_it_SM', 'goog.i18n.CompactNumberFormatSymbols_iw', 'goog.i18n.CompactNumberFormatSymbols_ja', 'goog.i18n.CompactNumberFormatSymbols_ja_JP', 'goog.i18n.CompactNumberFormatSymbols_ka', 'goog.i18n.CompactNumberFormatSymbols_ka_GE', 'goog.i18n.CompactNumberFormatSymbols_kk', 'goog.i18n.CompactNumberFormatSymbols_kk_Cyrl_KZ', 'goog.i18n.CompactNumberFormatSymbols_km', 'goog.i18n.CompactNumberFormatSymbols_km_KH', 'goog.i18n.CompactNumberFormatSymbols_kn', 'goog.i18n.CompactNumberFormatSymbols_kn_IN', 'goog.i18n.CompactNumberFormatSymbols_ko', 'goog.i18n.CompactNumberFormatSymbols_ko_KR', 'goog.i18n.CompactNumberFormatSymbols_ky', 'goog.i18n.CompactNumberFormatSymbols_ky_Cyrl_KG', 'goog.i18n.CompactNumberFormatSymbols_ln', 'goog.i18n.CompactNumberFormatSymbols_ln_CD', 'goog.i18n.CompactNumberFormatSymbols_lo', 'goog.i18n.CompactNumberFormatSymbols_lo_LA', 'goog.i18n.CompactNumberFormatSymbols_lt', 'goog.i18n.CompactNumberFormatSymbols_lt_LT', 'goog.i18n.CompactNumberFormatSymbols_lv', 'goog.i18n.CompactNumberFormatSymbols_lv_LV', 'goog.i18n.CompactNumberFormatSymbols_mk', 'goog.i18n.CompactNumberFormatSymbols_mk_MK', 'goog.i18n.CompactNumberFormatSymbols_ml', 'goog.i18n.CompactNumberFormatSymbols_ml_IN', 'goog.i18n.CompactNumberFormatSymbols_mn', 'goog.i18n.CompactNumberFormatSymbols_mn_Cyrl_MN', 'goog.i18n.CompactNumberFormatSymbols_mr', 'goog.i18n.CompactNumberFormatSymbols_mr_IN', 'goog.i18n.CompactNumberFormatSymbols_ms', 'goog.i18n.CompactNumberFormatSymbols_ms_Latn_MY', 'goog.i18n.CompactNumberFormatSymbols_mt', 'goog.i18n.CompactNumberFormatSymbols_mt_MT', 'goog.i18n.CompactNumberFormatSymbols_my', 'goog.i18n.CompactNumberFormatSymbols_my_MM', 'goog.i18n.CompactNumberFormatSymbols_nb', 'goog.i18n.CompactNumberFormatSymbols_nb_NO', 'goog.i18n.CompactNumberFormatSymbols_nb_SJ', 'goog.i18n.CompactNumberFormatSymbols_ne', 'goog.i18n.CompactNumberFormatSymbols_ne_NP', 'goog.i18n.CompactNumberFormatSymbols_nl', 'goog.i18n.CompactNumberFormatSymbols_nl_NL', 'goog.i18n.CompactNumberFormatSymbols_no', 'goog.i18n.CompactNumberFormatSymbols_no_NO', 'goog.i18n.CompactNumberFormatSymbols_or', 'goog.i18n.CompactNumberFormatSymbols_or_IN', 'goog.i18n.CompactNumberFormatSymbols_pa', 'goog.i18n.CompactNumberFormatSymbols_pa_Guru_IN', 'goog.i18n.CompactNumberFormatSymbols_pl', 'goog.i18n.CompactNumberFormatSymbols_pl_PL', 'goog.i18n.CompactNumberFormatSymbols_pt', 'goog.i18n.CompactNumberFormatSymbols_pt_BR', 'goog.i18n.CompactNumberFormatSymbols_pt_PT', 'goog.i18n.CompactNumberFormatSymbols_ro', 'goog.i18n.CompactNumberFormatSymbols_ro_RO', 'goog.i18n.CompactNumberFormatSymbols_ru', 'goog.i18n.CompactNumberFormatSymbols_ru_RU', 'goog.i18n.CompactNumberFormatSymbols_si', 'goog.i18n.CompactNumberFormatSymbols_si_LK', 'goog.i18n.CompactNumberFormatSymbols_sk', 'goog.i18n.CompactNumberFormatSymbols_sk_SK', 'goog.i18n.CompactNumberFormatSymbols_sl', 'goog.i18n.CompactNumberFormatSymbols_sl_SI', 'goog.i18n.CompactNumberFormatSymbols_sq', 'goog.i18n.CompactNumberFormatSymbols_sq_AL', 'goog.i18n.CompactNumberFormatSymbols_sr', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_RS', 'goog.i18n.CompactNumberFormatSymbols_sv', 'goog.i18n.CompactNumberFormatSymbols_sv_SE', 'goog.i18n.CompactNumberFormatSymbols_sw', 'goog.i18n.CompactNumberFormatSymbols_sw_TZ', 'goog.i18n.CompactNumberFormatSymbols_ta', 'goog.i18n.CompactNumberFormatSymbols_ta_IN', 'goog.i18n.CompactNumberFormatSymbols_te', 'goog.i18n.CompactNumberFormatSymbols_te_IN', 'goog.i18n.CompactNumberFormatSymbols_th', 'goog.i18n.CompactNumberFormatSymbols_th_TH', 'goog.i18n.CompactNumberFormatSymbols_tl', 'goog.i18n.CompactNumberFormatSymbols_tr', 'goog.i18n.CompactNumberFormatSymbols_tr_TR', 'goog.i18n.CompactNumberFormatSymbols_uk', 'goog.i18n.CompactNumberFormatSymbols_uk_UA', 'goog.i18n.CompactNumberFormatSymbols_ur', 'goog.i18n.CompactNumberFormatSymbols_ur_PK', 'goog.i18n.CompactNumberFormatSymbols_uz', 'goog.i18n.CompactNumberFormatSymbols_uz_Latn_UZ', 'goog.i18n.CompactNumberFormatSymbols_vi', 'goog.i18n.CompactNumberFormatSymbols_vi_VN', 'goog.i18n.CompactNumberFormatSymbols_zh', 'goog.i18n.CompactNumberFormatSymbols_zh_CN', 'goog.i18n.CompactNumberFormatSymbols_zh_HK', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_CN', 'goog.i18n.CompactNumberFormatSymbols_zh_TW', 'goog.i18n.CompactNumberFormatSymbols_zu', 'goog.i18n.CompactNumberFormatSymbols_zu_ZA'], []);
-goog.addDependency('i18n/compactnumberformatsymbols_ext.js', ['goog.i18n.CompactNumberFormatSymbolsExt', 'goog.i18n.CompactNumberFormatSymbols_aa', 'goog.i18n.CompactNumberFormatSymbols_aa_DJ', 'goog.i18n.CompactNumberFormatSymbols_aa_ER', 'goog.i18n.CompactNumberFormatSymbols_aa_ET', 'goog.i18n.CompactNumberFormatSymbols_af_NA', 'goog.i18n.CompactNumberFormatSymbols_agq', 'goog.i18n.CompactNumberFormatSymbols_agq_CM', 'goog.i18n.CompactNumberFormatSymbols_ak', 'goog.i18n.CompactNumberFormatSymbols_ak_GH', 'goog.i18n.CompactNumberFormatSymbols_ar_AE', 'goog.i18n.CompactNumberFormatSymbols_ar_BH', 'goog.i18n.CompactNumberFormatSymbols_ar_DJ', 'goog.i18n.CompactNumberFormatSymbols_ar_DZ', 'goog.i18n.CompactNumberFormatSymbols_ar_EG', 'goog.i18n.CompactNumberFormatSymbols_ar_EH', 'goog.i18n.CompactNumberFormatSymbols_ar_ER', 'goog.i18n.CompactNumberFormatSymbols_ar_IL', 'goog.i18n.CompactNumberFormatSymbols_ar_IQ', 'goog.i18n.CompactNumberFormatSymbols_ar_JO', 'goog.i18n.CompactNumberFormatSymbols_ar_KM', 'goog.i18n.CompactNumberFormatSymbols_ar_KW', 'goog.i18n.CompactNumberFormatSymbols_ar_LB', 'goog.i18n.CompactNumberFormatSymbols_ar_LY', 'goog.i18n.CompactNumberFormatSymbols_ar_MA', 'goog.i18n.CompactNumberFormatSymbols_ar_MR', 'goog.i18n.CompactNumberFormatSymbols_ar_OM', 'goog.i18n.CompactNumberFormatSymbols_ar_PS', 'goog.i18n.CompactNumberFormatSymbols_ar_QA', 'goog.i18n.CompactNumberFormatSymbols_ar_SA', 'goog.i18n.CompactNumberFormatSymbols_ar_SD', 'goog.i18n.CompactNumberFormatSymbols_ar_SO', 'goog.i18n.CompactNumberFormatSymbols_ar_SS', 'goog.i18n.CompactNumberFormatSymbols_ar_SY', 'goog.i18n.CompactNumberFormatSymbols_ar_TD', 'goog.i18n.CompactNumberFormatSymbols_ar_TN', 'goog.i18n.CompactNumberFormatSymbols_ar_YE', 'goog.i18n.CompactNumberFormatSymbols_as', 'goog.i18n.CompactNumberFormatSymbols_as_IN', 'goog.i18n.CompactNumberFormatSymbols_asa', 'goog.i18n.CompactNumberFormatSymbols_asa_TZ', 'goog.i18n.CompactNumberFormatSymbols_ast', 'goog.i18n.CompactNumberFormatSymbols_ast_ES', 'goog.i18n.CompactNumberFormatSymbols_az_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_az_Latn', 'goog.i18n.CompactNumberFormatSymbols_bas', 'goog.i18n.CompactNumberFormatSymbols_bas_CM', 'goog.i18n.CompactNumberFormatSymbols_be', 'goog.i18n.CompactNumberFormatSymbols_be_BY', 'goog.i18n.CompactNumberFormatSymbols_bem', 'goog.i18n.CompactNumberFormatSymbols_bem_ZM', 'goog.i18n.CompactNumberFormatSymbols_bez', 'goog.i18n.CompactNumberFormatSymbols_bez_TZ', 'goog.i18n.CompactNumberFormatSymbols_bm', 'goog.i18n.CompactNumberFormatSymbols_bm_ML', 'goog.i18n.CompactNumberFormatSymbols_bn_IN', 'goog.i18n.CompactNumberFormatSymbols_bo', 'goog.i18n.CompactNumberFormatSymbols_bo_CN', 'goog.i18n.CompactNumberFormatSymbols_bo_IN', 'goog.i18n.CompactNumberFormatSymbols_brx', 'goog.i18n.CompactNumberFormatSymbols_brx_IN', 'goog.i18n.CompactNumberFormatSymbols_bs', 'goog.i18n.CompactNumberFormatSymbols_bs_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_bs_Cyrl_BA', 'goog.i18n.CompactNumberFormatSymbols_bs_Latn', 'goog.i18n.CompactNumberFormatSymbols_bs_Latn_BA', 'goog.i18n.CompactNumberFormatSymbols_byn', 'goog.i18n.CompactNumberFormatSymbols_byn_ER', 'goog.i18n.CompactNumberFormatSymbols_cgg', 'goog.i18n.CompactNumberFormatSymbols_cgg_UG', 'goog.i18n.CompactNumberFormatSymbols_ckb', 'goog.i18n.CompactNumberFormatSymbols_ckb_Arab', 'goog.i18n.CompactNumberFormatSymbols_ckb_Arab_IQ', 'goog.i18n.CompactNumberFormatSymbols_ckb_Arab_IR', 'goog.i18n.CompactNumberFormatSymbols_ckb_IQ', 'goog.i18n.CompactNumberFormatSymbols_ckb_IR', 'goog.i18n.CompactNumberFormatSymbols_ckb_Latn', 'goog.i18n.CompactNumberFormatSymbols_ckb_Latn_IQ', 'goog.i18n.CompactNumberFormatSymbols_dav', 'goog.i18n.CompactNumberFormatSymbols_dav_KE', 'goog.i18n.CompactNumberFormatSymbols_de_LI', 'goog.i18n.CompactNumberFormatSymbols_dje', 'goog.i18n.CompactNumberFormatSymbols_dje_NE', 'goog.i18n.CompactNumberFormatSymbols_dua', 'goog.i18n.CompactNumberFormatSymbols_dua_CM', 'goog.i18n.CompactNumberFormatSymbols_dyo', 'goog.i18n.CompactNumberFormatSymbols_dyo_SN', 'goog.i18n.CompactNumberFormatSymbols_dz', 'goog.i18n.CompactNumberFormatSymbols_dz_BT', 'goog.i18n.CompactNumberFormatSymbols_ebu', 'goog.i18n.CompactNumberFormatSymbols_ebu_KE', 'goog.i18n.CompactNumberFormatSymbols_ee', 'goog.i18n.CompactNumberFormatSymbols_ee_GH', 'goog.i18n.CompactNumberFormatSymbols_ee_TG', 'goog.i18n.CompactNumberFormatSymbols_el_CY', 'goog.i18n.CompactNumberFormatSymbols_en_150', 'goog.i18n.CompactNumberFormatSymbols_en_AG', 'goog.i18n.CompactNumberFormatSymbols_en_AI', 'goog.i18n.CompactNumberFormatSymbols_en_BB', 'goog.i18n.CompactNumberFormatSymbols_en_BE', 'goog.i18n.CompactNumberFormatSymbols_en_BM', 'goog.i18n.CompactNumberFormatSymbols_en_BS', 'goog.i18n.CompactNumberFormatSymbols_en_BW', 'goog.i18n.CompactNumberFormatSymbols_en_BZ', 'goog.i18n.CompactNumberFormatSymbols_en_CA', 'goog.i18n.CompactNumberFormatSymbols_en_CC', 'goog.i18n.CompactNumberFormatSymbols_en_CK', 'goog.i18n.CompactNumberFormatSymbols_en_CM', 'goog.i18n.CompactNumberFormatSymbols_en_CX', 'goog.i18n.CompactNumberFormatSymbols_en_DM', 'goog.i18n.CompactNumberFormatSymbols_en_ER', 'goog.i18n.CompactNumberFormatSymbols_en_FJ', 'goog.i18n.CompactNumberFormatSymbols_en_FK', 'goog.i18n.CompactNumberFormatSymbols_en_GD', 'goog.i18n.CompactNumberFormatSymbols_en_GG', 'goog.i18n.CompactNumberFormatSymbols_en_GH', 'goog.i18n.CompactNumberFormatSymbols_en_GI', 'goog.i18n.CompactNumberFormatSymbols_en_GM', 'goog.i18n.CompactNumberFormatSymbols_en_GY', 'goog.i18n.CompactNumberFormatSymbols_en_HK', 'goog.i18n.CompactNumberFormatSymbols_en_IM', 'goog.i18n.CompactNumberFormatSymbols_en_JE', 'goog.i18n.CompactNumberFormatSymbols_en_JM', 'goog.i18n.CompactNumberFormatSymbols_en_KE', 'goog.i18n.CompactNumberFormatSymbols_en_KI', 'goog.i18n.CompactNumberFormatSymbols_en_KN', 'goog.i18n.CompactNumberFormatSymbols_en_KY', 'goog.i18n.CompactNumberFormatSymbols_en_LC', 'goog.i18n.CompactNumberFormatSymbols_en_LR', 'goog.i18n.CompactNumberFormatSymbols_en_LS', 'goog.i18n.CompactNumberFormatSymbols_en_MG', 'goog.i18n.CompactNumberFormatSymbols_en_MO', 'goog.i18n.CompactNumberFormatSymbols_en_MS', 'goog.i18n.CompactNumberFormatSymbols_en_MT', 'goog.i18n.CompactNumberFormatSymbols_en_MU', 'goog.i18n.CompactNumberFormatSymbols_en_MW', 'goog.i18n.CompactNumberFormatSymbols_en_NA', 'goog.i18n.CompactNumberFormatSymbols_en_NF', 'goog.i18n.CompactNumberFormatSymbols_en_NG', 'goog.i18n.CompactNumberFormatSymbols_en_NR', 'goog.i18n.CompactNumberFormatSymbols_en_NU', 'goog.i18n.CompactNumberFormatSymbols_en_NZ', 'goog.i18n.CompactNumberFormatSymbols_en_PG', 'goog.i18n.CompactNumberFormatSymbols_en_PH', 'goog.i18n.CompactNumberFormatSymbols_en_PK', 'goog.i18n.CompactNumberFormatSymbols_en_PN', 'goog.i18n.CompactNumberFormatSymbols_en_RW', 'goog.i18n.CompactNumberFormatSymbols_en_SB', 'goog.i18n.CompactNumberFormatSymbols_en_SC', 'goog.i18n.CompactNumberFormatSymbols_en_SD', 'goog.i18n.CompactNumberFormatSymbols_en_SH', 'goog.i18n.CompactNumberFormatSymbols_en_SL', 'goog.i18n.CompactNumberFormatSymbols_en_SS', 'goog.i18n.CompactNumberFormatSymbols_en_SX', 'goog.i18n.CompactNumberFormatSymbols_en_SZ', 'goog.i18n.CompactNumberFormatSymbols_en_TK', 'goog.i18n.CompactNumberFormatSymbols_en_TO', 'goog.i18n.CompactNumberFormatSymbols_en_TT', 'goog.i18n.CompactNumberFormatSymbols_en_TV', 'goog.i18n.CompactNumberFormatSymbols_en_TZ', 'goog.i18n.CompactNumberFormatSymbols_en_UG', 'goog.i18n.CompactNumberFormatSymbols_en_VC', 'goog.i18n.CompactNumberFormatSymbols_en_VU', 'goog.i18n.CompactNumberFormatSymbols_en_WS', 'goog.i18n.CompactNumberFormatSymbols_en_ZM', 'goog.i18n.CompactNumberFormatSymbols_eo', 'goog.i18n.CompactNumberFormatSymbols_eo_001', 'goog.i18n.CompactNumberFormatSymbols_es_AR', 'goog.i18n.CompactNumberFormatSymbols_es_BO', 'goog.i18n.CompactNumberFormatSymbols_es_CL', 'goog.i18n.CompactNumberFormatSymbols_es_CO', 'goog.i18n.CompactNumberFormatSymbols_es_CR', 'goog.i18n.CompactNumberFormatSymbols_es_CU', 'goog.i18n.CompactNumberFormatSymbols_es_DO', 'goog.i18n.CompactNumberFormatSymbols_es_EC', 'goog.i18n.CompactNumberFormatSymbols_es_GQ', 'goog.i18n.CompactNumberFormatSymbols_es_GT', 'goog.i18n.CompactNumberFormatSymbols_es_HN', 'goog.i18n.CompactNumberFormatSymbols_es_MX', 'goog.i18n.CompactNumberFormatSymbols_es_NI', 'goog.i18n.CompactNumberFormatSymbols_es_PA', 'goog.i18n.CompactNumberFormatSymbols_es_PE', 'goog.i18n.CompactNumberFormatSymbols_es_PH', 'goog.i18n.CompactNumberFormatSymbols_es_PR', 'goog.i18n.CompactNumberFormatSymbols_es_PY', 'goog.i18n.CompactNumberFormatSymbols_es_SV', 'goog.i18n.CompactNumberFormatSymbols_es_US', 'goog.i18n.CompactNumberFormatSymbols_es_UY', 'goog.i18n.CompactNumberFormatSymbols_es_VE', 'goog.i18n.CompactNumberFormatSymbols_ewo', 'goog.i18n.CompactNumberFormatSymbols_ewo_CM', 'goog.i18n.CompactNumberFormatSymbols_fa_AF', 'goog.i18n.CompactNumberFormatSymbols_ff', 'goog.i18n.CompactNumberFormatSymbols_ff_CM', 'goog.i18n.CompactNumberFormatSymbols_ff_GN', 'goog.i18n.CompactNumberFormatSymbols_ff_MR', 'goog.i18n.CompactNumberFormatSymbols_ff_SN', 'goog.i18n.CompactNumberFormatSymbols_fo', 'goog.i18n.CompactNumberFormatSymbols_fo_FO', 'goog.i18n.CompactNumberFormatSymbols_fr_BE', 'goog.i18n.CompactNumberFormatSymbols_fr_BF', 'goog.i18n.CompactNumberFormatSymbols_fr_BI', 'goog.i18n.CompactNumberFormatSymbols_fr_BJ', 'goog.i18n.CompactNumberFormatSymbols_fr_CD', 'goog.i18n.CompactNumberFormatSymbols_fr_CF', 'goog.i18n.CompactNumberFormatSymbols_fr_CG', 'goog.i18n.CompactNumberFormatSymbols_fr_CH', 'goog.i18n.CompactNumberFormatSymbols_fr_CI', 'goog.i18n.CompactNumberFormatSymbols_fr_CM', 'goog.i18n.CompactNumberFormatSymbols_fr_DJ', 'goog.i18n.CompactNumberFormatSymbols_fr_DZ', 'goog.i18n.CompactNumberFormatSymbols_fr_GA', 'goog.i18n.CompactNumberFormatSymbols_fr_GN', 'goog.i18n.CompactNumberFormatSymbols_fr_GQ', 'goog.i18n.CompactNumberFormatSymbols_fr_HT', 'goog.i18n.CompactNumberFormatSymbols_fr_KM', 'goog.i18n.CompactNumberFormatSymbols_fr_LU', 'goog.i18n.CompactNumberFormatSymbols_fr_MA', 'goog.i18n.CompactNumberFormatSymbols_fr_MG', 'goog.i18n.CompactNumberFormatSymbols_fr_ML', 'goog.i18n.CompactNumberFormatSymbols_fr_MR', 'goog.i18n.CompactNumberFormatSymbols_fr_MU', 'goog.i18n.CompactNumberFormatSymbols_fr_NC', 'goog.i18n.CompactNumberFormatSymbols_fr_NE', 'goog.i18n.CompactNumberFormatSymbols_fr_PF', 'goog.i18n.CompactNumberFormatSymbols_fr_RW', 'goog.i18n.CompactNumberFormatSymbols_fr_SC', 'goog.i18n.CompactNumberFormatSymbols_fr_SN', 'goog.i18n.CompactNumberFormatSymbols_fr_SY', 'goog.i18n.CompactNumberFormatSymbols_fr_TD', 'goog.i18n.CompactNumberFormatSymbols_fr_TG', 'goog.i18n.CompactNumberFormatSymbols_fr_TN', 'goog.i18n.CompactNumberFormatSymbols_fr_VU', 'goog.i18n.CompactNumberFormatSymbols_fr_WF', 'goog.i18n.CompactNumberFormatSymbols_fur', 'goog.i18n.CompactNumberFormatSymbols_fur_IT', 'goog.i18n.CompactNumberFormatSymbols_fy', 'goog.i18n.CompactNumberFormatSymbols_fy_NL', 'goog.i18n.CompactNumberFormatSymbols_ga', 'goog.i18n.CompactNumberFormatSymbols_ga_IE', 'goog.i18n.CompactNumberFormatSymbols_gd', 'goog.i18n.CompactNumberFormatSymbols_gd_GB', 'goog.i18n.CompactNumberFormatSymbols_guz', 'goog.i18n.CompactNumberFormatSymbols_guz_KE', 'goog.i18n.CompactNumberFormatSymbols_gv', 'goog.i18n.CompactNumberFormatSymbols_gv_IM', 'goog.i18n.CompactNumberFormatSymbols_ha', 'goog.i18n.CompactNumberFormatSymbols_ha_Latn', 'goog.i18n.CompactNumberFormatSymbols_ha_Latn_GH', 'goog.i18n.CompactNumberFormatSymbols_ha_Latn_NE', 'goog.i18n.CompactNumberFormatSymbols_ha_Latn_NG', 'goog.i18n.CompactNumberFormatSymbols_hr_BA', 'goog.i18n.CompactNumberFormatSymbols_ia', 'goog.i18n.CompactNumberFormatSymbols_ia_FR', 'goog.i18n.CompactNumberFormatSymbols_ig', 'goog.i18n.CompactNumberFormatSymbols_ig_NG', 'goog.i18n.CompactNumberFormatSymbols_ii', 'goog.i18n.CompactNumberFormatSymbols_ii_CN', 'goog.i18n.CompactNumberFormatSymbols_it_CH', 'goog.i18n.CompactNumberFormatSymbols_jgo', 'goog.i18n.CompactNumberFormatSymbols_jgo_CM', 'goog.i18n.CompactNumberFormatSymbols_jmc', 'goog.i18n.CompactNumberFormatSymbols_jmc_TZ', 'goog.i18n.CompactNumberFormatSymbols_kab', 'goog.i18n.CompactNumberFormatSymbols_kab_DZ', 'goog.i18n.CompactNumberFormatSymbols_kam', 'goog.i18n.CompactNumberFormatSymbols_kam_KE', 'goog.i18n.CompactNumberFormatSymbols_kde', 'goog.i18n.CompactNumberFormatSymbols_kde_TZ', 'goog.i18n.CompactNumberFormatSymbols_kea', 'goog.i18n.CompactNumberFormatSymbols_kea_CV', 'goog.i18n.CompactNumberFormatSymbols_khq', 'goog.i18n.CompactNumberFormatSymbols_khq_ML', 'goog.i18n.CompactNumberFormatSymbols_ki', 'goog.i18n.CompactNumberFormatSymbols_ki_KE', 'goog.i18n.CompactNumberFormatSymbols_kk_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_kkj', 'goog.i18n.CompactNumberFormatSymbols_kkj_CM', 'goog.i18n.CompactNumberFormatSymbols_kl', 'goog.i18n.CompactNumberFormatSymbols_kl_GL', 'goog.i18n.CompactNumberFormatSymbols_kln', 'goog.i18n.CompactNumberFormatSymbols_kln_KE', 'goog.i18n.CompactNumberFormatSymbols_ko_KP', 'goog.i18n.CompactNumberFormatSymbols_kok', 'goog.i18n.CompactNumberFormatSymbols_kok_IN', 'goog.i18n.CompactNumberFormatSymbols_ks', 'goog.i18n.CompactNumberFormatSymbols_ks_Arab', 'goog.i18n.CompactNumberFormatSymbols_ks_Arab_IN', 'goog.i18n.CompactNumberFormatSymbols_ksb', 'goog.i18n.CompactNumberFormatSymbols_ksb_TZ', 'goog.i18n.CompactNumberFormatSymbols_ksf', 'goog.i18n.CompactNumberFormatSymbols_ksf_CM', 'goog.i18n.CompactNumberFormatSymbols_ksh', 'goog.i18n.CompactNumberFormatSymbols_ksh_DE', 'goog.i18n.CompactNumberFormatSymbols_kw', 'goog.i18n.CompactNumberFormatSymbols_kw_GB', 'goog.i18n.CompactNumberFormatSymbols_ky_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_lag', 'goog.i18n.CompactNumberFormatSymbols_lag_TZ', 'goog.i18n.CompactNumberFormatSymbols_lg', 'goog.i18n.CompactNumberFormatSymbols_lg_UG', 'goog.i18n.CompactNumberFormatSymbols_lkt', 'goog.i18n.CompactNumberFormatSymbols_lkt_US', 'goog.i18n.CompactNumberFormatSymbols_ln_AO', 'goog.i18n.CompactNumberFormatSymbols_ln_CF', 'goog.i18n.CompactNumberFormatSymbols_ln_CG', 'goog.i18n.CompactNumberFormatSymbols_lu', 'goog.i18n.CompactNumberFormatSymbols_lu_CD', 'goog.i18n.CompactNumberFormatSymbols_luo', 'goog.i18n.CompactNumberFormatSymbols_luo_KE', 'goog.i18n.CompactNumberFormatSymbols_luy', 'goog.i18n.CompactNumberFormatSymbols_luy_KE', 'goog.i18n.CompactNumberFormatSymbols_mas', 'goog.i18n.CompactNumberFormatSymbols_mas_KE', 'goog.i18n.CompactNumberFormatSymbols_mas_TZ', 'goog.i18n.CompactNumberFormatSymbols_mer', 'goog.i18n.CompactNumberFormatSymbols_mer_KE', 'goog.i18n.CompactNumberFormatSymbols_mfe', 'goog.i18n.CompactNumberFormatSymbols_mfe_MU', 'goog.i18n.CompactNumberFormatSymbols_mg', 'goog.i18n.CompactNumberFormatSymbols_mg_MG', 'goog.i18n.CompactNumberFormatSymbols_mgh', 'goog.i18n.CompactNumberFormatSymbols_mgh_MZ', 'goog.i18n.CompactNumberFormatSymbols_mgo', 'goog.i18n.CompactNumberFormatSymbols_mgo_CM', 'goog.i18n.CompactNumberFormatSymbols_mn_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_ms_Latn', 'goog.i18n.CompactNumberFormatSymbols_ms_Latn_BN', 'goog.i18n.CompactNumberFormatSymbols_ms_Latn_SG', 'goog.i18n.CompactNumberFormatSymbols_mua', 'goog.i18n.CompactNumberFormatSymbols_mua_CM', 'goog.i18n.CompactNumberFormatSymbols_naq', 'goog.i18n.CompactNumberFormatSymbols_naq_NA', 'goog.i18n.CompactNumberFormatSymbols_nd', 'goog.i18n.CompactNumberFormatSymbols_nd_ZW', 'goog.i18n.CompactNumberFormatSymbols_ne_IN', 'goog.i18n.CompactNumberFormatSymbols_nl_AW', 'goog.i18n.CompactNumberFormatSymbols_nl_BE', 'goog.i18n.CompactNumberFormatSymbols_nl_BQ', 'goog.i18n.CompactNumberFormatSymbols_nl_CW', 'goog.i18n.CompactNumberFormatSymbols_nl_SR', 'goog.i18n.CompactNumberFormatSymbols_nl_SX', 'goog.i18n.CompactNumberFormatSymbols_nmg', 'goog.i18n.CompactNumberFormatSymbols_nmg_CM', 'goog.i18n.CompactNumberFormatSymbols_nn', 'goog.i18n.CompactNumberFormatSymbols_nn_NO', 'goog.i18n.CompactNumberFormatSymbols_nnh', 'goog.i18n.CompactNumberFormatSymbols_nnh_CM', 'goog.i18n.CompactNumberFormatSymbols_nr', 'goog.i18n.CompactNumberFormatSymbols_nr_ZA', 'goog.i18n.CompactNumberFormatSymbols_nso', 'goog.i18n.CompactNumberFormatSymbols_nso_ZA', 'goog.i18n.CompactNumberFormatSymbols_nus', 'goog.i18n.CompactNumberFormatSymbols_nus_SD', 'goog.i18n.CompactNumberFormatSymbols_nyn', 'goog.i18n.CompactNumberFormatSymbols_nyn_UG', 'goog.i18n.CompactNumberFormatSymbols_om', 'goog.i18n.CompactNumberFormatSymbols_om_ET', 'goog.i18n.CompactNumberFormatSymbols_om_KE', 'goog.i18n.CompactNumberFormatSymbols_os', 'goog.i18n.CompactNumberFormatSymbols_os_GE', 'goog.i18n.CompactNumberFormatSymbols_os_RU', 'goog.i18n.CompactNumberFormatSymbols_pa_Arab', 'goog.i18n.CompactNumberFormatSymbols_pa_Arab_PK', 'goog.i18n.CompactNumberFormatSymbols_pa_Guru', 'goog.i18n.CompactNumberFormatSymbols_ps', 'goog.i18n.CompactNumberFormatSymbols_ps_AF', 'goog.i18n.CompactNumberFormatSymbols_pt_AO', 'goog.i18n.CompactNumberFormatSymbols_pt_CV', 'goog.i18n.CompactNumberFormatSymbols_pt_GW', 'goog.i18n.CompactNumberFormatSymbols_pt_MO', 'goog.i18n.CompactNumberFormatSymbols_pt_MZ', 'goog.i18n.CompactNumberFormatSymbols_pt_ST', 'goog.i18n.CompactNumberFormatSymbols_pt_TL', 'goog.i18n.CompactNumberFormatSymbols_rm', 'goog.i18n.CompactNumberFormatSymbols_rm_CH', 'goog.i18n.CompactNumberFormatSymbols_rn', 'goog.i18n.CompactNumberFormatSymbols_rn_BI', 'goog.i18n.CompactNumberFormatSymbols_ro_MD', 'goog.i18n.CompactNumberFormatSymbols_rof', 'goog.i18n.CompactNumberFormatSymbols_rof_TZ', 'goog.i18n.CompactNumberFormatSymbols_ru_BY', 'goog.i18n.CompactNumberFormatSymbols_ru_KG', 'goog.i18n.CompactNumberFormatSymbols_ru_KZ', 'goog.i18n.CompactNumberFormatSymbols_ru_MD', 'goog.i18n.CompactNumberFormatSymbols_ru_UA', 'goog.i18n.CompactNumberFormatSymbols_rw', 'goog.i18n.CompactNumberFormatSymbols_rw_RW', 'goog.i18n.CompactNumberFormatSymbols_rwk', 'goog.i18n.CompactNumberFormatSymbols_rwk_TZ', 'goog.i18n.CompactNumberFormatSymbols_sah', 'goog.i18n.CompactNumberFormatSymbols_sah_RU', 'goog.i18n.CompactNumberFormatSymbols_saq', 'goog.i18n.CompactNumberFormatSymbols_saq_KE', 'goog.i18n.CompactNumberFormatSymbols_sbp', 'goog.i18n.CompactNumberFormatSymbols_sbp_TZ', 'goog.i18n.CompactNumberFormatSymbols_se', 'goog.i18n.CompactNumberFormatSymbols_se_FI', 'goog.i18n.CompactNumberFormatSymbols_se_NO', 'goog.i18n.CompactNumberFormatSymbols_seh', 'goog.i18n.CompactNumberFormatSymbols_seh_MZ', 'goog.i18n.CompactNumberFormatSymbols_ses', 'goog.i18n.CompactNumberFormatSymbols_ses_ML', 'goog.i18n.CompactNumberFormatSymbols_sg', 'goog.i18n.CompactNumberFormatSymbols_sg_CF', 'goog.i18n.CompactNumberFormatSymbols_shi', 'goog.i18n.CompactNumberFormatSymbols_shi_Latn', 'goog.i18n.CompactNumberFormatSymbols_shi_Latn_MA', 'goog.i18n.CompactNumberFormatSymbols_shi_Tfng', 'goog.i18n.CompactNumberFormatSymbols_shi_Tfng_MA', 'goog.i18n.CompactNumberFormatSymbols_sn', 'goog.i18n.CompactNumberFormatSymbols_sn_ZW', 'goog.i18n.CompactNumberFormatSymbols_so', 'goog.i18n.CompactNumberFormatSymbols_so_DJ', 'goog.i18n.CompactNumberFormatSymbols_so_ET', 'goog.i18n.CompactNumberFormatSymbols_so_KE', 'goog.i18n.CompactNumberFormatSymbols_so_SO', 'goog.i18n.CompactNumberFormatSymbols_sq_MK', 'goog.i18n.CompactNumberFormatSymbols_sq_XK', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_BA', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_ME', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_XK', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_BA', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_ME', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_RS', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_XK', 'goog.i18n.CompactNumberFormatSymbols_ss', 'goog.i18n.CompactNumberFormatSymbols_ss_SZ', 'goog.i18n.CompactNumberFormatSymbols_ss_ZA', 'goog.i18n.CompactNumberFormatSymbols_ssy', 'goog.i18n.CompactNumberFormatSymbols_ssy_ER', 'goog.i18n.CompactNumberFormatSymbols_st', 'goog.i18n.CompactNumberFormatSymbols_st_LS', 'goog.i18n.CompactNumberFormatSymbols_st_ZA', 'goog.i18n.CompactNumberFormatSymbols_sv_AX', 'goog.i18n.CompactNumberFormatSymbols_sv_FI', 'goog.i18n.CompactNumberFormatSymbols_sw_KE', 'goog.i18n.CompactNumberFormatSymbols_sw_UG', 'goog.i18n.CompactNumberFormatSymbols_swc', 'goog.i18n.CompactNumberFormatSymbols_swc_CD', 'goog.i18n.CompactNumberFormatSymbols_ta_LK', 'goog.i18n.CompactNumberFormatSymbols_ta_MY', 'goog.i18n.CompactNumberFormatSymbols_ta_SG', 'goog.i18n.CompactNumberFormatSymbols_teo', 'goog.i18n.CompactNumberFormatSymbols_teo_KE', 'goog.i18n.CompactNumberFormatSymbols_teo_UG', 'goog.i18n.CompactNumberFormatSymbols_tg', 'goog.i18n.CompactNumberFormatSymbols_tg_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_tg_Cyrl_TJ', 'goog.i18n.CompactNumberFormatSymbols_ti', 'goog.i18n.CompactNumberFormatSymbols_ti_ER', 'goog.i18n.CompactNumberFormatSymbols_ti_ET', 'goog.i18n.CompactNumberFormatSymbols_tig', 'goog.i18n.CompactNumberFormatSymbols_tig_ER', 'goog.i18n.CompactNumberFormatSymbols_tn', 'goog.i18n.CompactNumberFormatSymbols_tn_BW', 'goog.i18n.CompactNumberFormatSymbols_tn_ZA', 'goog.i18n.CompactNumberFormatSymbols_to', 'goog.i18n.CompactNumberFormatSymbols_to_TO', 'goog.i18n.CompactNumberFormatSymbols_tr_CY', 'goog.i18n.CompactNumberFormatSymbols_ts', 'goog.i18n.CompactNumberFormatSymbols_ts_ZA', 'goog.i18n.CompactNumberFormatSymbols_twq', 'goog.i18n.CompactNumberFormatSymbols_twq_NE', 'goog.i18n.CompactNumberFormatSymbols_tzm', 'goog.i18n.CompactNumberFormatSymbols_tzm_Latn', 'goog.i18n.CompactNumberFormatSymbols_tzm_Latn_MA', 'goog.i18n.CompactNumberFormatSymbols_ug', 'goog.i18n.CompactNumberFormatSymbols_ug_Arab', 'goog.i18n.CompactNumberFormatSymbols_ug_Arab_CN', 'goog.i18n.CompactNumberFormatSymbols_ur_IN', 'goog.i18n.CompactNumberFormatSymbols_uz_Arab', 'goog.i18n.CompactNumberFormatSymbols_uz_Arab_AF', 'goog.i18n.CompactNumberFormatSymbols_uz_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_uz_Cyrl_UZ', 'goog.i18n.CompactNumberFormatSymbols_uz_Latn', 'goog.i18n.CompactNumberFormatSymbols_vai', 'goog.i18n.CompactNumberFormatSymbols_vai_Latn', 'goog.i18n.CompactNumberFormatSymbols_vai_Latn_LR', 'goog.i18n.CompactNumberFormatSymbols_vai_Vaii', 'goog.i18n.CompactNumberFormatSymbols_vai_Vaii_LR', 'goog.i18n.CompactNumberFormatSymbols_ve', 'goog.i18n.CompactNumberFormatSymbols_ve_ZA', 'goog.i18n.CompactNumberFormatSymbols_vo', 'goog.i18n.CompactNumberFormatSymbols_vo_001', 'goog.i18n.CompactNumberFormatSymbols_vun', 'goog.i18n.CompactNumberFormatSymbols_vun_TZ', 'goog.i18n.CompactNumberFormatSymbols_wae', 'goog.i18n.CompactNumberFormatSymbols_wae_CH', 'goog.i18n.CompactNumberFormatSymbols_wal', 'goog.i18n.CompactNumberFormatSymbols_wal_ET', 'goog.i18n.CompactNumberFormatSymbols_xh', 'goog.i18n.CompactNumberFormatSymbols_xh_ZA', 'goog.i18n.CompactNumberFormatSymbols_xog', 'goog.i18n.CompactNumberFormatSymbols_xog_UG', 'goog.i18n.CompactNumberFormatSymbols_yav', 'goog.i18n.CompactNumberFormatSymbols_yav_CM', 'goog.i18n.CompactNumberFormatSymbols_yo', 'goog.i18n.CompactNumberFormatSymbols_yo_BJ', 'goog.i18n.CompactNumberFormatSymbols_yo_NG', 'goog.i18n.CompactNumberFormatSymbols_zgh', 'goog.i18n.CompactNumberFormatSymbols_zgh_MA', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_HK', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_MO', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_SG', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant_HK', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant_MO', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant_TW'], []);
-goog.addDependency('i18n/currency.js', ['goog.i18n.currency', 'goog.i18n.currency.CurrencyInfo', 'goog.i18n.currency.CurrencyInfoTier2'], []);
-goog.addDependency('i18n/currency_test.js', ['goog.i18n.currencyTest'], ['goog.i18n.NumberFormat', 'goog.i18n.currency', 'goog.i18n.currency.CurrencyInfo', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('i18n/currencycodemap.js', ['goog.i18n.currencyCodeMap', 'goog.i18n.currencyCodeMapTier2'], []);
-goog.addDependency('i18n/datetimeformat.js', ['goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeFormat.Format'], ['goog.asserts', 'goog.date', 'goog.i18n.DateTimeSymbols', 'goog.i18n.TimeZone', 'goog.string']);
-goog.addDependency('i18n/datetimeformat_test.js', ['goog.i18n.DateTimeFormatTest'], ['goog.date.Date', 'goog.date.DateTime', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimePatterns', 'goog.i18n.DateTimePatterns_de', 'goog.i18n.DateTimePatterns_en', 'goog.i18n.DateTimePatterns_fa', 'goog.i18n.DateTimePatterns_fr', 'goog.i18n.DateTimePatterns_ja', 'goog.i18n.DateTimePatterns_sv', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_ar_AE', 'goog.i18n.DateTimeSymbols_ar_SA', 'goog.i18n.DateTimeSymbols_bn_BD', 'goog.i18n.DateTimeSymbols_de', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_en_GB', 'goog.i18n.DateTimeSymbols_en_IE', 'goog.i18n.DateTimeSymbols_en_IN', 'goog.i18n.DateTimeSymbols_en_US', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.DateTimeSymbols_fr', 'goog.i18n.DateTimeSymbols_fr_DJ', 'goog.i18n.DateTimeSymbols_he_IL', 'goog.i18n.DateTimeSymbols_ja', 'goog.i18n.DateTimeSymbols_ro_RO', 'goog.i18n.DateTimeSymbols_sv', 'goog.i18n.TimeZone', 'goog.testing.jsunit']);
-goog.addDependency('i18n/datetimeparse.js', ['goog.i18n.DateTimeParse'], ['goog.date', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeSymbols']);
-goog.addDependency('i18n/datetimeparse_test.js', ['goog.i18n.DateTimeParseTest'], ['goog.date.Date', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeParse', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.DateTimeSymbols_fr', 'goog.i18n.DateTimeSymbols_pl', 'goog.i18n.DateTimeSymbols_zh', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('i18n/datetimepatterns.js', ['goog.i18n.DateTimePatterns', 'goog.i18n.DateTimePatterns_af', 'goog.i18n.DateTimePatterns_am', 'goog.i18n.DateTimePatterns_ar', 'goog.i18n.DateTimePatterns_az', 'goog.i18n.DateTimePatterns_bg', 'goog.i18n.DateTimePatterns_bn', 'goog.i18n.DateTimePatterns_br', 'goog.i18n.DateTimePatterns_ca', 'goog.i18n.DateTimePatterns_chr', 'goog.i18n.DateTimePatterns_cs', 'goog.i18n.DateTimePatterns_cy', 'goog.i18n.DateTimePatterns_da', 'goog.i18n.DateTimePatterns_de', 'goog.i18n.DateTimePatterns_de_AT', 'goog.i18n.DateTimePatterns_de_CH', 'goog.i18n.DateTimePatterns_el', 'goog.i18n.DateTimePatterns_en', 'goog.i18n.DateTimePatterns_en_AU', 'goog.i18n.DateTimePatterns_en_GB', 'goog.i18n.DateTimePatterns_en_IE', 'goog.i18n.DateTimePatterns_en_IN', 'goog.i18n.DateTimePatterns_en_SG', 'goog.i18n.DateTimePatterns_en_US', 'goog.i18n.DateTimePatterns_en_ZA', 'goog.i18n.DateTimePatterns_es', 'goog.i18n.DateTimePatterns_es_419', 'goog.i18n.DateTimePatterns_es_ES', 'goog.i18n.DateTimePatterns_et', 'goog.i18n.DateTimePatterns_eu', 'goog.i18n.DateTimePatterns_fa', 'goog.i18n.DateTimePatterns_fi', 'goog.i18n.DateTimePatterns_fil', 'goog.i18n.DateTimePatterns_fr', 'goog.i18n.DateTimePatterns_fr_CA', 'goog.i18n.DateTimePatterns_gl', 'goog.i18n.DateTimePatterns_gsw', 'goog.i18n.DateTimePatterns_gu', 'goog.i18n.DateTimePatterns_haw', 'goog.i18n.DateTimePatterns_he', 'goog.i18n.DateTimePatterns_hi', 'goog.i18n.DateTimePatterns_hr', 'goog.i18n.DateTimePatterns_hu', 'goog.i18n.DateTimePatterns_hy', 'goog.i18n.DateTimePatterns_id', 'goog.i18n.DateTimePatterns_in', 'goog.i18n.DateTimePatterns_is', 'goog.i18n.DateTimePatterns_it', 'goog.i18n.DateTimePatterns_iw', 'goog.i18n.DateTimePatterns_ja', 'goog.i18n.DateTimePatterns_ka', 'goog.i18n.DateTimePatterns_kk', 'goog.i18n.DateTimePatterns_km', 'goog.i18n.DateTimePatterns_kn', 'goog.i18n.DateTimePatterns_ko', 'goog.i18n.DateTimePatterns_ky', 'goog.i18n.DateTimePatterns_ln', 'goog.i18n.DateTimePatterns_lo', 'goog.i18n.DateTimePatterns_lt', 'goog.i18n.DateTimePatterns_lv', 'goog.i18n.DateTimePatterns_mk', 'goog.i18n.DateTimePatterns_ml', 'goog.i18n.DateTimePatterns_mn', 'goog.i18n.DateTimePatterns_mo', 'goog.i18n.DateTimePatterns_mr', 'goog.i18n.DateTimePatterns_ms', 'goog.i18n.DateTimePatterns_mt', 'goog.i18n.DateTimePatterns_my', 'goog.i18n.DateTimePatterns_nb', 'goog.i18n.DateTimePatterns_ne', 'goog.i18n.DateTimePatterns_nl', 'goog.i18n.DateTimePatterns_no', 'goog.i18n.DateTimePatterns_no_NO', 'goog.i18n.DateTimePatterns_or', 'goog.i18n.DateTimePatterns_pa', 'goog.i18n.DateTimePatterns_pl', 'goog.i18n.DateTimePatterns_pt', 'goog.i18n.DateTimePatterns_pt_BR', 'goog.i18n.DateTimePatterns_pt_PT', 'goog.i18n.DateTimePatterns_ro', 'goog.i18n.DateTimePatterns_ru', 'goog.i18n.DateTimePatterns_sh', 'goog.i18n.DateTimePatterns_si', 'goog.i18n.DateTimePatterns_sk', 'goog.i18n.DateTimePatterns_sl', 'goog.i18n.DateTimePatterns_sq', 'goog.i18n.DateTimePatterns_sr', 'goog.i18n.DateTimePatterns_sv', 'goog.i18n.DateTimePatterns_sw', 'goog.i18n.DateTimePatterns_ta', 'goog.i18n.DateTimePatterns_te', 'goog.i18n.DateTimePatterns_th', 'goog.i18n.DateTimePatterns_tl', 'goog.i18n.DateTimePatterns_tr', 'goog.i18n.DateTimePatterns_uk', 'goog.i18n.DateTimePatterns_ur', 'goog.i18n.DateTimePatterns_uz', 'goog.i18n.DateTimePatterns_vi', 'goog.i18n.DateTimePatterns_zh', 'goog.i18n.DateTimePatterns_zh_CN', 'goog.i18n.DateTimePatterns_zh_HK', 'goog.i18n.DateTimePatterns_zh_TW', 'goog.i18n.DateTimePatterns_zu'], []);
-goog.addDependency('i18n/datetimepatternsext.js', ['goog.i18n.DateTimePatternsExt', 'goog.i18n.DateTimePatterns_af_NA', 'goog.i18n.DateTimePatterns_af_ZA', 'goog.i18n.DateTimePatterns_agq', 'goog.i18n.DateTimePatterns_agq_CM', 'goog.i18n.DateTimePatterns_ak', 'goog.i18n.DateTimePatterns_ak_GH', 'goog.i18n.DateTimePatterns_am_ET', 'goog.i18n.DateTimePatterns_ar_001', 'goog.i18n.DateTimePatterns_ar_AE', 'goog.i18n.DateTimePatterns_ar_BH', 'goog.i18n.DateTimePatterns_ar_DJ', 'goog.i18n.DateTimePatterns_ar_DZ', 'goog.i18n.DateTimePatterns_ar_EG', 'goog.i18n.DateTimePatterns_ar_EH', 'goog.i18n.DateTimePatterns_ar_ER', 'goog.i18n.DateTimePatterns_ar_IL', 'goog.i18n.DateTimePatterns_ar_IQ', 'goog.i18n.DateTimePatterns_ar_JO', 'goog.i18n.DateTimePatterns_ar_KM', 'goog.i18n.DateTimePatterns_ar_KW', 'goog.i18n.DateTimePatterns_ar_LB', 'goog.i18n.DateTimePatterns_ar_LY', 'goog.i18n.DateTimePatterns_ar_MA', 'goog.i18n.DateTimePatterns_ar_MR', 'goog.i18n.DateTimePatterns_ar_OM', 'goog.i18n.DateTimePatterns_ar_PS', 'goog.i18n.DateTimePatterns_ar_QA', 'goog.i18n.DateTimePatterns_ar_SA', 'goog.i18n.DateTimePatterns_ar_SD', 'goog.i18n.DateTimePatterns_ar_SO', 'goog.i18n.DateTimePatterns_ar_SS', 'goog.i18n.DateTimePatterns_ar_SY', 'goog.i18n.DateTimePatterns_ar_TD', 'goog.i18n.DateTimePatterns_ar_TN', 'goog.i18n.DateTimePatterns_ar_YE', 'goog.i18n.DateTimePatterns_as', 'goog.i18n.DateTimePatterns_as_IN', 'goog.i18n.DateTimePatterns_asa', 'goog.i18n.DateTimePatterns_asa_TZ', 'goog.i18n.DateTimePatterns_az_Cyrl', 'goog.i18n.DateTimePatterns_az_Cyrl_AZ', 'goog.i18n.DateTimePatterns_az_Latn', 'goog.i18n.DateTimePatterns_az_Latn_AZ', 'goog.i18n.DateTimePatterns_bas', 'goog.i18n.DateTimePatterns_bas_CM', 'goog.i18n.DateTimePatterns_be', 'goog.i18n.DateTimePatterns_be_BY', 'goog.i18n.DateTimePatterns_bem', 'goog.i18n.DateTimePatterns_bem_ZM', 'goog.i18n.DateTimePatterns_bez', 'goog.i18n.DateTimePatterns_bez_TZ', 'goog.i18n.DateTimePatterns_bg_BG', 'goog.i18n.DateTimePatterns_bm', 'goog.i18n.DateTimePatterns_bm_ML', 'goog.i18n.DateTimePatterns_bn_BD', 'goog.i18n.DateTimePatterns_bn_IN', 'goog.i18n.DateTimePatterns_bo', 'goog.i18n.DateTimePatterns_bo_CN', 'goog.i18n.DateTimePatterns_bo_IN', 'goog.i18n.DateTimePatterns_br_FR', 'goog.i18n.DateTimePatterns_brx', 'goog.i18n.DateTimePatterns_brx_IN', 'goog.i18n.DateTimePatterns_bs', 'goog.i18n.DateTimePatterns_bs_Cyrl', 'goog.i18n.DateTimePatterns_bs_Cyrl_BA', 'goog.i18n.DateTimePatterns_bs_Latn', 'goog.i18n.DateTimePatterns_bs_Latn_BA', 'goog.i18n.DateTimePatterns_ca_AD', 'goog.i18n.DateTimePatterns_ca_ES', 'goog.i18n.DateTimePatterns_ca_FR', 'goog.i18n.DateTimePatterns_ca_IT', 'goog.i18n.DateTimePatterns_cgg', 'goog.i18n.DateTimePatterns_cgg_UG', 'goog.i18n.DateTimePatterns_chr_US', 'goog.i18n.DateTimePatterns_cs_CZ', 'goog.i18n.DateTimePatterns_cy_GB', 'goog.i18n.DateTimePatterns_da_DK', 'goog.i18n.DateTimePatterns_da_GL', 'goog.i18n.DateTimePatterns_dav', 'goog.i18n.DateTimePatterns_dav_KE', 'goog.i18n.DateTimePatterns_de_BE', 'goog.i18n.DateTimePatterns_de_DE', 'goog.i18n.DateTimePatterns_de_LI', 'goog.i18n.DateTimePatterns_de_LU', 'goog.i18n.DateTimePatterns_dje', 'goog.i18n.DateTimePatterns_dje_NE', 'goog.i18n.DateTimePatterns_dua', 'goog.i18n.DateTimePatterns_dua_CM', 'goog.i18n.DateTimePatterns_dyo', 'goog.i18n.DateTimePatterns_dyo_SN', 'goog.i18n.DateTimePatterns_dz', 'goog.i18n.DateTimePatterns_dz_BT', 'goog.i18n.DateTimePatterns_ebu', 'goog.i18n.DateTimePatterns_ebu_KE', 'goog.i18n.DateTimePatterns_ee', 'goog.i18n.DateTimePatterns_ee_GH', 'goog.i18n.DateTimePatterns_ee_TG', 'goog.i18n.DateTimePatterns_el_CY', 'goog.i18n.DateTimePatterns_el_GR', 'goog.i18n.DateTimePatterns_en_001', 'goog.i18n.DateTimePatterns_en_150', 'goog.i18n.DateTimePatterns_en_AG', 'goog.i18n.DateTimePatterns_en_AI', 'goog.i18n.DateTimePatterns_en_AS', 'goog.i18n.DateTimePatterns_en_BB', 'goog.i18n.DateTimePatterns_en_BE', 'goog.i18n.DateTimePatterns_en_BM', 'goog.i18n.DateTimePatterns_en_BS', 'goog.i18n.DateTimePatterns_en_BW', 'goog.i18n.DateTimePatterns_en_BZ', 'goog.i18n.DateTimePatterns_en_CA', 'goog.i18n.DateTimePatterns_en_CC', 'goog.i18n.DateTimePatterns_en_CK', 'goog.i18n.DateTimePatterns_en_CM', 'goog.i18n.DateTimePatterns_en_CX', 'goog.i18n.DateTimePatterns_en_DG', 'goog.i18n.DateTimePatterns_en_DM', 'goog.i18n.DateTimePatterns_en_ER', 'goog.i18n.DateTimePatterns_en_FJ', 'goog.i18n.DateTimePatterns_en_FK', 'goog.i18n.DateTimePatterns_en_FM', 'goog.i18n.DateTimePatterns_en_GD', 'goog.i18n.DateTimePatterns_en_GG', 'goog.i18n.DateTimePatterns_en_GH', 'goog.i18n.DateTimePatterns_en_GI', 'goog.i18n.DateTimePatterns_en_GM', 'goog.i18n.DateTimePatterns_en_GU', 'goog.i18n.DateTimePatterns_en_GY', 'goog.i18n.DateTimePatterns_en_HK', 'goog.i18n.DateTimePatterns_en_IM', 'goog.i18n.DateTimePatterns_en_IO', 'goog.i18n.DateTimePatterns_en_JE', 'goog.i18n.DateTimePatterns_en_JM', 'goog.i18n.DateTimePatterns_en_KE', 'goog.i18n.DateTimePatterns_en_KI', 'goog.i18n.DateTimePatterns_en_KN', 'goog.i18n.DateTimePatterns_en_KY', 'goog.i18n.DateTimePatterns_en_LC', 'goog.i18n.DateTimePatterns_en_LR', 'goog.i18n.DateTimePatterns_en_LS', 'goog.i18n.DateTimePatterns_en_MG', 'goog.i18n.DateTimePatterns_en_MH', 'goog.i18n.DateTimePatterns_en_MO', 'goog.i18n.DateTimePatterns_en_MP', 'goog.i18n.DateTimePatterns_en_MS', 'goog.i18n.DateTimePatterns_en_MT', 'goog.i18n.DateTimePatterns_en_MU', 'goog.i18n.DateTimePatterns_en_MW', 'goog.i18n.DateTimePatterns_en_NA', 'goog.i18n.DateTimePatterns_en_NF', 'goog.i18n.DateTimePatterns_en_NG', 'goog.i18n.DateTimePatterns_en_NR', 'goog.i18n.DateTimePatterns_en_NU', 'goog.i18n.DateTimePatterns_en_NZ', 'goog.i18n.DateTimePatterns_en_PG', 'goog.i18n.DateTimePatterns_en_PH', 'goog.i18n.DateTimePatterns_en_PK', 'goog.i18n.DateTimePatterns_en_PN', 'goog.i18n.DateTimePatterns_en_PR', 'goog.i18n.DateTimePatterns_en_PW', 'goog.i18n.DateTimePatterns_en_RW', 'goog.i18n.DateTimePatterns_en_SB', 'goog.i18n.DateTimePatterns_en_SC', 'goog.i18n.DateTimePatterns_en_SD', 'goog.i18n.DateTimePatterns_en_SH', 'goog.i18n.DateTimePatterns_en_SL', 'goog.i18n.DateTimePatterns_en_SS', 'goog.i18n.DateTimePatterns_en_SX', 'goog.i18n.DateTimePatterns_en_SZ', 'goog.i18n.DateTimePatterns_en_TC', 'goog.i18n.DateTimePatterns_en_TK', 'goog.i18n.DateTimePatterns_en_TO', 'goog.i18n.DateTimePatterns_en_TT', 'goog.i18n.DateTimePatterns_en_TV', 'goog.i18n.DateTimePatterns_en_TZ', 'goog.i18n.DateTimePatterns_en_UG', 'goog.i18n.DateTimePatterns_en_UM', 'goog.i18n.DateTimePatterns_en_US_POSIX', 'goog.i18n.DateTimePatterns_en_VC', 'goog.i18n.DateTimePatterns_en_VG', 'goog.i18n.DateTimePatterns_en_VI', 'goog.i18n.DateTimePatterns_en_VU', 'goog.i18n.DateTimePatterns_en_WS', 'goog.i18n.DateTimePatterns_en_ZM', 'goog.i18n.DateTimePatterns_en_ZW', 'goog.i18n.DateTimePatterns_eo', 'goog.i18n.DateTimePatterns_es_AR', 'goog.i18n.DateTimePatterns_es_BO', 'goog.i18n.DateTimePatterns_es_CL', 'goog.i18n.DateTimePatterns_es_CO', 'goog.i18n.DateTimePatterns_es_CR', 'goog.i18n.DateTimePatterns_es_CU', 'goog.i18n.DateTimePatterns_es_DO', 'goog.i18n.DateTimePatterns_es_EA', 'goog.i18n.DateTimePatterns_es_EC', 'goog.i18n.DateTimePatterns_es_GQ', 'goog.i18n.DateTimePatterns_es_GT', 'goog.i18n.DateTimePatterns_es_HN', 'goog.i18n.DateTimePatterns_es_IC', 'goog.i18n.DateTimePatterns_es_MX', 'goog.i18n.DateTimePatterns_es_NI', 'goog.i18n.DateTimePatterns_es_PA', 'goog.i18n.DateTimePatterns_es_PE', 'goog.i18n.DateTimePatterns_es_PH', 'goog.i18n.DateTimePatterns_es_PR', 'goog.i18n.DateTimePatterns_es_PY', 'goog.i18n.DateTimePatterns_es_SV', 'goog.i18n.DateTimePatterns_es_US', 'goog.i18n.DateTimePatterns_es_UY', 'goog.i18n.DateTimePatterns_es_VE', 'goog.i18n.DateTimePatterns_et_EE', 'goog.i18n.DateTimePatterns_eu_ES', 'goog.i18n.DateTimePatterns_ewo', 'goog.i18n.DateTimePatterns_ewo_CM', 'goog.i18n.DateTimePatterns_fa_AF', 'goog.i18n.DateTimePatterns_fa_IR', 'goog.i18n.DateTimePatterns_ff', 'goog.i18n.DateTimePatterns_ff_SN', 'goog.i18n.DateTimePatterns_fi_FI', 'goog.i18n.DateTimePatterns_fil_PH', 'goog.i18n.DateTimePatterns_fo', 'goog.i18n.DateTimePatterns_fo_FO', 'goog.i18n.DateTimePatterns_fr_BE', 'goog.i18n.DateTimePatterns_fr_BF', 'goog.i18n.DateTimePatterns_fr_BI', 'goog.i18n.DateTimePatterns_fr_BJ', 'goog.i18n.DateTimePatterns_fr_BL', 'goog.i18n.DateTimePatterns_fr_CD', 'goog.i18n.DateTimePatterns_fr_CF', 'goog.i18n.DateTimePatterns_fr_CG', 'goog.i18n.DateTimePatterns_fr_CH', 'goog.i18n.DateTimePatterns_fr_CI', 'goog.i18n.DateTimePatterns_fr_CM', 'goog.i18n.DateTimePatterns_fr_DJ', 'goog.i18n.DateTimePatterns_fr_DZ', 'goog.i18n.DateTimePatterns_fr_FR', 'goog.i18n.DateTimePatterns_fr_GA', 'goog.i18n.DateTimePatterns_fr_GF', 'goog.i18n.DateTimePatterns_fr_GN', 'goog.i18n.DateTimePatterns_fr_GP', 'goog.i18n.DateTimePatterns_fr_GQ', 'goog.i18n.DateTimePatterns_fr_HT', 'goog.i18n.DateTimePatterns_fr_KM', 'goog.i18n.DateTimePatterns_fr_LU', 'goog.i18n.DateTimePatterns_fr_MA', 'goog.i18n.DateTimePatterns_fr_MC', 'goog.i18n.DateTimePatterns_fr_MF', 'goog.i18n.DateTimePatterns_fr_MG', 'goog.i18n.DateTimePatterns_fr_ML', 'goog.i18n.DateTimePatterns_fr_MQ', 'goog.i18n.DateTimePatterns_fr_MR', 'goog.i18n.DateTimePatterns_fr_MU', 'goog.i18n.DateTimePatterns_fr_NC', 'goog.i18n.DateTimePatterns_fr_NE', 'goog.i18n.DateTimePatterns_fr_PF', 'goog.i18n.DateTimePatterns_fr_PM', 'goog.i18n.DateTimePatterns_fr_RE', 'goog.i18n.DateTimePatterns_fr_RW', 'goog.i18n.DateTimePatterns_fr_SC', 'goog.i18n.DateTimePatterns_fr_SN', 'goog.i18n.DateTimePatterns_fr_SY', 'goog.i18n.DateTimePatterns_fr_TD', 'goog.i18n.DateTimePatterns_fr_TG', 'goog.i18n.DateTimePatterns_fr_TN', 'goog.i18n.DateTimePatterns_fr_VU', 'goog.i18n.DateTimePatterns_fr_WF', 'goog.i18n.DateTimePatterns_fr_YT', 'goog.i18n.DateTimePatterns_ga', 'goog.i18n.DateTimePatterns_ga_IE', 'goog.i18n.DateTimePatterns_gl_ES', 'goog.i18n.DateTimePatterns_gsw_CH', 'goog.i18n.DateTimePatterns_gsw_LI', 'goog.i18n.DateTimePatterns_gu_IN', 'goog.i18n.DateTimePatterns_guz', 'goog.i18n.DateTimePatterns_guz_KE', 'goog.i18n.DateTimePatterns_gv', 'goog.i18n.DateTimePatterns_gv_IM', 'goog.i18n.DateTimePatterns_ha', 'goog.i18n.DateTimePatterns_ha_Latn', 'goog.i18n.DateTimePatterns_ha_Latn_GH', 'goog.i18n.DateTimePatterns_ha_Latn_NE', 'goog.i18n.DateTimePatterns_ha_Latn_NG', 'goog.i18n.DateTimePatterns_haw_US', 'goog.i18n.DateTimePatterns_he_IL', 'goog.i18n.DateTimePatterns_hi_IN', 'goog.i18n.DateTimePatterns_hr_BA', 'goog.i18n.DateTimePatterns_hr_HR', 'goog.i18n.DateTimePatterns_hu_HU', 'goog.i18n.DateTimePatterns_hy_AM', 'goog.i18n.DateTimePatterns_id_ID', 'goog.i18n.DateTimePatterns_ig', 'goog.i18n.DateTimePatterns_ig_NG', 'goog.i18n.DateTimePatterns_ii', 'goog.i18n.DateTimePatterns_ii_CN', 'goog.i18n.DateTimePatterns_is_IS', 'goog.i18n.DateTimePatterns_it_CH', 'goog.i18n.DateTimePatterns_it_IT', 'goog.i18n.DateTimePatterns_it_SM', 'goog.i18n.DateTimePatterns_ja_JP', 'goog.i18n.DateTimePatterns_jgo', 'goog.i18n.DateTimePatterns_jgo_CM', 'goog.i18n.DateTimePatterns_jmc', 'goog.i18n.DateTimePatterns_jmc_TZ', 'goog.i18n.DateTimePatterns_ka_GE', 'goog.i18n.DateTimePatterns_kab', 'goog.i18n.DateTimePatterns_kab_DZ', 'goog.i18n.DateTimePatterns_kam', 'goog.i18n.DateTimePatterns_kam_KE', 'goog.i18n.DateTimePatterns_kde', 'goog.i18n.DateTimePatterns_kde_TZ', 'goog.i18n.DateTimePatterns_kea', 'goog.i18n.DateTimePatterns_kea_CV', 'goog.i18n.DateTimePatterns_khq', 'goog.i18n.DateTimePatterns_khq_ML', 'goog.i18n.DateTimePatterns_ki', 'goog.i18n.DateTimePatterns_ki_KE', 'goog.i18n.DateTimePatterns_kk_Cyrl', 'goog.i18n.DateTimePatterns_kk_Cyrl_KZ', 'goog.i18n.DateTimePatterns_kkj', 'goog.i18n.DateTimePatterns_kkj_CM', 'goog.i18n.DateTimePatterns_kl', 'goog.i18n.DateTimePatterns_kl_GL', 'goog.i18n.DateTimePatterns_kln', 'goog.i18n.DateTimePatterns_kln_KE', 'goog.i18n.DateTimePatterns_km_KH', 'goog.i18n.DateTimePatterns_kn_IN', 'goog.i18n.DateTimePatterns_ko_KP', 'goog.i18n.DateTimePatterns_ko_KR', 'goog.i18n.DateTimePatterns_kok', 'goog.i18n.DateTimePatterns_kok_IN', 'goog.i18n.DateTimePatterns_ks', 'goog.i18n.DateTimePatterns_ks_Arab', 'goog.i18n.DateTimePatterns_ks_Arab_IN', 'goog.i18n.DateTimePatterns_ksb', 'goog.i18n.DateTimePatterns_ksb_TZ', 'goog.i18n.DateTimePatterns_ksf', 'goog.i18n.DateTimePatterns_ksf_CM', 'goog.i18n.DateTimePatterns_kw', 'goog.i18n.DateTimePatterns_kw_GB', 'goog.i18n.DateTimePatterns_ky_Cyrl', 'goog.i18n.DateTimePatterns_ky_Cyrl_KG', 'goog.i18n.DateTimePatterns_lag', 'goog.i18n.DateTimePatterns_lag_TZ', 'goog.i18n.DateTimePatterns_lg', 'goog.i18n.DateTimePatterns_lg_UG', 'goog.i18n.DateTimePatterns_lkt', 'goog.i18n.DateTimePatterns_lkt_US', 'goog.i18n.DateTimePatterns_ln_AO', 'goog.i18n.DateTimePatterns_ln_CD', 'goog.i18n.DateTimePatterns_ln_CF', 'goog.i18n.DateTimePatterns_ln_CG', 'goog.i18n.DateTimePatterns_lo_LA', 'goog.i18n.DateTimePatterns_lt_LT', 'goog.i18n.DateTimePatterns_lu', 'goog.i18n.DateTimePatterns_lu_CD', 'goog.i18n.DateTimePatterns_luo', 'goog.i18n.DateTimePatterns_luo_KE', 'goog.i18n.DateTimePatterns_luy', 'goog.i18n.DateTimePatterns_luy_KE', 'goog.i18n.DateTimePatterns_lv_LV', 'goog.i18n.DateTimePatterns_mas', 'goog.i18n.DateTimePatterns_mas_KE', 'goog.i18n.DateTimePatterns_mas_TZ', 'goog.i18n.DateTimePatterns_mer', 'goog.i18n.DateTimePatterns_mer_KE', 'goog.i18n.DateTimePatterns_mfe', 'goog.i18n.DateTimePatterns_mfe_MU', 'goog.i18n.DateTimePatterns_mg', 'goog.i18n.DateTimePatterns_mg_MG', 'goog.i18n.DateTimePatterns_mgh', 'goog.i18n.DateTimePatterns_mgh_MZ', 'goog.i18n.DateTimePatterns_mgo', 'goog.i18n.DateTimePatterns_mgo_CM', 'goog.i18n.DateTimePatterns_mk_MK', 'goog.i18n.DateTimePatterns_ml_IN', 'goog.i18n.DateTimePatterns_mn_Cyrl', 'goog.i18n.DateTimePatterns_mn_Cyrl_MN', 'goog.i18n.DateTimePatterns_mr_IN', 'goog.i18n.DateTimePatterns_ms_Latn', 'goog.i18n.DateTimePatterns_ms_Latn_BN', 'goog.i18n.DateTimePatterns_ms_Latn_MY', 'goog.i18n.DateTimePatterns_ms_Latn_SG', 'goog.i18n.DateTimePatterns_mt_MT', 'goog.i18n.DateTimePatterns_mua', 'goog.i18n.DateTimePatterns_mua_CM', 'goog.i18n.DateTimePatterns_my_MM', 'goog.i18n.DateTimePatterns_naq', 'goog.i18n.DateTimePatterns_naq_NA', 'goog.i18n.DateTimePatterns_nb_NO', 'goog.i18n.DateTimePatterns_nb_SJ', 'goog.i18n.DateTimePatterns_nd', 'goog.i18n.DateTimePatterns_nd_ZW', 'goog.i18n.DateTimePatterns_ne_IN', 'goog.i18n.DateTimePatterns_ne_NP', 'goog.i18n.DateTimePatterns_nl_AW', 'goog.i18n.DateTimePatterns_nl_BE', 'goog.i18n.DateTimePatterns_nl_BQ', 'goog.i18n.DateTimePatterns_nl_CW', 'goog.i18n.DateTimePatterns_nl_NL', 'goog.i18n.DateTimePatterns_nl_SR', 'goog.i18n.DateTimePatterns_nl_SX', 'goog.i18n.DateTimePatterns_nmg', 'goog.i18n.DateTimePatterns_nmg_CM', 'goog.i18n.DateTimePatterns_nn', 'goog.i18n.DateTimePatterns_nn_NO', 'goog.i18n.DateTimePatterns_nnh', 'goog.i18n.DateTimePatterns_nnh_CM', 'goog.i18n.DateTimePatterns_nus', 'goog.i18n.DateTimePatterns_nus_SD', 'goog.i18n.DateTimePatterns_nyn', 'goog.i18n.DateTimePatterns_nyn_UG', 'goog.i18n.DateTimePatterns_om', 'goog.i18n.DateTimePatterns_om_ET', 'goog.i18n.DateTimePatterns_om_KE', 'goog.i18n.DateTimePatterns_or_IN', 'goog.i18n.DateTimePatterns_pa_Arab', 'goog.i18n.DateTimePatterns_pa_Arab_PK', 'goog.i18n.DateTimePatterns_pa_Guru', 'goog.i18n.DateTimePatterns_pa_Guru_IN', 'goog.i18n.DateTimePatterns_pl_PL', 'goog.i18n.DateTimePatterns_ps', 'goog.i18n.DateTimePatterns_ps_AF', 'goog.i18n.DateTimePatterns_pt_AO', 'goog.i18n.DateTimePatterns_pt_CV', 'goog.i18n.DateTimePatterns_pt_GW', 'goog.i18n.DateTimePatterns_pt_MO', 'goog.i18n.DateTimePatterns_pt_MZ', 'goog.i18n.DateTimePatterns_pt_ST', 'goog.i18n.DateTimePatterns_pt_TL', 'goog.i18n.DateTimePatterns_rm', 'goog.i18n.DateTimePatterns_rm_CH', 'goog.i18n.DateTimePatterns_rn', 'goog.i18n.DateTimePatterns_rn_BI', 'goog.i18n.DateTimePatterns_ro_MD', 'goog.i18n.DateTimePatterns_ro_RO', 'goog.i18n.DateTimePatterns_rof', 'goog.i18n.DateTimePatterns_rof_TZ', 'goog.i18n.DateTimePatterns_ru_BY', 'goog.i18n.DateTimePatterns_ru_KG', 'goog.i18n.DateTimePatterns_ru_KZ', 'goog.i18n.DateTimePatterns_ru_MD', 'goog.i18n.DateTimePatterns_ru_RU', 'goog.i18n.DateTimePatterns_ru_UA', 'goog.i18n.DateTimePatterns_rw', 'goog.i18n.DateTimePatterns_rw_RW', 'goog.i18n.DateTimePatterns_rwk', 'goog.i18n.DateTimePatterns_rwk_TZ', 'goog.i18n.DateTimePatterns_saq', 'goog.i18n.DateTimePatterns_saq_KE', 'goog.i18n.DateTimePatterns_sbp', 'goog.i18n.DateTimePatterns_sbp_TZ', 'goog.i18n.DateTimePatterns_seh', 'goog.i18n.DateTimePatterns_seh_MZ', 'goog.i18n.DateTimePatterns_ses', 'goog.i18n.DateTimePatterns_ses_ML', 'goog.i18n.DateTimePatterns_sg', 'goog.i18n.DateTimePatterns_sg_CF', 'goog.i18n.DateTimePatterns_shi', 'goog.i18n.DateTimePatterns_shi_Latn', 'goog.i18n.DateTimePatterns_shi_Latn_MA', 'goog.i18n.DateTimePatterns_shi_Tfng', 'goog.i18n.DateTimePatterns_shi_Tfng_MA', 'goog.i18n.DateTimePatterns_si_LK', 'goog.i18n.DateTimePatterns_sk_SK', 'goog.i18n.DateTimePatterns_sl_SI', 'goog.i18n.DateTimePatterns_sn', 'goog.i18n.DateTimePatterns_sn_ZW', 'goog.i18n.DateTimePatterns_so', 'goog.i18n.DateTimePatterns_so_DJ', 'goog.i18n.DateTimePatterns_so_ET', 'goog.i18n.DateTimePatterns_so_KE', 'goog.i18n.DateTimePatterns_so_SO', 'goog.i18n.DateTimePatterns_sq_AL', 'goog.i18n.DateTimePatterns_sq_MK', 'goog.i18n.DateTimePatterns_sq_XK', 'goog.i18n.DateTimePatterns_sr_Cyrl', 'goog.i18n.DateTimePatterns_sr_Cyrl_BA', 'goog.i18n.DateTimePatterns_sr_Cyrl_ME', 'goog.i18n.DateTimePatterns_sr_Cyrl_RS', 'goog.i18n.DateTimePatterns_sr_Cyrl_XK', 'goog.i18n.DateTimePatterns_sr_Latn', 'goog.i18n.DateTimePatterns_sr_Latn_BA', 'goog.i18n.DateTimePatterns_sr_Latn_ME', 'goog.i18n.DateTimePatterns_sr_Latn_RS', 'goog.i18n.DateTimePatterns_sr_Latn_XK', 'goog.i18n.DateTimePatterns_sv_AX', 'goog.i18n.DateTimePatterns_sv_FI', 'goog.i18n.DateTimePatterns_sv_SE', 'goog.i18n.DateTimePatterns_sw_KE', 'goog.i18n.DateTimePatterns_sw_TZ', 'goog.i18n.DateTimePatterns_sw_UG', 'goog.i18n.DateTimePatterns_swc', 'goog.i18n.DateTimePatterns_swc_CD', 'goog.i18n.DateTimePatterns_ta_IN', 'goog.i18n.DateTimePatterns_ta_LK', 'goog.i18n.DateTimePatterns_ta_MY', 'goog.i18n.DateTimePatterns_ta_SG', 'goog.i18n.DateTimePatterns_te_IN', 'goog.i18n.DateTimePatterns_teo', 'goog.i18n.DateTimePatterns_teo_KE', 'goog.i18n.DateTimePatterns_teo_UG', 'goog.i18n.DateTimePatterns_th_TH', 'goog.i18n.DateTimePatterns_ti', 'goog.i18n.DateTimePatterns_ti_ER', 'goog.i18n.DateTimePatterns_ti_ET', 'goog.i18n.DateTimePatterns_to', 'goog.i18n.DateTimePatterns_to_TO', 'goog.i18n.DateTimePatterns_tr_CY', 'goog.i18n.DateTimePatterns_tr_TR', 'goog.i18n.DateTimePatterns_twq', 'goog.i18n.DateTimePatterns_twq_NE', 'goog.i18n.DateTimePatterns_tzm', 'goog.i18n.DateTimePatterns_tzm_Latn', 'goog.i18n.DateTimePatterns_tzm_Latn_MA', 'goog.i18n.DateTimePatterns_ug', 'goog.i18n.DateTimePatterns_ug_Arab', 'goog.i18n.DateTimePatterns_ug_Arab_CN', 'goog.i18n.DateTimePatterns_uk_UA', 'goog.i18n.DateTimePatterns_ur_IN', 'goog.i18n.DateTimePatterns_ur_PK', 'goog.i18n.DateTimePatterns_uz_Arab', 'goog.i18n.DateTimePatterns_uz_Arab_AF', 'goog.i18n.DateTimePatterns_uz_Cyrl', 'goog.i18n.DateTimePatterns_uz_Cyrl_UZ', 'goog.i18n.DateTimePatterns_uz_Latn', 'goog.i18n.DateTimePatterns_uz_Latn_UZ', 'goog.i18n.DateTimePatterns_vai', 'goog.i18n.DateTimePatterns_vai_Latn', 'goog.i18n.DateTimePatterns_vai_Latn_LR', 'goog.i18n.DateTimePatterns_vai_Vaii', 'goog.i18n.DateTimePatterns_vai_Vaii_LR', 'goog.i18n.DateTimePatterns_vi_VN', 'goog.i18n.DateTimePatterns_vun', 'goog.i18n.DateTimePatterns_vun_TZ', 'goog.i18n.DateTimePatterns_xog', 'goog.i18n.DateTimePatterns_xog_UG', 'goog.i18n.DateTimePatterns_yav', 'goog.i18n.DateTimePatterns_yav_CM', 'goog.i18n.DateTimePatterns_yo', 'goog.i18n.DateTimePatterns_yo_BJ', 'goog.i18n.DateTimePatterns_yo_NG', 'goog.i18n.DateTimePatterns_zgh', 'goog.i18n.DateTimePatterns_zgh_MA', 'goog.i18n.DateTimePatterns_zh_Hans', 'goog.i18n.DateTimePatterns_zh_Hans_CN', 'goog.i18n.DateTimePatterns_zh_Hans_HK', 'goog.i18n.DateTimePatterns_zh_Hans_MO', 'goog.i18n.DateTimePatterns_zh_Hans_SG', 'goog.i18n.DateTimePatterns_zh_Hant', 'goog.i18n.DateTimePatterns_zh_Hant_HK', 'goog.i18n.DateTimePatterns_zh_Hant_MO', 'goog.i18n.DateTimePatterns_zh_Hant_TW', 'goog.i18n.DateTimePatterns_zu_ZA'], ['goog.i18n.DateTimePatterns']);
-goog.addDependency('i18n/datetimesymbols.js', ['goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_af', 'goog.i18n.DateTimeSymbols_am', 'goog.i18n.DateTimeSymbols_ar', 'goog.i18n.DateTimeSymbols_az', 'goog.i18n.DateTimeSymbols_bg', 'goog.i18n.DateTimeSymbols_bn', 'goog.i18n.DateTimeSymbols_br', 'goog.i18n.DateTimeSymbols_ca', 'goog.i18n.DateTimeSymbols_chr', 'goog.i18n.DateTimeSymbols_cs', 'goog.i18n.DateTimeSymbols_cy', 'goog.i18n.DateTimeSymbols_da', 'goog.i18n.DateTimeSymbols_de', 'goog.i18n.DateTimeSymbols_de_AT', 'goog.i18n.DateTimeSymbols_de_CH', 'goog.i18n.DateTimeSymbols_el', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_en_AU', 'goog.i18n.DateTimeSymbols_en_GB', 'goog.i18n.DateTimeSymbols_en_IE', 'goog.i18n.DateTimeSymbols_en_IN', 'goog.i18n.DateTimeSymbols_en_ISO', 'goog.i18n.DateTimeSymbols_en_SG', 'goog.i18n.DateTimeSymbols_en_US', 'goog.i18n.DateTimeSymbols_en_ZA', 'goog.i18n.DateTimeSymbols_es', 'goog.i18n.DateTimeSymbols_es_419', 'goog.i18n.DateTimeSymbols_es_ES', 'goog.i18n.DateTimeSymbols_et', 'goog.i18n.DateTimeSymbols_eu', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.DateTimeSymbols_fi', 'goog.i18n.DateTimeSymbols_fil', 'goog.i18n.DateTimeSymbols_fr', 'goog.i18n.DateTimeSymbols_fr_CA', 'goog.i18n.DateTimeSymbols_gl', 'goog.i18n.DateTimeSymbols_gsw', 'goog.i18n.DateTimeSymbols_gu', 'goog.i18n.DateTimeSymbols_haw', 'goog.i18n.DateTimeSymbols_he', 'goog.i18n.DateTimeSymbols_hi', 'goog.i18n.DateTimeSymbols_hr', 'goog.i18n.DateTimeSymbols_hu', 'goog.i18n.DateTimeSymbols_hy', 'goog.i18n.DateTimeSymbols_id', 'goog.i18n.DateTimeSymbols_in', 'goog.i18n.DateTimeSymbols_is', 'goog.i18n.DateTimeSymbols_it', 'goog.i18n.DateTimeSymbols_iw', 'goog.i18n.DateTimeSymbols_ja', 'goog.i18n.DateTimeSymbols_ka', 'goog.i18n.DateTimeSymbols_kk', 'goog.i18n.DateTimeSymbols_km', 'goog.i18n.DateTimeSymbols_kn', 'goog.i18n.DateTimeSymbols_ko', 'goog.i18n.DateTimeSymbols_ky', 'goog.i18n.DateTimeSymbols_ln', 'goog.i18n.DateTimeSymbols_lo', 'goog.i18n.DateTimeSymbols_lt', 'goog.i18n.DateTimeSymbols_lv', 'goog.i18n.DateTimeSymbols_mk', 'goog.i18n.DateTimeSymbols_ml', 'goog.i18n.DateTimeSymbols_mn', 'goog.i18n.DateTimeSymbols_mr', 'goog.i18n.DateTimeSymbols_ms', 'goog.i18n.DateTimeSymbols_mt', 'goog.i18n.DateTimeSymbols_my', 'goog.i18n.DateTimeSymbols_nb', 'goog.i18n.DateTimeSymbols_ne', 'goog.i18n.DateTimeSymbols_nl', 'goog.i18n.DateTimeSymbols_no', 'goog.i18n.DateTimeSymbols_no_NO', 'goog.i18n.DateTimeSymbols_or', 'goog.i18n.DateTimeSymbols_pa', 'goog.i18n.DateTimeSymbols_pl', 'goog.i18n.DateTimeSymbols_pt', 'goog.i18n.DateTimeSymbols_pt_BR', 'goog.i18n.DateTimeSymbols_pt_PT', 'goog.i18n.DateTimeSymbols_ro', 'goog.i18n.DateTimeSymbols_ru', 'goog.i18n.DateTimeSymbols_si', 'goog.i18n.DateTimeSymbols_sk', 'goog.i18n.DateTimeSymbols_sl', 'goog.i18n.DateTimeSymbols_sq', 'goog.i18n.DateTimeSymbols_sr', 'goog.i18n.DateTimeSymbols_sv', 'goog.i18n.DateTimeSymbols_sw', 'goog.i18n.DateTimeSymbols_ta', 'goog.i18n.DateTimeSymbols_te', 'goog.i18n.DateTimeSymbols_th', 'goog.i18n.DateTimeSymbols_tl', 'goog.i18n.DateTimeSymbols_tr', 'goog.i18n.DateTimeSymbols_uk', 'goog.i18n.DateTimeSymbols_ur', 'goog.i18n.DateTimeSymbols_uz', 'goog.i18n.DateTimeSymbols_vi', 'goog.i18n.DateTimeSymbols_zh', 'goog.i18n.DateTimeSymbols_zh_CN', 'goog.i18n.DateTimeSymbols_zh_HK', 'goog.i18n.DateTimeSymbols_zh_TW', 'goog.i18n.DateTimeSymbols_zu'], []);
-goog.addDependency('i18n/datetimesymbolsext.js', ['goog.i18n.DateTimeSymbolsExt', 'goog.i18n.DateTimeSymbols_aa', 'goog.i18n.DateTimeSymbols_aa_DJ', 'goog.i18n.DateTimeSymbols_aa_ER', 'goog.i18n.DateTimeSymbols_aa_ET', 'goog.i18n.DateTimeSymbols_af_NA', 'goog.i18n.DateTimeSymbols_af_ZA', 'goog.i18n.DateTimeSymbols_agq', 'goog.i18n.DateTimeSymbols_agq_CM', 'goog.i18n.DateTimeSymbols_ak', 'goog.i18n.DateTimeSymbols_ak_GH', 'goog.i18n.DateTimeSymbols_am_ET', 'goog.i18n.DateTimeSymbols_ar_001', 'goog.i18n.DateTimeSymbols_ar_AE', 'goog.i18n.DateTimeSymbols_ar_BH', 'goog.i18n.DateTimeSymbols_ar_DJ', 'goog.i18n.DateTimeSymbols_ar_DZ', 'goog.i18n.DateTimeSymbols_ar_EG', 'goog.i18n.DateTimeSymbols_ar_EH', 'goog.i18n.DateTimeSymbols_ar_ER', 'goog.i18n.DateTimeSymbols_ar_IL', 'goog.i18n.DateTimeSymbols_ar_IQ', 'goog.i18n.DateTimeSymbols_ar_JO', 'goog.i18n.DateTimeSymbols_ar_KM', 'goog.i18n.DateTimeSymbols_ar_KW', 'goog.i18n.DateTimeSymbols_ar_LB', 'goog.i18n.DateTimeSymbols_ar_LY', 'goog.i18n.DateTimeSymbols_ar_MA', 'goog.i18n.DateTimeSymbols_ar_MR', 'goog.i18n.DateTimeSymbols_ar_OM', 'goog.i18n.DateTimeSymbols_ar_PS', 'goog.i18n.DateTimeSymbols_ar_QA', 'goog.i18n.DateTimeSymbols_ar_SA', 'goog.i18n.DateTimeSymbols_ar_SD', 'goog.i18n.DateTimeSymbols_ar_SO', 'goog.i18n.DateTimeSymbols_ar_SS', 'goog.i18n.DateTimeSymbols_ar_SY', 'goog.i18n.DateTimeSymbols_ar_TD', 'goog.i18n.DateTimeSymbols_ar_TN', 'goog.i18n.DateTimeSymbols_ar_YE', 'goog.i18n.DateTimeSymbols_as', 'goog.i18n.DateTimeSymbols_as_IN', 'goog.i18n.DateTimeSymbols_asa', 'goog.i18n.DateTimeSymbols_asa_TZ', 'goog.i18n.DateTimeSymbols_ast', 'goog.i18n.DateTimeSymbols_ast_ES', 'goog.i18n.DateTimeSymbols_az_Cyrl', 'goog.i18n.DateTimeSymbols_az_Cyrl_AZ', 'goog.i18n.DateTimeSymbols_az_Latn', 'goog.i18n.DateTimeSymbols_az_Latn_AZ', 'goog.i18n.DateTimeSymbols_bas', 'goog.i18n.DateTimeSymbols_bas_CM', 'goog.i18n.DateTimeSymbols_be', 'goog.i18n.DateTimeSymbols_be_BY', 'goog.i18n.DateTimeSymbols_bem', 'goog.i18n.DateTimeSymbols_bem_ZM', 'goog.i18n.DateTimeSymbols_bez', 'goog.i18n.DateTimeSymbols_bez_TZ', 'goog.i18n.DateTimeSymbols_bg_BG', 'goog.i18n.DateTimeSymbols_bm', 'goog.i18n.DateTimeSymbols_bm_ML', 'goog.i18n.DateTimeSymbols_bn_BD', 'goog.i18n.DateTimeSymbols_bn_IN', 'goog.i18n.DateTimeSymbols_bo', 'goog.i18n.DateTimeSymbols_bo_CN', 'goog.i18n.DateTimeSymbols_bo_IN', 'goog.i18n.DateTimeSymbols_br_FR', 'goog.i18n.DateTimeSymbols_brx', 'goog.i18n.DateTimeSymbols_brx_IN', 'goog.i18n.DateTimeSymbols_bs', 'goog.i18n.DateTimeSymbols_bs_Cyrl', 'goog.i18n.DateTimeSymbols_bs_Cyrl_BA', 'goog.i18n.DateTimeSymbols_bs_Latn', 'goog.i18n.DateTimeSymbols_bs_Latn_BA', 'goog.i18n.DateTimeSymbols_byn', 'goog.i18n.DateTimeSymbols_byn_ER', 'goog.i18n.DateTimeSymbols_ca_AD', 'goog.i18n.DateTimeSymbols_ca_ES', 'goog.i18n.DateTimeSymbols_ca_ES_VALENCIA', 'goog.i18n.DateTimeSymbols_ca_FR', 'goog.i18n.DateTimeSymbols_ca_IT', 'goog.i18n.DateTimeSymbols_cgg', 'goog.i18n.DateTimeSymbols_cgg_UG', 'goog.i18n.DateTimeSymbols_chr_US', 'goog.i18n.DateTimeSymbols_ckb', 'goog.i18n.DateTimeSymbols_ckb_Arab', 'goog.i18n.DateTimeSymbols_ckb_Arab_IQ', 'goog.i18n.DateTimeSymbols_ckb_Arab_IR', 'goog.i18n.DateTimeSymbols_ckb_IQ', 'goog.i18n.DateTimeSymbols_ckb_IR', 'goog.i18n.DateTimeSymbols_ckb_Latn', 'goog.i18n.DateTimeSymbols_ckb_Latn_IQ', 'goog.i18n.DateTimeSymbols_cs_CZ', 'goog.i18n.DateTimeSymbols_cy_GB', 'goog.i18n.DateTimeSymbols_da_DK', 'goog.i18n.DateTimeSymbols_da_GL', 'goog.i18n.DateTimeSymbols_dav', 'goog.i18n.DateTimeSymbols_dav_KE', 'goog.i18n.DateTimeSymbols_de_BE', 'goog.i18n.DateTimeSymbols_de_DE', 'goog.i18n.DateTimeSymbols_de_LI', 'goog.i18n.DateTimeSymbols_de_LU', 'goog.i18n.DateTimeSymbols_dje', 'goog.i18n.DateTimeSymbols_dje_NE', 'goog.i18n.DateTimeSymbols_dua', 'goog.i18n.DateTimeSymbols_dua_CM', 'goog.i18n.DateTimeSymbols_dyo', 'goog.i18n.DateTimeSymbols_dyo_SN', 'goog.i18n.DateTimeSymbols_dz', 'goog.i18n.DateTimeSymbols_dz_BT', 'goog.i18n.DateTimeSymbols_ebu', 'goog.i18n.DateTimeSymbols_ebu_KE', 'goog.i18n.DateTimeSymbols_ee', 'goog.i18n.DateTimeSymbols_ee_GH', 'goog.i18n.DateTimeSymbols_ee_TG', 'goog.i18n.DateTimeSymbols_el_CY', 'goog.i18n.DateTimeSymbols_el_GR', 'goog.i18n.DateTimeSymbols_en_001', 'goog.i18n.DateTimeSymbols_en_150', 'goog.i18n.DateTimeSymbols_en_AG', 'goog.i18n.DateTimeSymbols_en_AI', 'goog.i18n.DateTimeSymbols_en_AS', 'goog.i18n.DateTimeSymbols_en_BB', 'goog.i18n.DateTimeSymbols_en_BE', 'goog.i18n.DateTimeSymbols_en_BM', 'goog.i18n.DateTimeSymbols_en_BS', 'goog.i18n.DateTimeSymbols_en_BW', 'goog.i18n.DateTimeSymbols_en_BZ', 'goog.i18n.DateTimeSymbols_en_CA', 'goog.i18n.DateTimeSymbols_en_CC', 'goog.i18n.DateTimeSymbols_en_CK', 'goog.i18n.DateTimeSymbols_en_CM', 'goog.i18n.DateTimeSymbols_en_CX', 'goog.i18n.DateTimeSymbols_en_DG', 'goog.i18n.DateTimeSymbols_en_DM', 'goog.i18n.DateTimeSymbols_en_ER', 'goog.i18n.DateTimeSymbols_en_FJ', 'goog.i18n.DateTimeSymbols_en_FK', 'goog.i18n.DateTimeSymbols_en_FM', 'goog.i18n.DateTimeSymbols_en_GD', 'goog.i18n.DateTimeSymbols_en_GG', 'goog.i18n.DateTimeSymbols_en_GH', 'goog.i18n.DateTimeSymbols_en_GI', 'goog.i18n.DateTimeSymbols_en_GM', 'goog.i18n.DateTimeSymbols_en_GU', 'goog.i18n.DateTimeSymbols_en_GY', 'goog.i18n.DateTimeSymbols_en_HK', 'goog.i18n.DateTimeSymbols_en_IM', 'goog.i18n.DateTimeSymbols_en_IO', 'goog.i18n.DateTimeSymbols_en_JE', 'goog.i18n.DateTimeSymbols_en_JM', 'goog.i18n.DateTimeSymbols_en_KE', 'goog.i18n.DateTimeSymbols_en_KI', 'goog.i18n.DateTimeSymbols_en_KN', 'goog.i18n.DateTimeSymbols_en_KY', 'goog.i18n.DateTimeSymbols_en_LC', 'goog.i18n.DateTimeSymbols_en_LR', 'goog.i18n.DateTimeSymbols_en_LS', 'goog.i18n.DateTimeSymbols_en_MG', 'goog.i18n.DateTimeSymbols_en_MH', 'goog.i18n.DateTimeSymbols_en_MO', 'goog.i18n.DateTimeSymbols_en_MP', 'goog.i18n.DateTimeSymbols_en_MS', 'goog.i18n.DateTimeSymbols_en_MT', 'goog.i18n.DateTimeSymbols_en_MU', 'goog.i18n.DateTimeSymbols_en_MW', 'goog.i18n.DateTimeSymbols_en_NA', 'goog.i18n.DateTimeSymbols_en_NF', 'goog.i18n.DateTimeSymbols_en_NG', 'goog.i18n.DateTimeSymbols_en_NR', 'goog.i18n.DateTimeSymbols_en_NU', 'goog.i18n.DateTimeSymbols_en_NZ', 'goog.i18n.DateTimeSymbols_en_PG', 'goog.i18n.DateTimeSymbols_en_PH', 'goog.i18n.DateTimeSymbols_en_PK', 'goog.i18n.DateTimeSymbols_en_PN', 'goog.i18n.DateTimeSymbols_en_PR', 'goog.i18n.DateTimeSymbols_en_PW', 'goog.i18n.DateTimeSymbols_en_RW', 'goog.i18n.DateTimeSymbols_en_SB', 'goog.i18n.DateTimeSymbols_en_SC', 'goog.i18n.DateTimeSymbols_en_SD', 'goog.i18n.DateTimeSymbols_en_SH', 'goog.i18n.DateTimeSymbols_en_SL', 'goog.i18n.DateTimeSymbols_en_SS', 'goog.i18n.DateTimeSymbols_en_SX', 'goog.i18n.DateTimeSymbols_en_SZ', 'goog.i18n.DateTimeSymbols_en_TC', 'goog.i18n.DateTimeSymbols_en_TK', 'goog.i18n.DateTimeSymbols_en_TO', 'goog.i18n.DateTimeSymbols_en_TT', 'goog.i18n.DateTimeSymbols_en_TV', 'goog.i18n.DateTimeSymbols_en_TZ', 'goog.i18n.DateTimeSymbols_en_UG', 'goog.i18n.DateTimeSymbols_en_UM', 'goog.i18n.DateTimeSymbols_en_VC', 'goog.i18n.DateTimeSymbols_en_VG', 'goog.i18n.DateTimeSymbols_en_VI', 'goog.i18n.DateTimeSymbols_en_VU', 'goog.i18n.DateTimeSymbols_en_WS', 'goog.i18n.DateTimeSymbols_en_ZM', 'goog.i18n.DateTimeSymbols_en_ZW', 'goog.i18n.DateTimeSymbols_eo', 'goog.i18n.DateTimeSymbols_eo_001', 'goog.i18n.DateTimeSymbols_es_AR', 'goog.i18n.DateTimeSymbols_es_BO', 'goog.i18n.DateTimeSymbols_es_CL', 'goog.i18n.DateTimeSymbols_es_CO', 'goog.i18n.DateTimeSymbols_es_CR', 'goog.i18n.DateTimeSymbols_es_CU', 'goog.i18n.DateTimeSymbols_es_DO', 'goog.i18n.DateTimeSymbols_es_EA', 'goog.i18n.DateTimeSymbols_es_EC', 'goog.i18n.DateTimeSymbols_es_GQ', 'goog.i18n.DateTimeSymbols_es_GT', 'goog.i18n.DateTimeSymbols_es_HN', 'goog.i18n.DateTimeSymbols_es_IC', 'goog.i18n.DateTimeSymbols_es_MX', 'goog.i18n.DateTimeSymbols_es_NI', 'goog.i18n.DateTimeSymbols_es_PA', 'goog.i18n.DateTimeSymbols_es_PE', 'goog.i18n.DateTimeSymbols_es_PH', 'goog.i18n.DateTimeSymbols_es_PR', 'goog.i18n.DateTimeSymbols_es_PY', 'goog.i18n.DateTimeSymbols_es_SV', 'goog.i18n.DateTimeSymbols_es_US', 'goog.i18n.DateTimeSymbols_es_UY', 'goog.i18n.DateTimeSymbols_es_VE', 'goog.i18n.DateTimeSymbols_et_EE', 'goog.i18n.DateTimeSymbols_eu_ES', 'goog.i18n.DateTimeSymbols_ewo', 'goog.i18n.DateTimeSymbols_ewo_CM', 'goog.i18n.DateTimeSymbols_fa_AF', 'goog.i18n.DateTimeSymbols_fa_IR', 'goog.i18n.DateTimeSymbols_ff', 'goog.i18n.DateTimeSymbols_ff_CM', 'goog.i18n.DateTimeSymbols_ff_GN', 'goog.i18n.DateTimeSymbols_ff_MR', 'goog.i18n.DateTimeSymbols_ff_SN', 'goog.i18n.DateTimeSymbols_fi_FI', 'goog.i18n.DateTimeSymbols_fil_PH', 'goog.i18n.DateTimeSymbols_fo', 'goog.i18n.DateTimeSymbols_fo_FO', 'goog.i18n.DateTimeSymbols_fr_BE', 'goog.i18n.DateTimeSymbols_fr_BF', 'goog.i18n.DateTimeSymbols_fr_BI', 'goog.i18n.DateTimeSymbols_fr_BJ', 'goog.i18n.DateTimeSymbols_fr_BL', 'goog.i18n.DateTimeSymbols_fr_CD', 'goog.i18n.DateTimeSymbols_fr_CF', 'goog.i18n.DateTimeSymbols_fr_CG', 'goog.i18n.DateTimeSymbols_fr_CH', 'goog.i18n.DateTimeSymbols_fr_CI', 'goog.i18n.DateTimeSymbols_fr_CM', 'goog.i18n.DateTimeSymbols_fr_DJ', 'goog.i18n.DateTimeSymbols_fr_DZ', 'goog.i18n.DateTimeSymbols_fr_FR', 'goog.i18n.DateTimeSymbols_fr_GA', 'goog.i18n.DateTimeSymbols_fr_GF', 'goog.i18n.DateTimeSymbols_fr_GN', 'goog.i18n.DateTimeSymbols_fr_GP', 'goog.i18n.DateTimeSymbols_fr_GQ', 'goog.i18n.DateTimeSymbols_fr_HT', 'goog.i18n.DateTimeSymbols_fr_KM', 'goog.i18n.DateTimeSymbols_fr_LU', 'goog.i18n.DateTimeSymbols_fr_MA', 'goog.i18n.DateTimeSymbols_fr_MC', 'goog.i18n.DateTimeSymbols_fr_MF', 'goog.i18n.DateTimeSymbols_fr_MG', 'goog.i18n.DateTimeSymbols_fr_ML', 'goog.i18n.DateTimeSymbols_fr_MQ', 'goog.i18n.DateTimeSymbols_fr_MR', 'goog.i18n.DateTimeSymbols_fr_MU', 'goog.i18n.DateTimeSymbols_fr_NC', 'goog.i18n.DateTimeSymbols_fr_NE', 'goog.i18n.DateTimeSymbols_fr_PF', 'goog.i18n.DateTimeSymbols_fr_PM', 'goog.i18n.DateTimeSymbols_fr_RE', 'goog.i18n.DateTimeSymbols_fr_RW', 'goog.i18n.DateTimeSymbols_fr_SC', 'goog.i18n.DateTimeSymbols_fr_SN', 'goog.i18n.DateTimeSymbols_fr_SY', 'goog.i18n.DateTimeSymbols_fr_TD', 'goog.i18n.DateTimeSymbols_fr_TG', 'goog.i18n.DateTimeSymbols_fr_TN', 'goog.i18n.DateTimeSymbols_fr_VU', 'goog.i18n.DateTimeSymbols_fr_WF', 'goog.i18n.DateTimeSymbols_fr_YT', 'goog.i18n.DateTimeSymbols_fur', 'goog.i18n.DateTimeSymbols_fur_IT', 'goog.i18n.DateTimeSymbols_fy', 'goog.i18n.DateTimeSymbols_fy_NL', 'goog.i18n.DateTimeSymbols_ga', 'goog.i18n.DateTimeSymbols_ga_IE', 'goog.i18n.DateTimeSymbols_gd', 'goog.i18n.DateTimeSymbols_gd_GB', 'goog.i18n.DateTimeSymbols_gl_ES', 'goog.i18n.DateTimeSymbols_gsw_CH', 'goog.i18n.DateTimeSymbols_gsw_LI', 'goog.i18n.DateTimeSymbols_gu_IN', 'goog.i18n.DateTimeSymbols_guz', 'goog.i18n.DateTimeSymbols_guz_KE', 'goog.i18n.DateTimeSymbols_gv', 'goog.i18n.DateTimeSymbols_gv_IM', 'goog.i18n.DateTimeSymbols_ha', 'goog.i18n.DateTimeSymbols_ha_Latn', 'goog.i18n.DateTimeSymbols_ha_Latn_GH', 'goog.i18n.DateTimeSymbols_ha_Latn_NE', 'goog.i18n.DateTimeSymbols_ha_Latn_NG', 'goog.i18n.DateTimeSymbols_haw_US', 'goog.i18n.DateTimeSymbols_he_IL', 'goog.i18n.DateTimeSymbols_hi_IN', 'goog.i18n.DateTimeSymbols_hr_BA', 'goog.i18n.DateTimeSymbols_hr_HR', 'goog.i18n.DateTimeSymbols_hu_HU', 'goog.i18n.DateTimeSymbols_hy_AM', 'goog.i18n.DateTimeSymbols_ia', 'goog.i18n.DateTimeSymbols_ia_FR', 'goog.i18n.DateTimeSymbols_id_ID', 'goog.i18n.DateTimeSymbols_ig', 'goog.i18n.DateTimeSymbols_ig_NG', 'goog.i18n.DateTimeSymbols_ii', 'goog.i18n.DateTimeSymbols_ii_CN', 'goog.i18n.DateTimeSymbols_is_IS', 'goog.i18n.DateTimeSymbols_it_CH', 'goog.i18n.DateTimeSymbols_it_IT', 'goog.i18n.DateTimeSymbols_it_SM', 'goog.i18n.DateTimeSymbols_ja_JP', 'goog.i18n.DateTimeSymbols_jgo', 'goog.i18n.DateTimeSymbols_jgo_CM', 'goog.i18n.DateTimeSymbols_jmc', 'goog.i18n.DateTimeSymbols_jmc_TZ', 'goog.i18n.DateTimeSymbols_ka_GE', 'goog.i18n.DateTimeSymbols_kab', 'goog.i18n.DateTimeSymbols_kab_DZ', 'goog.i18n.DateTimeSymbols_kam', 'goog.i18n.DateTimeSymbols_kam_KE', 'goog.i18n.DateTimeSymbols_kde', 'goog.i18n.DateTimeSymbols_kde_TZ', 'goog.i18n.DateTimeSymbols_kea', 'goog.i18n.DateTimeSymbols_kea_CV', 'goog.i18n.DateTimeSymbols_khq', 'goog.i18n.DateTimeSymbols_khq_ML', 'goog.i18n.DateTimeSymbols_ki', 'goog.i18n.DateTimeSymbols_ki_KE', 'goog.i18n.DateTimeSymbols_kk_Cyrl', 'goog.i18n.DateTimeSymbols_kk_Cyrl_KZ', 'goog.i18n.DateTimeSymbols_kkj', 'goog.i18n.DateTimeSymbols_kkj_CM', 'goog.i18n.DateTimeSymbols_kl', 'goog.i18n.DateTimeSymbols_kl_GL', 'goog.i18n.DateTimeSymbols_kln', 'goog.i18n.DateTimeSymbols_kln_KE', 'goog.i18n.DateTimeSymbols_km_KH', 'goog.i18n.DateTimeSymbols_kn_IN', 'goog.i18n.DateTimeSymbols_ko_KP', 'goog.i18n.DateTimeSymbols_ko_KR', 'goog.i18n.DateTimeSymbols_kok', 'goog.i18n.DateTimeSymbols_kok_IN', 'goog.i18n.DateTimeSymbols_ks', 'goog.i18n.DateTimeSymbols_ks_Arab', 'goog.i18n.DateTimeSymbols_ks_Arab_IN', 'goog.i18n.DateTimeSymbols_ksb', 'goog.i18n.DateTimeSymbols_ksb_TZ', 'goog.i18n.DateTimeSymbols_ksf', 'goog.i18n.DateTimeSymbols_ksf_CM', 'goog.i18n.DateTimeSymbols_ksh', 'goog.i18n.DateTimeSymbols_ksh_DE', 'goog.i18n.DateTimeSymbols_kw', 'goog.i18n.DateTimeSymbols_kw_GB', 'goog.i18n.DateTimeSymbols_ky_Cyrl', 'goog.i18n.DateTimeSymbols_ky_Cyrl_KG', 'goog.i18n.DateTimeSymbols_lag', 'goog.i18n.DateTimeSymbols_lag_TZ', 'goog.i18n.DateTimeSymbols_lg', 'goog.i18n.DateTimeSymbols_lg_UG', 'goog.i18n.DateTimeSymbols_lkt', 'goog.i18n.DateTimeSymbols_lkt_US', 'goog.i18n.DateTimeSymbols_ln_AO', 'goog.i18n.DateTimeSymbols_ln_CD', 'goog.i18n.DateTimeSymbols_ln_CF', 'goog.i18n.DateTimeSymbols_ln_CG', 'goog.i18n.DateTimeSymbols_lo_LA', 'goog.i18n.DateTimeSymbols_lt_LT', 'goog.i18n.DateTimeSymbols_lu', 'goog.i18n.DateTimeSymbols_lu_CD', 'goog.i18n.DateTimeSymbols_luo', 'goog.i18n.DateTimeSymbols_luo_KE', 'goog.i18n.DateTimeSymbols_luy', 'goog.i18n.DateTimeSymbols_luy_KE', 'goog.i18n.DateTimeSymbols_lv_LV', 'goog.i18n.DateTimeSymbols_mas', 'goog.i18n.DateTimeSymbols_mas_KE', 'goog.i18n.DateTimeSymbols_mas_TZ', 'goog.i18n.DateTimeSymbols_mer', 'goog.i18n.DateTimeSymbols_mer_KE', 'goog.i18n.DateTimeSymbols_mfe', 'goog.i18n.DateTimeSymbols_mfe_MU', 'goog.i18n.DateTimeSymbols_mg', 'goog.i18n.DateTimeSymbols_mg_MG', 'goog.i18n.DateTimeSymbols_mgh', 'goog.i18n.DateTimeSymbols_mgh_MZ', 'goog.i18n.DateTimeSymbols_mgo', 'goog.i18n.DateTimeSymbols_mgo_CM', 'goog.i18n.DateTimeSymbols_mk_MK', 'goog.i18n.DateTimeSymbols_ml_IN', 'goog.i18n.DateTimeSymbols_mn_Cyrl', 'goog.i18n.DateTimeSymbols_mn_Cyrl_MN', 'goog.i18n.DateTimeSymbols_mr_IN', 'goog.i18n.DateTimeSymbols_ms_Latn', 'goog.i18n.DateTimeSymbols_ms_Latn_BN', 'goog.i18n.DateTimeSymbols_ms_Latn_MY', 'goog.i18n.DateTimeSymbols_ms_Latn_SG', 'goog.i18n.DateTimeSymbols_mt_MT', 'goog.i18n.DateTimeSymbols_mua', 'goog.i18n.DateTimeSymbols_mua_CM', 'goog.i18n.DateTimeSymbols_my_MM', 'goog.i18n.DateTimeSymbols_naq', 'goog.i18n.DateTimeSymbols_naq_NA', 'goog.i18n.DateTimeSymbols_nb_NO', 'goog.i18n.DateTimeSymbols_nb_SJ', 'goog.i18n.DateTimeSymbols_nd', 'goog.i18n.DateTimeSymbols_nd_ZW', 'goog.i18n.DateTimeSymbols_ne_IN', 'goog.i18n.DateTimeSymbols_ne_NP', 'goog.i18n.DateTimeSymbols_nl_AW', 'goog.i18n.DateTimeSymbols_nl_BE', 'goog.i18n.DateTimeSymbols_nl_BQ', 'goog.i18n.DateTimeSymbols_nl_CW', 'goog.i18n.DateTimeSymbols_nl_NL', 'goog.i18n.DateTimeSymbols_nl_SR', 'goog.i18n.DateTimeSymbols_nl_SX', 'goog.i18n.DateTimeSymbols_nmg', 'goog.i18n.DateTimeSymbols_nmg_CM', 'goog.i18n.DateTimeSymbols_nn', 'goog.i18n.DateTimeSymbols_nn_NO', 'goog.i18n.DateTimeSymbols_nnh', 'goog.i18n.DateTimeSymbols_nnh_CM', 'goog.i18n.DateTimeSymbols_nr', 'goog.i18n.DateTimeSymbols_nr_ZA', 'goog.i18n.DateTimeSymbols_nso', 'goog.i18n.DateTimeSymbols_nso_ZA', 'goog.i18n.DateTimeSymbols_nus', 'goog.i18n.DateTimeSymbols_nus_SD', 'goog.i18n.DateTimeSymbols_nyn', 'goog.i18n.DateTimeSymbols_nyn_UG', 'goog.i18n.DateTimeSymbols_om', 'goog.i18n.DateTimeSymbols_om_ET', 'goog.i18n.DateTimeSymbols_om_KE', 'goog.i18n.DateTimeSymbols_or_IN', 'goog.i18n.DateTimeSymbols_os', 'goog.i18n.DateTimeSymbols_os_GE', 'goog.i18n.DateTimeSymbols_os_RU', 'goog.i18n.DateTimeSymbols_pa_Arab', 'goog.i18n.DateTimeSymbols_pa_Arab_PK', 'goog.i18n.DateTimeSymbols_pa_Guru', 'goog.i18n.DateTimeSymbols_pa_Guru_IN', 'goog.i18n.DateTimeSymbols_pl_PL', 'goog.i18n.DateTimeSymbols_ps', 'goog.i18n.DateTimeSymbols_ps_AF', 'goog.i18n.DateTimeSymbols_pt_AO', 'goog.i18n.DateTimeSymbols_pt_CV', 'goog.i18n.DateTimeSymbols_pt_GW', 'goog.i18n.DateTimeSymbols_pt_MO', 'goog.i18n.DateTimeSymbols_pt_MZ', 'goog.i18n.DateTimeSymbols_pt_ST', 'goog.i18n.DateTimeSymbols_pt_TL', 'goog.i18n.DateTimeSymbols_rm', 'goog.i18n.DateTimeSymbols_rm_CH', 'goog.i18n.DateTimeSymbols_rn', 'goog.i18n.DateTimeSymbols_rn_BI', 'goog.i18n.DateTimeSymbols_ro_MD', 'goog.i18n.DateTimeSymbols_ro_RO', 'goog.i18n.DateTimeSymbols_rof', 'goog.i18n.DateTimeSymbols_rof_TZ', 'goog.i18n.DateTimeSymbols_ru_BY', 'goog.i18n.DateTimeSymbols_ru_KG', 'goog.i18n.DateTimeSymbols_ru_KZ', 'goog.i18n.DateTimeSymbols_ru_MD', 'goog.i18n.DateTimeSymbols_ru_RU', 'goog.i18n.DateTimeSymbols_ru_UA', 'goog.i18n.DateTimeSymbols_rw', 'goog.i18n.DateTimeSymbols_rw_RW', 'goog.i18n.DateTimeSymbols_rwk', 'goog.i18n.DateTimeSymbols_rwk_TZ', 'goog.i18n.DateTimeSymbols_sah', 'goog.i18n.DateTimeSymbols_sah_RU', 'goog.i18n.DateTimeSymbols_saq', 'goog.i18n.DateTimeSymbols_saq_KE', 'goog.i18n.DateTimeSymbols_sbp', 'goog.i18n.DateTimeSymbols_sbp_TZ', 'goog.i18n.DateTimeSymbols_se', 'goog.i18n.DateTimeSymbols_se_FI', 'goog.i18n.DateTimeSymbols_se_NO', 'goog.i18n.DateTimeSymbols_seh', 'goog.i18n.DateTimeSymbols_seh_MZ', 'goog.i18n.DateTimeSymbols_ses', 'goog.i18n.DateTimeSymbols_ses_ML', 'goog.i18n.DateTimeSymbols_sg', 'goog.i18n.DateTimeSymbols_sg_CF', 'goog.i18n.DateTimeSymbols_shi', 'goog.i18n.DateTimeSymbols_shi_Latn', 'goog.i18n.DateTimeSymbols_shi_Latn_MA', 'goog.i18n.DateTimeSymbols_shi_Tfng', 'goog.i18n.DateTimeSymbols_shi_Tfng_MA', 'goog.i18n.DateTimeSymbols_si_LK', 'goog.i18n.DateTimeSymbols_sk_SK', 'goog.i18n.DateTimeSymbols_sl_SI', 'goog.i18n.DateTimeSymbols_sn', 'goog.i18n.DateTimeSymbols_sn_ZW', 'goog.i18n.DateTimeSymbols_so', 'goog.i18n.DateTimeSymbols_so_DJ', 'goog.i18n.DateTimeSymbols_so_ET', 'goog.i18n.DateTimeSymbols_so_KE', 'goog.i18n.DateTimeSymbols_so_SO', 'goog.i18n.DateTimeSymbols_sq_AL', 'goog.i18n.DateTimeSymbols_sq_MK', 'goog.i18n.DateTimeSymbols_sq_XK', 'goog.i18n.DateTimeSymbols_sr_Cyrl', 'goog.i18n.DateTimeSymbols_sr_Cyrl_BA', 'goog.i18n.DateTimeSymbols_sr_Cyrl_ME', 'goog.i18n.DateTimeSymbols_sr_Cyrl_RS', 'goog.i18n.DateTimeSymbols_sr_Cyrl_XK', 'goog.i18n.DateTimeSymbols_sr_Latn', 'goog.i18n.DateTimeSymbols_sr_Latn_BA', 'goog.i18n.DateTimeSymbols_sr_Latn_ME', 'goog.i18n.DateTimeSymbols_sr_Latn_RS', 'goog.i18n.DateTimeSymbols_sr_Latn_XK', 'goog.i18n.DateTimeSymbols_ss', 'goog.i18n.DateTimeSymbols_ss_SZ', 'goog.i18n.DateTimeSymbols_ss_ZA', 'goog.i18n.DateTimeSymbols_ssy', 'goog.i18n.DateTimeSymbols_ssy_ER', 'goog.i18n.DateTimeSymbols_st', 'goog.i18n.DateTimeSymbols_st_LS', 'goog.i18n.DateTimeSymbols_st_ZA', 'goog.i18n.DateTimeSymbols_sv_AX', 'goog.i18n.DateTimeSymbols_sv_FI', 'goog.i18n.DateTimeSymbols_sv_SE', 'goog.i18n.DateTimeSymbols_sw_KE', 'goog.i18n.DateTimeSymbols_sw_TZ', 'goog.i18n.DateTimeSymbols_sw_UG', 'goog.i18n.DateTimeSymbols_swc', 'goog.i18n.DateTimeSymbols_swc_CD', 'goog.i18n.DateTimeSymbols_ta_IN', 'goog.i18n.DateTimeSymbols_ta_LK', 'goog.i18n.DateTimeSymbols_ta_MY', 'goog.i18n.DateTimeSymbols_ta_SG', 'goog.i18n.DateTimeSymbols_te_IN', 'goog.i18n.DateTimeSymbols_teo', 'goog.i18n.DateTimeSymbols_teo_KE', 'goog.i18n.DateTimeSymbols_teo_UG', 'goog.i18n.DateTimeSymbols_tg', 'goog.i18n.DateTimeSymbols_tg_Cyrl', 'goog.i18n.DateTimeSymbols_tg_Cyrl_TJ', 'goog.i18n.DateTimeSymbols_th_TH', 'goog.i18n.DateTimeSymbols_ti', 'goog.i18n.DateTimeSymbols_ti_ER', 'goog.i18n.DateTimeSymbols_ti_ET', 'goog.i18n.DateTimeSymbols_tig', 'goog.i18n.DateTimeSymbols_tig_ER', 'goog.i18n.DateTimeSymbols_tn', 'goog.i18n.DateTimeSymbols_tn_BW', 'goog.i18n.DateTimeSymbols_tn_ZA', 'goog.i18n.DateTimeSymbols_to', 'goog.i18n.DateTimeSymbols_to_TO', 'goog.i18n.DateTimeSymbols_tr_CY', 'goog.i18n.DateTimeSymbols_tr_TR', 'goog.i18n.DateTimeSymbols_ts', 'goog.i18n.DateTimeSymbols_ts_ZA', 'goog.i18n.DateTimeSymbols_twq', 'goog.i18n.DateTimeSymbols_twq_NE', 'goog.i18n.DateTimeSymbols_tzm', 'goog.i18n.DateTimeSymbols_tzm_Latn', 'goog.i18n.DateTimeSymbols_tzm_Latn_MA', 'goog.i18n.DateTimeSymbols_ug', 'goog.i18n.DateTimeSymbols_ug_Arab', 'goog.i18n.DateTimeSymbols_ug_Arab_CN', 'goog.i18n.DateTimeSymbols_uk_UA', 'goog.i18n.DateTimeSymbols_ur_IN', 'goog.i18n.DateTimeSymbols_ur_PK', 'goog.i18n.DateTimeSymbols_uz_Arab', 'goog.i18n.DateTimeSymbols_uz_Arab_AF', 'goog.i18n.DateTimeSymbols_uz_Cyrl', 'goog.i18n.DateTimeSymbols_uz_Cyrl_UZ', 'goog.i18n.DateTimeSymbols_uz_Latn', 'goog.i18n.DateTimeSymbols_uz_Latn_UZ', 'goog.i18n.DateTimeSymbols_vai', 'goog.i18n.DateTimeSymbols_vai_Latn', 'goog.i18n.DateTimeSymbols_vai_Latn_LR', 'goog.i18n.DateTimeSymbols_vai_Vaii', 'goog.i18n.DateTimeSymbols_vai_Vaii_LR', 'goog.i18n.DateTimeSymbols_ve', 'goog.i18n.DateTimeSymbols_ve_ZA', 'goog.i18n.DateTimeSymbols_vi_VN', 'goog.i18n.DateTimeSymbols_vo', 'goog.i18n.DateTimeSymbols_vo_001', 'goog.i18n.DateTimeSymbols_vun', 'goog.i18n.DateTimeSymbols_vun_TZ', 'goog.i18n.DateTimeSymbols_wae', 'goog.i18n.DateTimeSymbols_wae_CH', 'goog.i18n.DateTimeSymbols_wal', 'goog.i18n.DateTimeSymbols_wal_ET', 'goog.i18n.DateTimeSymbols_xh', 'goog.i18n.DateTimeSymbols_xh_ZA', 'goog.i18n.DateTimeSymbols_xog', 'goog.i18n.DateTimeSymbols_xog_UG', 'goog.i18n.DateTimeSymbols_yav', 'goog.i18n.DateTimeSymbols_yav_CM', 'goog.i18n.DateTimeSymbols_yo', 'goog.i18n.DateTimeSymbols_yo_BJ', 'goog.i18n.DateTimeSymbols_yo_NG', 'goog.i18n.DateTimeSymbols_zgh', 'goog.i18n.DateTimeSymbols_zgh_MA', 'goog.i18n.DateTimeSymbols_zh_Hans', 'goog.i18n.DateTimeSymbols_zh_Hans_CN', 'goog.i18n.DateTimeSymbols_zh_Hans_HK', 'goog.i18n.DateTimeSymbols_zh_Hans_MO', 'goog.i18n.DateTimeSymbols_zh_Hans_SG', 'goog.i18n.DateTimeSymbols_zh_Hant', 'goog.i18n.DateTimeSymbols_zh_Hant_HK', 'goog.i18n.DateTimeSymbols_zh_Hant_MO', 'goog.i18n.DateTimeSymbols_zh_Hant_TW', 'goog.i18n.DateTimeSymbols_zu_ZA'], ['goog.i18n.DateTimeSymbols']);
-goog.addDependency('i18n/graphemebreak.js', ['goog.i18n.GraphemeBreak'], ['goog.structs.InversionMap']);
-goog.addDependency('i18n/graphemebreak_test.js', ['goog.i18n.GraphemeBreakTest'], ['goog.i18n.GraphemeBreak', 'goog.testing.jsunit']);
-goog.addDependency('i18n/messageformat.js', ['goog.i18n.MessageFormat'], ['goog.asserts', 'goog.i18n.NumberFormat', 'goog.i18n.ordinalRules', 'goog.i18n.pluralRules']);
-goog.addDependency('i18n/messageformat_test.js', ['goog.i18n.MessageFormatTest'], ['goog.i18n.MessageFormat', 'goog.i18n.NumberFormatSymbols_hr', 'goog.i18n.pluralRules', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('i18n/mime.js', ['goog.i18n.mime', 'goog.i18n.mime.encode'], ['goog.array']);
-goog.addDependency('i18n/mime_test.js', ['goog.i18n.mime.encodeTest'], ['goog.i18n.mime.encode', 'goog.testing.jsunit']);
-goog.addDependency('i18n/numberformat.js', ['goog.i18n.NumberFormat', 'goog.i18n.NumberFormat.CurrencyStyle', 'goog.i18n.NumberFormat.Format'], ['goog.asserts', 'goog.i18n.CompactNumberFormatSymbols', 'goog.i18n.NumberFormatSymbols', 'goog.i18n.currency', 'goog.math']);
-goog.addDependency('i18n/numberformat_test.js', ['goog.i18n.NumberFormatTest'], ['goog.i18n.CompactNumberFormatSymbols', 'goog.i18n.CompactNumberFormatSymbols_de', 'goog.i18n.CompactNumberFormatSymbols_en', 'goog.i18n.CompactNumberFormatSymbols_fr', 'goog.i18n.NumberFormat', 'goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_de', 'goog.i18n.NumberFormatSymbols_en', 'goog.i18n.NumberFormatSymbols_fr', 'goog.i18n.NumberFormatSymbols_pl', 'goog.i18n.NumberFormatSymbols_ro', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('i18n/numberformatsymbols.js', ['goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_af', 'goog.i18n.NumberFormatSymbols_af_ZA', 'goog.i18n.NumberFormatSymbols_am', 'goog.i18n.NumberFormatSymbols_am_ET', 'goog.i18n.NumberFormatSymbols_ar', 'goog.i18n.NumberFormatSymbols_ar_001', 'goog.i18n.NumberFormatSymbols_az', 'goog.i18n.NumberFormatSymbols_az_Cyrl_AZ', 'goog.i18n.NumberFormatSymbols_az_Latn_AZ', 'goog.i18n.NumberFormatSymbols_bg', 'goog.i18n.NumberFormatSymbols_bg_BG', 'goog.i18n.NumberFormatSymbols_bn', 'goog.i18n.NumberFormatSymbols_bn_BD', 'goog.i18n.NumberFormatSymbols_br', 'goog.i18n.NumberFormatSymbols_br_FR', 'goog.i18n.NumberFormatSymbols_ca', 'goog.i18n.NumberFormatSymbols_ca_AD', 'goog.i18n.NumberFormatSymbols_ca_ES', 'goog.i18n.NumberFormatSymbols_ca_ES_VALENCIA', 'goog.i18n.NumberFormatSymbols_ca_FR', 'goog.i18n.NumberFormatSymbols_ca_IT', 'goog.i18n.NumberFormatSymbols_chr', 'goog.i18n.NumberFormatSymbols_chr_US', 'goog.i18n.NumberFormatSymbols_cs', 'goog.i18n.NumberFormatSymbols_cs_CZ', 'goog.i18n.NumberFormatSymbols_cy', 'goog.i18n.NumberFormatSymbols_cy_GB', 'goog.i18n.NumberFormatSymbols_da', 'goog.i18n.NumberFormatSymbols_da_DK', 'goog.i18n.NumberFormatSymbols_da_GL', 'goog.i18n.NumberFormatSymbols_de', 'goog.i18n.NumberFormatSymbols_de_AT', 'goog.i18n.NumberFormatSymbols_de_BE', 'goog.i18n.NumberFormatSymbols_de_CH', 'goog.i18n.NumberFormatSymbols_de_DE', 'goog.i18n.NumberFormatSymbols_de_LU', 'goog.i18n.NumberFormatSymbols_el', 'goog.i18n.NumberFormatSymbols_el_GR', 'goog.i18n.NumberFormatSymbols_en', 'goog.i18n.NumberFormatSymbols_en_001', 'goog.i18n.NumberFormatSymbols_en_AS', 'goog.i18n.NumberFormatSymbols_en_AU', 'goog.i18n.NumberFormatSymbols_en_DG', 'goog.i18n.NumberFormatSymbols_en_FM', 'goog.i18n.NumberFormatSymbols_en_GB', 'goog.i18n.NumberFormatSymbols_en_GU', 'goog.i18n.NumberFormatSymbols_en_IE', 'goog.i18n.NumberFormatSymbols_en_IN', 'goog.i18n.NumberFormatSymbols_en_IO', 'goog.i18n.NumberFormatSymbols_en_MH', 'goog.i18n.NumberFormatSymbols_en_MP', 'goog.i18n.NumberFormatSymbols_en_PR', 'goog.i18n.NumberFormatSymbols_en_PW', 'goog.i18n.NumberFormatSymbols_en_SG', 'goog.i18n.NumberFormatSymbols_en_TC', 'goog.i18n.NumberFormatSymbols_en_UM', 'goog.i18n.NumberFormatSymbols_en_US', 'goog.i18n.NumberFormatSymbols_en_VG', 'goog.i18n.NumberFormatSymbols_en_VI', 'goog.i18n.NumberFormatSymbols_en_ZA', 'goog.i18n.NumberFormatSymbols_en_ZW', 'goog.i18n.NumberFormatSymbols_es', 'goog.i18n.NumberFormatSymbols_es_419', 'goog.i18n.NumberFormatSymbols_es_EA', 'goog.i18n.NumberFormatSymbols_es_ES', 'goog.i18n.NumberFormatSymbols_es_IC', 'goog.i18n.NumberFormatSymbols_et', 'goog.i18n.NumberFormatSymbols_et_EE', 'goog.i18n.NumberFormatSymbols_eu', 'goog.i18n.NumberFormatSymbols_eu_ES', 'goog.i18n.NumberFormatSymbols_fa', 'goog.i18n.NumberFormatSymbols_fa_IR', 'goog.i18n.NumberFormatSymbols_fi', 'goog.i18n.NumberFormatSymbols_fi_FI', 'goog.i18n.NumberFormatSymbols_fil', 'goog.i18n.NumberFormatSymbols_fil_PH', 'goog.i18n.NumberFormatSymbols_fr', 'goog.i18n.NumberFormatSymbols_fr_BL', 'goog.i18n.NumberFormatSymbols_fr_CA', 'goog.i18n.NumberFormatSymbols_fr_FR', 'goog.i18n.NumberFormatSymbols_fr_GF', 'goog.i18n.NumberFormatSymbols_fr_GP', 'goog.i18n.NumberFormatSymbols_fr_MC', 'goog.i18n.NumberFormatSymbols_fr_MF', 'goog.i18n.NumberFormatSymbols_fr_MQ', 'goog.i18n.NumberFormatSymbols_fr_PM', 'goog.i18n.NumberFormatSymbols_fr_RE', 'goog.i18n.NumberFormatSymbols_fr_YT', 'goog.i18n.NumberFormatSymbols_gl', 'goog.i18n.NumberFormatSymbols_gl_ES', 'goog.i18n.NumberFormatSymbols_gsw', 'goog.i18n.NumberFormatSymbols_gsw_CH', 'goog.i18n.NumberFormatSymbols_gsw_LI', 'goog.i18n.NumberFormatSymbols_gu', 'goog.i18n.NumberFormatSymbols_gu_IN', 'goog.i18n.NumberFormatSymbols_haw', 'goog.i18n.NumberFormatSymbols_haw_US', 'goog.i18n.NumberFormatSymbols_he', 'goog.i18n.NumberFormatSymbols_he_IL', 'goog.i18n.NumberFormatSymbols_hi', 'goog.i18n.NumberFormatSymbols_hi_IN', 'goog.i18n.NumberFormatSymbols_hr', 'goog.i18n.NumberFormatSymbols_hr_HR', 'goog.i18n.NumberFormatSymbols_hu', 'goog.i18n.NumberFormatSymbols_hu_HU', 'goog.i18n.NumberFormatSymbols_hy', 'goog.i18n.NumberFormatSymbols_hy_AM', 'goog.i18n.NumberFormatSymbols_id', 'goog.i18n.NumberFormatSymbols_id_ID', 'goog.i18n.NumberFormatSymbols_in', 'goog.i18n.NumberFormatSymbols_is', 'goog.i18n.NumberFormatSymbols_is_IS', 'goog.i18n.NumberFormatSymbols_it', 'goog.i18n.NumberFormatSymbols_it_IT', 'goog.i18n.NumberFormatSymbols_it_SM', 'goog.i18n.NumberFormatSymbols_iw', 'goog.i18n.NumberFormatSymbols_ja', 'goog.i18n.NumberFormatSymbols_ja_JP', 'goog.i18n.NumberFormatSymbols_ka', 'goog.i18n.NumberFormatSymbols_ka_GE', 'goog.i18n.NumberFormatSymbols_kk', 'goog.i18n.NumberFormatSymbols_kk_Cyrl_KZ', 'goog.i18n.NumberFormatSymbols_km', 'goog.i18n.NumberFormatSymbols_km_KH', 'goog.i18n.NumberFormatSymbols_kn', 'goog.i18n.NumberFormatSymbols_kn_IN', 'goog.i18n.NumberFormatSymbols_ko', 'goog.i18n.NumberFormatSymbols_ko_KR', 'goog.i18n.NumberFormatSymbols_ky', 'goog.i18n.NumberFormatSymbols_ky_Cyrl_KG', 'goog.i18n.NumberFormatSymbols_ln', 'goog.i18n.NumberFormatSymbols_ln_CD', 'goog.i18n.NumberFormatSymbols_lo', 'goog.i18n.NumberFormatSymbols_lo_LA', 'goog.i18n.NumberFormatSymbols_lt', 'goog.i18n.NumberFormatSymbols_lt_LT', 'goog.i18n.NumberFormatSymbols_lv', 'goog.i18n.NumberFormatSymbols_lv_LV', 'goog.i18n.NumberFormatSymbols_mk', 'goog.i18n.NumberFormatSymbols_mk_MK', 'goog.i18n.NumberFormatSymbols_ml', 'goog.i18n.NumberFormatSymbols_ml_IN', 'goog.i18n.NumberFormatSymbols_mn', 'goog.i18n.NumberFormatSymbols_mn_Cyrl_MN', 'goog.i18n.NumberFormatSymbols_mr', 'goog.i18n.NumberFormatSymbols_mr_IN', 'goog.i18n.NumberFormatSymbols_ms', 'goog.i18n.NumberFormatSymbols_ms_Latn_MY', 'goog.i18n.NumberFormatSymbols_mt', 'goog.i18n.NumberFormatSymbols_mt_MT', 'goog.i18n.NumberFormatSymbols_my', 'goog.i18n.NumberFormatSymbols_my_MM', 'goog.i18n.NumberFormatSymbols_nb', 'goog.i18n.NumberFormatSymbols_nb_NO', 'goog.i18n.NumberFormatSymbols_nb_SJ', 'goog.i18n.NumberFormatSymbols_ne', 'goog.i18n.NumberFormatSymbols_ne_NP', 'goog.i18n.NumberFormatSymbols_nl', 'goog.i18n.NumberFormatSymbols_nl_NL', 'goog.i18n.NumberFormatSymbols_no', 'goog.i18n.NumberFormatSymbols_no_NO', 'goog.i18n.NumberFormatSymbols_or', 'goog.i18n.NumberFormatSymbols_or_IN', 'goog.i18n.NumberFormatSymbols_pa', 'goog.i18n.NumberFormatSymbols_pa_Guru_IN', 'goog.i18n.NumberFormatSymbols_pl', 'goog.i18n.NumberFormatSymbols_pl_PL', 'goog.i18n.NumberFormatSymbols_pt', 'goog.i18n.NumberFormatSymbols_pt_BR', 'goog.i18n.NumberFormatSymbols_pt_PT', 'goog.i18n.NumberFormatSymbols_ro', 'goog.i18n.NumberFormatSymbols_ro_RO', 'goog.i18n.NumberFormatSymbols_ru', 'goog.i18n.NumberFormatSymbols_ru_RU', 'goog.i18n.NumberFormatSymbols_si', 'goog.i18n.NumberFormatSymbols_si_LK', 'goog.i18n.NumberFormatSymbols_sk', 'goog.i18n.NumberFormatSymbols_sk_SK', 'goog.i18n.NumberFormatSymbols_sl', 'goog.i18n.NumberFormatSymbols_sl_SI', 'goog.i18n.NumberFormatSymbols_sq', 'goog.i18n.NumberFormatSymbols_sq_AL', 'goog.i18n.NumberFormatSymbols_sr', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_RS', 'goog.i18n.NumberFormatSymbols_sv', 'goog.i18n.NumberFormatSymbols_sv_SE', 'goog.i18n.NumberFormatSymbols_sw', 'goog.i18n.NumberFormatSymbols_sw_TZ', 'goog.i18n.NumberFormatSymbols_ta', 'goog.i18n.NumberFormatSymbols_ta_IN', 'goog.i18n.NumberFormatSymbols_te', 'goog.i18n.NumberFormatSymbols_te_IN', 'goog.i18n.NumberFormatSymbols_th', 'goog.i18n.NumberFormatSymbols_th_TH', 'goog.i18n.NumberFormatSymbols_tl', 'goog.i18n.NumberFormatSymbols_tr', 'goog.i18n.NumberFormatSymbols_tr_TR', 'goog.i18n.NumberFormatSymbols_uk', 'goog.i18n.NumberFormatSymbols_uk_UA', 'goog.i18n.NumberFormatSymbols_ur', 'goog.i18n.NumberFormatSymbols_ur_PK', 'goog.i18n.NumberFormatSymbols_uz', 'goog.i18n.NumberFormatSymbols_uz_Latn_UZ', 'goog.i18n.NumberFormatSymbols_vi', 'goog.i18n.NumberFormatSymbols_vi_VN', 'goog.i18n.NumberFormatSymbols_zh', 'goog.i18n.NumberFormatSymbols_zh_CN', 'goog.i18n.NumberFormatSymbols_zh_HK', 'goog.i18n.NumberFormatSymbols_zh_Hans_CN', 'goog.i18n.NumberFormatSymbols_zh_TW', 'goog.i18n.NumberFormatSymbols_zu', 'goog.i18n.NumberFormatSymbols_zu_ZA'], []);
-goog.addDependency('i18n/numberformatsymbolsext.js', ['goog.i18n.NumberFormatSymbolsExt', 'goog.i18n.NumberFormatSymbols_aa', 'goog.i18n.NumberFormatSymbols_aa_DJ', 'goog.i18n.NumberFormatSymbols_aa_ER', 'goog.i18n.NumberFormatSymbols_aa_ET', 'goog.i18n.NumberFormatSymbols_af_NA', 'goog.i18n.NumberFormatSymbols_agq', 'goog.i18n.NumberFormatSymbols_agq_CM', 'goog.i18n.NumberFormatSymbols_ak', 'goog.i18n.NumberFormatSymbols_ak_GH', 'goog.i18n.NumberFormatSymbols_ar_AE', 'goog.i18n.NumberFormatSymbols_ar_BH', 'goog.i18n.NumberFormatSymbols_ar_DJ', 'goog.i18n.NumberFormatSymbols_ar_DZ', 'goog.i18n.NumberFormatSymbols_ar_EG', 'goog.i18n.NumberFormatSymbols_ar_EH', 'goog.i18n.NumberFormatSymbols_ar_ER', 'goog.i18n.NumberFormatSymbols_ar_IL', 'goog.i18n.NumberFormatSymbols_ar_IQ', 'goog.i18n.NumberFormatSymbols_ar_JO', 'goog.i18n.NumberFormatSymbols_ar_KM', 'goog.i18n.NumberFormatSymbols_ar_KW', 'goog.i18n.NumberFormatSymbols_ar_LB', 'goog.i18n.NumberFormatSymbols_ar_LY', 'goog.i18n.NumberFormatSymbols_ar_MA', 'goog.i18n.NumberFormatSymbols_ar_MR', 'goog.i18n.NumberFormatSymbols_ar_OM', 'goog.i18n.NumberFormatSymbols_ar_PS', 'goog.i18n.NumberFormatSymbols_ar_QA', 'goog.i18n.NumberFormatSymbols_ar_SA', 'goog.i18n.NumberFormatSymbols_ar_SD', 'goog.i18n.NumberFormatSymbols_ar_SO', 'goog.i18n.NumberFormatSymbols_ar_SS', 'goog.i18n.NumberFormatSymbols_ar_SY', 'goog.i18n.NumberFormatSymbols_ar_TD', 'goog.i18n.NumberFormatSymbols_ar_TN', 'goog.i18n.NumberFormatSymbols_ar_YE', 'goog.i18n.NumberFormatSymbols_as', 'goog.i18n.NumberFormatSymbols_as_IN', 'goog.i18n.NumberFormatSymbols_asa', 'goog.i18n.NumberFormatSymbols_asa_TZ', 'goog.i18n.NumberFormatSymbols_ast', 'goog.i18n.NumberFormatSymbols_ast_ES', 'goog.i18n.NumberFormatSymbols_az_Cyrl', 'goog.i18n.NumberFormatSymbols_az_Latn', 'goog.i18n.NumberFormatSymbols_bas', 'goog.i18n.NumberFormatSymbols_bas_CM', 'goog.i18n.NumberFormatSymbols_be', 'goog.i18n.NumberFormatSymbols_be_BY', 'goog.i18n.NumberFormatSymbols_bem', 'goog.i18n.NumberFormatSymbols_bem_ZM', 'goog.i18n.NumberFormatSymbols_bez', 'goog.i18n.NumberFormatSymbols_bez_TZ', 'goog.i18n.NumberFormatSymbols_bm', 'goog.i18n.NumberFormatSymbols_bm_ML', 'goog.i18n.NumberFormatSymbols_bn_IN', 'goog.i18n.NumberFormatSymbols_bo', 'goog.i18n.NumberFormatSymbols_bo_CN', 'goog.i18n.NumberFormatSymbols_bo_IN', 'goog.i18n.NumberFormatSymbols_brx', 'goog.i18n.NumberFormatSymbols_brx_IN', 'goog.i18n.NumberFormatSymbols_bs', 'goog.i18n.NumberFormatSymbols_bs_Cyrl', 'goog.i18n.NumberFormatSymbols_bs_Cyrl_BA', 'goog.i18n.NumberFormatSymbols_bs_Latn', 'goog.i18n.NumberFormatSymbols_bs_Latn_BA', 'goog.i18n.NumberFormatSymbols_byn', 'goog.i18n.NumberFormatSymbols_byn_ER', 'goog.i18n.NumberFormatSymbols_cgg', 'goog.i18n.NumberFormatSymbols_cgg_UG', 'goog.i18n.NumberFormatSymbols_ckb', 'goog.i18n.NumberFormatSymbols_ckb_Arab', 'goog.i18n.NumberFormatSymbols_ckb_Arab_IQ', 'goog.i18n.NumberFormatSymbols_ckb_Arab_IR', 'goog.i18n.NumberFormatSymbols_ckb_IQ', 'goog.i18n.NumberFormatSymbols_ckb_IR', 'goog.i18n.NumberFormatSymbols_ckb_Latn', 'goog.i18n.NumberFormatSymbols_ckb_Latn_IQ', 'goog.i18n.NumberFormatSymbols_dav', 'goog.i18n.NumberFormatSymbols_dav_KE', 'goog.i18n.NumberFormatSymbols_de_LI', 'goog.i18n.NumberFormatSymbols_dje', 'goog.i18n.NumberFormatSymbols_dje_NE', 'goog.i18n.NumberFormatSymbols_dua', 'goog.i18n.NumberFormatSymbols_dua_CM', 'goog.i18n.NumberFormatSymbols_dyo', 'goog.i18n.NumberFormatSymbols_dyo_SN', 'goog.i18n.NumberFormatSymbols_dz', 'goog.i18n.NumberFormatSymbols_dz_BT', 'goog.i18n.NumberFormatSymbols_ebu', 'goog.i18n.NumberFormatSymbols_ebu_KE', 'goog.i18n.NumberFormatSymbols_ee', 'goog.i18n.NumberFormatSymbols_ee_GH', 'goog.i18n.NumberFormatSymbols_ee_TG', 'goog.i18n.NumberFormatSymbols_el_CY', 'goog.i18n.NumberFormatSymbols_en_150', 'goog.i18n.NumberFormatSymbols_en_AG', 'goog.i18n.NumberFormatSymbols_en_AI', 'goog.i18n.NumberFormatSymbols_en_BB', 'goog.i18n.NumberFormatSymbols_en_BE', 'goog.i18n.NumberFormatSymbols_en_BM', 'goog.i18n.NumberFormatSymbols_en_BS', 'goog.i18n.NumberFormatSymbols_en_BW', 'goog.i18n.NumberFormatSymbols_en_BZ', 'goog.i18n.NumberFormatSymbols_en_CA', 'goog.i18n.NumberFormatSymbols_en_CC', 'goog.i18n.NumberFormatSymbols_en_CK', 'goog.i18n.NumberFormatSymbols_en_CM', 'goog.i18n.NumberFormatSymbols_en_CX', 'goog.i18n.NumberFormatSymbols_en_DM', 'goog.i18n.NumberFormatSymbols_en_ER', 'goog.i18n.NumberFormatSymbols_en_FJ', 'goog.i18n.NumberFormatSymbols_en_FK', 'goog.i18n.NumberFormatSymbols_en_GD', 'goog.i18n.NumberFormatSymbols_en_GG', 'goog.i18n.NumberFormatSymbols_en_GH', 'goog.i18n.NumberFormatSymbols_en_GI', 'goog.i18n.NumberFormatSymbols_en_GM', 'goog.i18n.NumberFormatSymbols_en_GY', 'goog.i18n.NumberFormatSymbols_en_HK', 'goog.i18n.NumberFormatSymbols_en_IM', 'goog.i18n.NumberFormatSymbols_en_JE', 'goog.i18n.NumberFormatSymbols_en_JM', 'goog.i18n.NumberFormatSymbols_en_KE', 'goog.i18n.NumberFormatSymbols_en_KI', 'goog.i18n.NumberFormatSymbols_en_KN', 'goog.i18n.NumberFormatSymbols_en_KY', 'goog.i18n.NumberFormatSymbols_en_LC', 'goog.i18n.NumberFormatSymbols_en_LR', 'goog.i18n.NumberFormatSymbols_en_LS', 'goog.i18n.NumberFormatSymbols_en_MG', 'goog.i18n.NumberFormatSymbols_en_MO', 'goog.i18n.NumberFormatSymbols_en_MS', 'goog.i18n.NumberFormatSymbols_en_MT', 'goog.i18n.NumberFormatSymbols_en_MU', 'goog.i18n.NumberFormatSymbols_en_MW', 'goog.i18n.NumberFormatSymbols_en_NA', 'goog.i18n.NumberFormatSymbols_en_NF', 'goog.i18n.NumberFormatSymbols_en_NG', 'goog.i18n.NumberFormatSymbols_en_NR', 'goog.i18n.NumberFormatSymbols_en_NU', 'goog.i18n.NumberFormatSymbols_en_NZ', 'goog.i18n.NumberFormatSymbols_en_PG', 'goog.i18n.NumberFormatSymbols_en_PH', 'goog.i18n.NumberFormatSymbols_en_PK', 'goog.i18n.NumberFormatSymbols_en_PN', 'goog.i18n.NumberFormatSymbols_en_RW', 'goog.i18n.NumberFormatSymbols_en_SB', 'goog.i18n.NumberFormatSymbols_en_SC', 'goog.i18n.NumberFormatSymbols_en_SD', 'goog.i18n.NumberFormatSymbols_en_SH', 'goog.i18n.NumberFormatSymbols_en_SL', 'goog.i18n.NumberFormatSymbols_en_SS', 'goog.i18n.NumberFormatSymbols_en_SX', 'goog.i18n.NumberFormatSymbols_en_SZ', 'goog.i18n.NumberFormatSymbols_en_TK', 'goog.i18n.NumberFormatSymbols_en_TO', 'goog.i18n.NumberFormatSymbols_en_TT', 'goog.i18n.NumberFormatSymbols_en_TV', 'goog.i18n.NumberFormatSymbols_en_TZ', 'goog.i18n.NumberFormatSymbols_en_UG', 'goog.i18n.NumberFormatSymbols_en_VC', 'goog.i18n.NumberFormatSymbols_en_VU', 'goog.i18n.NumberFormatSymbols_en_WS', 'goog.i18n.NumberFormatSymbols_en_ZM', 'goog.i18n.NumberFormatSymbols_eo', 'goog.i18n.NumberFormatSymbols_eo_001', 'goog.i18n.NumberFormatSymbols_es_AR', 'goog.i18n.NumberFormatSymbols_es_BO', 'goog.i18n.NumberFormatSymbols_es_CL', 'goog.i18n.NumberFormatSymbols_es_CO', 'goog.i18n.NumberFormatSymbols_es_CR', 'goog.i18n.NumberFormatSymbols_es_CU', 'goog.i18n.NumberFormatSymbols_es_DO', 'goog.i18n.NumberFormatSymbols_es_EC', 'goog.i18n.NumberFormatSymbols_es_GQ', 'goog.i18n.NumberFormatSymbols_es_GT', 'goog.i18n.NumberFormatSymbols_es_HN', 'goog.i18n.NumberFormatSymbols_es_MX', 'goog.i18n.NumberFormatSymbols_es_NI', 'goog.i18n.NumberFormatSymbols_es_PA', 'goog.i18n.NumberFormatSymbols_es_PE', 'goog.i18n.NumberFormatSymbols_es_PH', 'goog.i18n.NumberFormatSymbols_es_PR', 'goog.i18n.NumberFormatSymbols_es_PY', 'goog.i18n.NumberFormatSymbols_es_SV', 'goog.i18n.NumberFormatSymbols_es_US', 'goog.i18n.NumberFormatSymbols_es_UY', 'goog.i18n.NumberFormatSymbols_es_VE', 'goog.i18n.NumberFormatSymbols_ewo', 'goog.i18n.NumberFormatSymbols_ewo_CM', 'goog.i18n.NumberFormatSymbols_fa_AF', 'goog.i18n.NumberFormatSymbols_ff', 'goog.i18n.NumberFormatSymbols_ff_CM', 'goog.i18n.NumberFormatSymbols_ff_GN', 'goog.i18n.NumberFormatSymbols_ff_MR', 'goog.i18n.NumberFormatSymbols_ff_SN', 'goog.i18n.NumberFormatSymbols_fo', 'goog.i18n.NumberFormatSymbols_fo_FO', 'goog.i18n.NumberFormatSymbols_fr_BE', 'goog.i18n.NumberFormatSymbols_fr_BF', 'goog.i18n.NumberFormatSymbols_fr_BI', 'goog.i18n.NumberFormatSymbols_fr_BJ', 'goog.i18n.NumberFormatSymbols_fr_CD', 'goog.i18n.NumberFormatSymbols_fr_CF', 'goog.i18n.NumberFormatSymbols_fr_CG', 'goog.i18n.NumberFormatSymbols_fr_CH', 'goog.i18n.NumberFormatSymbols_fr_CI', 'goog.i18n.NumberFormatSymbols_fr_CM', 'goog.i18n.NumberFormatSymbols_fr_DJ', 'goog.i18n.NumberFormatSymbols_fr_DZ', 'goog.i18n.NumberFormatSymbols_fr_GA', 'goog.i18n.NumberFormatSymbols_fr_GN', 'goog.i18n.NumberFormatSymbols_fr_GQ', 'goog.i18n.NumberFormatSymbols_fr_HT', 'goog.i18n.NumberFormatSymbols_fr_KM', 'goog.i18n.NumberFormatSymbols_fr_LU', 'goog.i18n.NumberFormatSymbols_fr_MA', 'goog.i18n.NumberFormatSymbols_fr_MG', 'goog.i18n.NumberFormatSymbols_fr_ML', 'goog.i18n.NumberFormatSymbols_fr_MR', 'goog.i18n.NumberFormatSymbols_fr_MU', 'goog.i18n.NumberFormatSymbols_fr_NC', 'goog.i18n.NumberFormatSymbols_fr_NE', 'goog.i18n.NumberFormatSymbols_fr_PF', 'goog.i18n.NumberFormatSymbols_fr_RW', 'goog.i18n.NumberFormatSymbols_fr_SC', 'goog.i18n.NumberFormatSymbols_fr_SN', 'goog.i18n.NumberFormatSymbols_fr_SY', 'goog.i18n.NumberFormatSymbols_fr_TD', 'goog.i18n.NumberFormatSymbols_fr_TG', 'goog.i18n.NumberFormatSymbols_fr_TN', 'goog.i18n.NumberFormatSymbols_fr_VU', 'goog.i18n.NumberFormatSymbols_fr_WF', 'goog.i18n.NumberFormatSymbols_fur', 'goog.i18n.NumberFormatSymbols_fur_IT', 'goog.i18n.NumberFormatSymbols_fy', 'goog.i18n.NumberFormatSymbols_fy_NL', 'goog.i18n.NumberFormatSymbols_ga', 'goog.i18n.NumberFormatSymbols_ga_IE', 'goog.i18n.NumberFormatSymbols_gd', 'goog.i18n.NumberFormatSymbols_gd_GB', 'goog.i18n.NumberFormatSymbols_guz', 'goog.i18n.NumberFormatSymbols_guz_KE', 'goog.i18n.NumberFormatSymbols_gv', 'goog.i18n.NumberFormatSymbols_gv_IM', 'goog.i18n.NumberFormatSymbols_ha', 'goog.i18n.NumberFormatSymbols_ha_Latn', 'goog.i18n.NumberFormatSymbols_ha_Latn_GH', 'goog.i18n.NumberFormatSymbols_ha_Latn_NE', 'goog.i18n.NumberFormatSymbols_ha_Latn_NG', 'goog.i18n.NumberFormatSymbols_hr_BA', 'goog.i18n.NumberFormatSymbols_ia', 'goog.i18n.NumberFormatSymbols_ia_FR', 'goog.i18n.NumberFormatSymbols_ig', 'goog.i18n.NumberFormatSymbols_ig_NG', 'goog.i18n.NumberFormatSymbols_ii', 'goog.i18n.NumberFormatSymbols_ii_CN', 'goog.i18n.NumberFormatSymbols_it_CH', 'goog.i18n.NumberFormatSymbols_jgo', 'goog.i18n.NumberFormatSymbols_jgo_CM', 'goog.i18n.NumberFormatSymbols_jmc', 'goog.i18n.NumberFormatSymbols_jmc_TZ', 'goog.i18n.NumberFormatSymbols_kab', 'goog.i18n.NumberFormatSymbols_kab_DZ', 'goog.i18n.NumberFormatSymbols_kam', 'goog.i18n.NumberFormatSymbols_kam_KE', 'goog.i18n.NumberFormatSymbols_kde', 'goog.i18n.NumberFormatSymbols_kde_TZ', 'goog.i18n.NumberFormatSymbols_kea', 'goog.i18n.NumberFormatSymbols_kea_CV', 'goog.i18n.NumberFormatSymbols_khq', 'goog.i18n.NumberFormatSymbols_khq_ML', 'goog.i18n.NumberFormatSymbols_ki', 'goog.i18n.NumberFormatSymbols_ki_KE', 'goog.i18n.NumberFormatSymbols_kk_Cyrl', 'goog.i18n.NumberFormatSymbols_kkj', 'goog.i18n.NumberFormatSymbols_kkj_CM', 'goog.i18n.NumberFormatSymbols_kl', 'goog.i18n.NumberFormatSymbols_kl_GL', 'goog.i18n.NumberFormatSymbols_kln', 'goog.i18n.NumberFormatSymbols_kln_KE', 'goog.i18n.NumberFormatSymbols_ko_KP', 'goog.i18n.NumberFormatSymbols_kok', 'goog.i18n.NumberFormatSymbols_kok_IN', 'goog.i18n.NumberFormatSymbols_ks', 'goog.i18n.NumberFormatSymbols_ks_Arab', 'goog.i18n.NumberFormatSymbols_ks_Arab_IN', 'goog.i18n.NumberFormatSymbols_ksb', 'goog.i18n.NumberFormatSymbols_ksb_TZ', 'goog.i18n.NumberFormatSymbols_ksf', 'goog.i18n.NumberFormatSymbols_ksf_CM', 'goog.i18n.NumberFormatSymbols_ksh', 'goog.i18n.NumberFormatSymbols_ksh_DE', 'goog.i18n.NumberFormatSymbols_kw', 'goog.i18n.NumberFormatSymbols_kw_GB', 'goog.i18n.NumberFormatSymbols_ky_Cyrl', 'goog.i18n.NumberFormatSymbols_lag', 'goog.i18n.NumberFormatSymbols_lag_TZ', 'goog.i18n.NumberFormatSymbols_lg', 'goog.i18n.NumberFormatSymbols_lg_UG', 'goog.i18n.NumberFormatSymbols_lkt', 'goog.i18n.NumberFormatSymbols_lkt_US', 'goog.i18n.NumberFormatSymbols_ln_AO', 'goog.i18n.NumberFormatSymbols_ln_CF', 'goog.i18n.NumberFormatSymbols_ln_CG', 'goog.i18n.NumberFormatSymbols_lu', 'goog.i18n.NumberFormatSymbols_lu_CD', 'goog.i18n.NumberFormatSymbols_luo', 'goog.i18n.NumberFormatSymbols_luo_KE', 'goog.i18n.NumberFormatSymbols_luy', 'goog.i18n.NumberFormatSymbols_luy_KE', 'goog.i18n.NumberFormatSymbols_mas', 'goog.i18n.NumberFormatSymbols_mas_KE', 'goog.i18n.NumberFormatSymbols_mas_TZ', 'goog.i18n.NumberFormatSymbols_mer', 'goog.i18n.NumberFormatSymbols_mer_KE', 'goog.i18n.NumberFormatSymbols_mfe', 'goog.i18n.NumberFormatSymbols_mfe_MU', 'goog.i18n.NumberFormatSymbols_mg', 'goog.i18n.NumberFormatSymbols_mg_MG', 'goog.i18n.NumberFormatSymbols_mgh', 'goog.i18n.NumberFormatSymbols_mgh_MZ', 'goog.i18n.NumberFormatSymbols_mgo', 'goog.i18n.NumberFormatSymbols_mgo_CM', 'goog.i18n.NumberFormatSymbols_mn_Cyrl', 'goog.i18n.NumberFormatSymbols_ms_Latn', 'goog.i18n.NumberFormatSymbols_ms_Latn_BN', 'goog.i18n.NumberFormatSymbols_ms_Latn_SG', 'goog.i18n.NumberFormatSymbols_mua', 'goog.i18n.NumberFormatSymbols_mua_CM', 'goog.i18n.NumberFormatSymbols_naq', 'goog.i18n.NumberFormatSymbols_naq_NA', 'goog.i18n.NumberFormatSymbols_nd', 'goog.i18n.NumberFormatSymbols_nd_ZW', 'goog.i18n.NumberFormatSymbols_ne_IN', 'goog.i18n.NumberFormatSymbols_nl_AW', 'goog.i18n.NumberFormatSymbols_nl_BE', 'goog.i18n.NumberFormatSymbols_nl_BQ', 'goog.i18n.NumberFormatSymbols_nl_CW', 'goog.i18n.NumberFormatSymbols_nl_SR', 'goog.i18n.NumberFormatSymbols_nl_SX', 'goog.i18n.NumberFormatSymbols_nmg', 'goog.i18n.NumberFormatSymbols_nmg_CM', 'goog.i18n.NumberFormatSymbols_nn', 'goog.i18n.NumberFormatSymbols_nn_NO', 'goog.i18n.NumberFormatSymbols_nnh', 'goog.i18n.NumberFormatSymbols_nnh_CM', 'goog.i18n.NumberFormatSymbols_nr', 'goog.i18n.NumberFormatSymbols_nr_ZA', 'goog.i18n.NumberFormatSymbols_nso', 'goog.i18n.NumberFormatSymbols_nso_ZA', 'goog.i18n.NumberFormatSymbols_nus', 'goog.i18n.NumberFormatSymbols_nus_SD', 'goog.i18n.NumberFormatSymbols_nyn', 'goog.i18n.NumberFormatSymbols_nyn_UG', 'goog.i18n.NumberFormatSymbols_om', 'goog.i18n.NumberFormatSymbols_om_ET', 'goog.i18n.NumberFormatSymbols_om_KE', 'goog.i18n.NumberFormatSymbols_os', 'goog.i18n.NumberFormatSymbols_os_GE', 'goog.i18n.NumberFormatSymbols_os_RU', 'goog.i18n.NumberFormatSymbols_pa_Arab', 'goog.i18n.NumberFormatSymbols_pa_Arab_PK', 'goog.i18n.NumberFormatSymbols_pa_Guru', 'goog.i18n.NumberFormatSymbols_ps', 'goog.i18n.NumberFormatSymbols_ps_AF', 'goog.i18n.NumberFormatSymbols_pt_AO', 'goog.i18n.NumberFormatSymbols_pt_CV', 'goog.i18n.NumberFormatSymbols_pt_GW', 'goog.i18n.NumberFormatSymbols_pt_MO', 'goog.i18n.NumberFormatSymbols_pt_MZ', 'goog.i18n.NumberFormatSymbols_pt_ST', 'goog.i18n.NumberFormatSymbols_pt_TL', 'goog.i18n.NumberFormatSymbols_rm', 'goog.i18n.NumberFormatSymbols_rm_CH', 'goog.i18n.NumberFormatSymbols_rn', 'goog.i18n.NumberFormatSymbols_rn_BI', 'goog.i18n.NumberFormatSymbols_ro_MD', 'goog.i18n.NumberFormatSymbols_rof', 'goog.i18n.NumberFormatSymbols_rof_TZ', 'goog.i18n.NumberFormatSymbols_ru_BY', 'goog.i18n.NumberFormatSymbols_ru_KG', 'goog.i18n.NumberFormatSymbols_ru_KZ', 'goog.i18n.NumberFormatSymbols_ru_MD', 'goog.i18n.NumberFormatSymbols_ru_UA', 'goog.i18n.NumberFormatSymbols_rw', 'goog.i18n.NumberFormatSymbols_rw_RW', 'goog.i18n.NumberFormatSymbols_rwk', 'goog.i18n.NumberFormatSymbols_rwk_TZ', 'goog.i18n.NumberFormatSymbols_sah', 'goog.i18n.NumberFormatSymbols_sah_RU', 'goog.i18n.NumberFormatSymbols_saq', 'goog.i18n.NumberFormatSymbols_saq_KE', 'goog.i18n.NumberFormatSymbols_sbp', 'goog.i18n.NumberFormatSymbols_sbp_TZ', 'goog.i18n.NumberFormatSymbols_se', 'goog.i18n.NumberFormatSymbols_se_FI', 'goog.i18n.NumberFormatSymbols_se_NO', 'goog.i18n.NumberFormatSymbols_seh', 'goog.i18n.NumberFormatSymbols_seh_MZ', 'goog.i18n.NumberFormatSymbols_ses', 'goog.i18n.NumberFormatSymbols_ses_ML', 'goog.i18n.NumberFormatSymbols_sg', 'goog.i18n.NumberFormatSymbols_sg_CF', 'goog.i18n.NumberFormatSymbols_shi', 'goog.i18n.NumberFormatSymbols_shi_Latn', 'goog.i18n.NumberFormatSymbols_shi_Latn_MA', 'goog.i18n.NumberFormatSymbols_shi_Tfng', 'goog.i18n.NumberFormatSymbols_shi_Tfng_MA', 'goog.i18n.NumberFormatSymbols_sn', 'goog.i18n.NumberFormatSymbols_sn_ZW', 'goog.i18n.NumberFormatSymbols_so', 'goog.i18n.NumberFormatSymbols_so_DJ', 'goog.i18n.NumberFormatSymbols_so_ET', 'goog.i18n.NumberFormatSymbols_so_KE', 'goog.i18n.NumberFormatSymbols_so_SO', 'goog.i18n.NumberFormatSymbols_sq_MK', 'goog.i18n.NumberFormatSymbols_sq_XK', 'goog.i18n.NumberFormatSymbols_sr_Cyrl', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_BA', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_ME', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_XK', 'goog.i18n.NumberFormatSymbols_sr_Latn', 'goog.i18n.NumberFormatSymbols_sr_Latn_BA', 'goog.i18n.NumberFormatSymbols_sr_Latn_ME', 'goog.i18n.NumberFormatSymbols_sr_Latn_RS', 'goog.i18n.NumberFormatSymbols_sr_Latn_XK', 'goog.i18n.NumberFormatSymbols_ss', 'goog.i18n.NumberFormatSymbols_ss_SZ', 'goog.i18n.NumberFormatSymbols_ss_ZA', 'goog.i18n.NumberFormatSymbols_ssy', 'goog.i18n.NumberFormatSymbols_ssy_ER', 'goog.i18n.NumberFormatSymbols_st', 'goog.i18n.NumberFormatSymbols_st_LS', 'goog.i18n.NumberFormatSymbols_st_ZA', 'goog.i18n.NumberFormatSymbols_sv_AX', 'goog.i18n.NumberFormatSymbols_sv_FI', 'goog.i18n.NumberFormatSymbols_sw_KE', 'goog.i18n.NumberFormatSymbols_sw_UG', 'goog.i18n.NumberFormatSymbols_swc', 'goog.i18n.NumberFormatSymbols_swc_CD', 'goog.i18n.NumberFormatSymbols_ta_LK', 'goog.i18n.NumberFormatSymbols_ta_MY', 'goog.i18n.NumberFormatSymbols_ta_SG', 'goog.i18n.NumberFormatSymbols_teo', 'goog.i18n.NumberFormatSymbols_teo_KE', 'goog.i18n.NumberFormatSymbols_teo_UG', 'goog.i18n.NumberFormatSymbols_tg', 'goog.i18n.NumberFormatSymbols_tg_Cyrl', 'goog.i18n.NumberFormatSymbols_tg_Cyrl_TJ', 'goog.i18n.NumberFormatSymbols_ti', 'goog.i18n.NumberFormatSymbols_ti_ER', 'goog.i18n.NumberFormatSymbols_ti_ET', 'goog.i18n.NumberFormatSymbols_tig', 'goog.i18n.NumberFormatSymbols_tig_ER', 'goog.i18n.NumberFormatSymbols_tn', 'goog.i18n.NumberFormatSymbols_tn_BW', 'goog.i18n.NumberFormatSymbols_tn_ZA', 'goog.i18n.NumberFormatSymbols_to', 'goog.i18n.NumberFormatSymbols_to_TO', 'goog.i18n.NumberFormatSymbols_tr_CY', 'goog.i18n.NumberFormatSymbols_ts', 'goog.i18n.NumberFormatSymbols_ts_ZA', 'goog.i18n.NumberFormatSymbols_twq', 'goog.i18n.NumberFormatSymbols_twq_NE', 'goog.i18n.NumberFormatSymbols_tzm', 'goog.i18n.NumberFormatSymbols_tzm_Latn', 'goog.i18n.NumberFormatSymbols_tzm_Latn_MA', 'goog.i18n.NumberFormatSymbols_ug', 'goog.i18n.NumberFormatSymbols_ug_Arab', 'goog.i18n.NumberFormatSymbols_ug_Arab_CN', 'goog.i18n.NumberFormatSymbols_ur_IN', 'goog.i18n.NumberFormatSymbols_uz_Arab', 'goog.i18n.NumberFormatSymbols_uz_Arab_AF', 'goog.i18n.NumberFormatSymbols_uz_Cyrl', 'goog.i18n.NumberFormatSymbols_uz_Cyrl_UZ', 'goog.i18n.NumberFormatSymbols_uz_Latn', 'goog.i18n.NumberFormatSymbols_vai', 'goog.i18n.NumberFormatSymbols_vai_Latn', 'goog.i18n.NumberFormatSymbols_vai_Latn_LR', 'goog.i18n.NumberFormatSymbols_vai_Vaii', 'goog.i18n.NumberFormatSymbols_vai_Vaii_LR', 'goog.i18n.NumberFormatSymbols_ve', 'goog.i18n.NumberFormatSymbols_ve_ZA', 'goog.i18n.NumberFormatSymbols_vo', 'goog.i18n.NumberFormatSymbols_vo_001', 'goog.i18n.NumberFormatSymbols_vun', 'goog.i18n.NumberFormatSymbols_vun_TZ', 'goog.i18n.NumberFormatSymbols_wae', 'goog.i18n.NumberFormatSymbols_wae_CH', 'goog.i18n.NumberFormatSymbols_wal', 'goog.i18n.NumberFormatSymbols_wal_ET', 'goog.i18n.NumberFormatSymbols_xh', 'goog.i18n.NumberFormatSymbols_xh_ZA', 'goog.i18n.NumberFormatSymbols_xog', 'goog.i18n.NumberFormatSymbols_xog_UG', 'goog.i18n.NumberFormatSymbols_yav', 'goog.i18n.NumberFormatSymbols_yav_CM', 'goog.i18n.NumberFormatSymbols_yo', 'goog.i18n.NumberFormatSymbols_yo_BJ', 'goog.i18n.NumberFormatSymbols_yo_NG', 'goog.i18n.NumberFormatSymbols_zgh', 'goog.i18n.NumberFormatSymbols_zgh_MA', 'goog.i18n.NumberFormatSymbols_zh_Hans', 'goog.i18n.NumberFormatSymbols_zh_Hans_HK', 'goog.i18n.NumberFormatSymbols_zh_Hans_MO', 'goog.i18n.NumberFormatSymbols_zh_Hans_SG', 'goog.i18n.NumberFormatSymbols_zh_Hant', 'goog.i18n.NumberFormatSymbols_zh_Hant_HK', 'goog.i18n.NumberFormatSymbols_zh_Hant_MO', 'goog.i18n.NumberFormatSymbols_zh_Hant_TW'], ['goog.i18n.NumberFormatSymbols']);
-goog.addDependency('i18n/ordinalrules.js', ['goog.i18n.ordinalRules'], []);
-goog.addDependency('i18n/pluralrules.js', ['goog.i18n.pluralRules'], []);
-goog.addDependency('i18n/pluralrules_test.js', ['goog.i18n.pluralRulesTest'], ['goog.i18n.pluralRules', 'goog.testing.jsunit']);
-goog.addDependency('i18n/timezone.js', ['goog.i18n.TimeZone'], ['goog.array', 'goog.date.DateLike', 'goog.string']);
-goog.addDependency('i18n/timezone_test.js', ['goog.i18n.TimeZoneTest'], ['goog.i18n.TimeZone', 'goog.testing.jsunit']);
-goog.addDependency('i18n/uchar.js', ['goog.i18n.uChar'], []);
-goog.addDependency('i18n/uchar/localnamefetcher.js', ['goog.i18n.uChar.LocalNameFetcher'], ['goog.i18n.uChar', 'goog.i18n.uChar.NameFetcher', 'goog.log']);
-goog.addDependency('i18n/uchar/localnamefetcher_test.js', ['goog.i18n.uChar.LocalNameFetcherTest'], ['goog.i18n.uChar.LocalNameFetcher', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('i18n/uchar/namefetcher.js', ['goog.i18n.uChar.NameFetcher'], []);
-goog.addDependency('i18n/uchar/remotenamefetcher.js', ['goog.i18n.uChar.RemoteNameFetcher'], ['goog.Disposable', 'goog.Uri', 'goog.i18n.uChar', 'goog.i18n.uChar.NameFetcher', 'goog.log', 'goog.net.XhrIo', 'goog.structs.Map']);
-goog.addDependency('i18n/uchar/remotenamefetcher_test.js', ['goog.i18n.uChar.RemoteNameFetcherTest'], ['goog.i18n.uChar.RemoteNameFetcher', 'goog.net.XhrIo', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction']);
-goog.addDependency('i18n/uchar_test.js', ['goog.i18n.uCharTest'], ['goog.i18n.uChar', 'goog.testing.jsunit']);
-goog.addDependency('iter/iter.js', ['goog.iter', 'goog.iter.Iterable', 'goog.iter.Iterator', 'goog.iter.StopIteration'], ['goog.array', 'goog.asserts', 'goog.functions', 'goog.math']);
-goog.addDependency('iter/iter_test.js', ['goog.iterTest'], ['goog.iter', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.testing.jsunit']);
-goog.addDependency('json/evaljsonprocessor.js', ['goog.json.EvalJsonProcessor'], ['goog.json', 'goog.json.Processor', 'goog.json.Serializer']);
-goog.addDependency('json/hybrid.js', ['goog.json.hybrid'], ['goog.asserts', 'goog.json']);
-goog.addDependency('json/hybrid_test.js', ['goog.json.hybridTest'], ['goog.json', 'goog.json.hybrid', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('json/hybridjsonprocessor.js', ['goog.json.HybridJsonProcessor'], ['goog.json.Processor', 'goog.json.hybrid']);
-goog.addDependency('json/hybridjsonprocessor_test.js', ['goog.json.HybridJsonProcessorTest'], ['goog.json.HybridJsonProcessor', 'goog.json.hybrid', 'goog.testing.jsunit']);
-goog.addDependency('json/json.js', ['goog.json', 'goog.json.Replacer', 'goog.json.Reviver', 'goog.json.Serializer'], []);
-goog.addDependency('json/json_perf.js', ['goog.jsonPerf'], ['goog.dom', 'goog.json', 'goog.math', 'goog.string', 'goog.testing.PerformanceTable', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('json/json_test.js', ['goog.jsonTest'], ['goog.functions', 'goog.json', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('json/nativejsonprocessor.js', ['goog.json.NativeJsonProcessor'], ['goog.asserts', 'goog.json', 'goog.json.Processor']);
-goog.addDependency('json/processor.js', ['goog.json.Processor'], ['goog.string.Parser', 'goog.string.Stringifier']);
-goog.addDependency('json/processor_test.js', ['goog.json.processorTest'], ['goog.json.EvalJsonProcessor', 'goog.json.NativeJsonProcessor', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('labs/dom/pagevisibilitymonitor.js', ['goog.labs.dom.PageVisibilityEvent', 'goog.labs.dom.PageVisibilityMonitor', 'goog.labs.dom.PageVisibilityState'], ['goog.dom', 'goog.dom.vendor', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.memoize']);
-goog.addDependency('labs/dom/pagevisibilitymonitor_test.js', ['goog.labs.dom.PageVisibilityMonitorTest'], ['goog.events', 'goog.functions', 'goog.labs.dom.PageVisibilityMonitor', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('labs/events/nondisposableeventtarget.js', ['goog.labs.events.NonDisposableEventTarget'], ['goog.array', 'goog.asserts', 'goog.events.Event', 'goog.events.Listenable', 'goog.events.ListenerMap', 'goog.object']);
-goog.addDependency('labs/events/nondisposableeventtarget_test.js', ['goog.labs.events.NonDisposableEventTargetTest'], ['goog.events.Listenable', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.labs.events.NonDisposableEventTarget', 'goog.testing.jsunit']);
-goog.addDependency('labs/events/nondisposableeventtarget_via_googevents_test.js', ['goog.labs.events.NonDisposableEventTargetGoogEventsTest'], ['goog.events', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.labs.events.NonDisposableEventTarget', 'goog.object', 'goog.testing', 'goog.testing.jsunit']);
-goog.addDependency('labs/events/touch.js', ['goog.labs.events.touch', 'goog.labs.events.touch.TouchData'], ['goog.array', 'goog.asserts', 'goog.events.EventType', 'goog.string']);
-goog.addDependency('labs/events/touch_test.js', ['goog.labs.events.touchTest'], ['goog.labs.events.touch', 'goog.testing.jsunit']);
-goog.addDependency('labs/format/csv.js', ['goog.labs.format.csv', 'goog.labs.format.csv.ParseError', 'goog.labs.format.csv.Token'], ['goog.array', 'goog.asserts', 'goog.debug.Error', 'goog.object', 'goog.string', 'goog.string.newlines']);
-goog.addDependency('labs/format/csv_test.js', ['goog.labs.format.csvTest'], ['goog.labs.format.csv', 'goog.labs.format.csv.ParseError', 'goog.object', 'goog.testing.asserts', 'goog.testing.jsunit']);
-goog.addDependency('labs/html/attribute_rewriter.js', ['goog.labs.html.AttributeRewriter', 'goog.labs.html.AttributeValue', 'goog.labs.html.attributeRewriterPresubmitWorkaround'], []);
-goog.addDependency('labs/html/sanitizer.js', ['goog.labs.html.Sanitizer'], ['goog.asserts', 'goog.html.SafeUrl', 'goog.labs.html.attributeRewriterPresubmitWorkaround', 'goog.labs.html.scrubber', 'goog.object', 'goog.string']);
-goog.addDependency('labs/html/sanitizer_test.js', ['goog.labs.html.SanitizerTest'], ['goog.html.SafeUrl', 'goog.labs.html.Sanitizer', 'goog.string', 'goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('labs/html/scrubber.js', ['goog.labs.html.scrubber'], ['goog.array', 'goog.dom.tags', 'goog.labs.html.attributeRewriterPresubmitWorkaround', 'goog.string']);
-goog.addDependency('labs/html/scrubber_test.js', ['goog.html.ScrubberTest'], ['goog.labs.html.scrubber', 'goog.object', 'goog.string', 'goog.testing.jsunit']);
-goog.addDependency('labs/i18n/listformat.js', ['goog.labs.i18n.GenderInfo', 'goog.labs.i18n.GenderInfo.Gender', 'goog.labs.i18n.ListFormat'], ['goog.asserts', 'goog.labs.i18n.ListFormatSymbols']);
-goog.addDependency('labs/i18n/listformat_test.js', ['goog.labs.i18n.ListFormatTest'], ['goog.labs.i18n.GenderInfo', 'goog.labs.i18n.ListFormat', 'goog.labs.i18n.ListFormatSymbols', 'goog.labs.i18n.ListFormatSymbols_el', 'goog.labs.i18n.ListFormatSymbols_en', 'goog.labs.i18n.ListFormatSymbols_fr', 'goog.labs.i18n.ListFormatSymbols_ml', 'goog.labs.i18n.ListFormatSymbols_zu', 'goog.testing.jsunit']);
-goog.addDependency('labs/i18n/listsymbols.js', ['goog.labs.i18n.ListFormatSymbols', 'goog.labs.i18n.ListFormatSymbols_af', 'goog.labs.i18n.ListFormatSymbols_am', 'goog.labs.i18n.ListFormatSymbols_ar', 'goog.labs.i18n.ListFormatSymbols_az', 'goog.labs.i18n.ListFormatSymbols_bg', 'goog.labs.i18n.ListFormatSymbols_bn', 'goog.labs.i18n.ListFormatSymbols_br', 'goog.labs.i18n.ListFormatSymbols_ca', 'goog.labs.i18n.ListFormatSymbols_chr', 'goog.labs.i18n.ListFormatSymbols_cs', 'goog.labs.i18n.ListFormatSymbols_cy', 'goog.labs.i18n.ListFormatSymbols_da', 'goog.labs.i18n.ListFormatSymbols_de', 'goog.labs.i18n.ListFormatSymbols_de_AT', 'goog.labs.i18n.ListFormatSymbols_de_CH', 'goog.labs.i18n.ListFormatSymbols_el', 'goog.labs.i18n.ListFormatSymbols_en', 'goog.labs.i18n.ListFormatSymbols_en_AU', 'goog.labs.i18n.ListFormatSymbols_en_GB', 'goog.labs.i18n.ListFormatSymbols_en_IE', 'goog.labs.i18n.ListFormatSymbols_en_IN', 'goog.labs.i18n.ListFormatSymbols_en_ISO', 'goog.labs.i18n.ListFormatSymbols_en_SG', 'goog.labs.i18n.ListFormatSymbols_en_US', 'goog.labs.i18n.ListFormatSymbols_en_ZA', 'goog.labs.i18n.ListFormatSymbols_es', 'goog.labs.i18n.ListFormatSymbols_es_419', 'goog.labs.i18n.ListFormatSymbols_es_ES', 'goog.labs.i18n.ListFormatSymbols_et', 'goog.labs.i18n.ListFormatSymbols_eu', 'goog.labs.i18n.ListFormatSymbols_fa', 'goog.labs.i18n.ListFormatSymbols_fi', 'goog.labs.i18n.ListFormatSymbols_fil', 'goog.labs.i18n.ListFormatSymbols_fr', 'goog.labs.i18n.ListFormatSymbols_fr_CA', 'goog.labs.i18n.ListFormatSymbols_gl', 'goog.labs.i18n.ListFormatSymbols_gsw', 'goog.labs.i18n.ListFormatSymbols_gu', 'goog.labs.i18n.ListFormatSymbols_haw', 'goog.labs.i18n.ListFormatSymbols_he', 'goog.labs.i18n.ListFormatSymbols_hi', 'goog.labs.i18n.ListFormatSymbols_hr', 'goog.labs.i18n.ListFormatSymbols_hu', 'goog.labs.i18n.ListFormatSymbols_hy', 'goog.labs.i18n.ListFormatSymbols_id', 'goog.labs.i18n.ListFormatSymbols_in', 'goog.labs.i18n.ListFormatSymbols_is', 'goog.labs.i18n.ListFormatSymbols_it', 'goog.labs.i18n.ListFormatSymbols_iw', 'goog.labs.i18n.ListFormatSymbols_ja', 'goog.labs.i18n.ListFormatSymbols_ka', 'goog.labs.i18n.ListFormatSymbols_kk', 'goog.labs.i18n.ListFormatSymbols_km', 'goog.labs.i18n.ListFormatSymbols_kn', 'goog.labs.i18n.ListFormatSymbols_ko', 'goog.labs.i18n.ListFormatSymbols_ky', 'goog.labs.i18n.ListFormatSymbols_ln', 'goog.labs.i18n.ListFormatSymbols_lo', 'goog.labs.i18n.ListFormatSymbols_lt', 'goog.labs.i18n.ListFormatSymbols_lv', 'goog.labs.i18n.ListFormatSymbols_mk', 'goog.labs.i18n.ListFormatSymbols_ml', 'goog.labs.i18n.ListFormatSymbols_mn', 'goog.labs.i18n.ListFormatSymbols_mo', 'goog.labs.i18n.ListFormatSymbols_mr', 'goog.labs.i18n.ListFormatSymbols_ms', 'goog.labs.i18n.ListFormatSymbols_mt', 'goog.labs.i18n.ListFormatSymbols_my', 'goog.labs.i18n.ListFormatSymbols_nb', 'goog.labs.i18n.ListFormatSymbols_ne', 'goog.labs.i18n.ListFormatSymbols_nl', 'goog.labs.i18n.ListFormatSymbols_no', 'goog.labs.i18n.ListFormatSymbols_no_NO', 'goog.labs.i18n.ListFormatSymbols_or', 'goog.labs.i18n.ListFormatSymbols_pa', 'goog.labs.i18n.ListFormatSymbols_pl', 'goog.labs.i18n.ListFormatSymbols_pt', 'goog.labs.i18n.ListFormatSymbols_pt_BR', 'goog.labs.i18n.ListFormatSymbols_pt_PT', 'goog.labs.i18n.ListFormatSymbols_ro', 'goog.labs.i18n.ListFormatSymbols_ru', 'goog.labs.i18n.ListFormatSymbols_sh', 'goog.labs.i18n.ListFormatSymbols_si', 'goog.labs.i18n.ListFormatSymbols_sk', 'goog.labs.i18n.ListFormatSymbols_sl', 'goog.labs.i18n.ListFormatSymbols_sq', 'goog.labs.i18n.ListFormatSymbols_sr', 'goog.labs.i18n.ListFormatSymbols_sv', 'goog.labs.i18n.ListFormatSymbols_sw', 'goog.labs.i18n.ListFormatSymbols_ta', 'goog.labs.i18n.ListFormatSymbols_te', 'goog.labs.i18n.ListFormatSymbols_th', 'goog.labs.i18n.ListFormatSymbols_tl', 'goog.labs.i18n.ListFormatSymbols_tr', 'goog.labs.i18n.ListFormatSymbols_uk', 'goog.labs.i18n.ListFormatSymbols_ur', 'goog.labs.i18n.ListFormatSymbols_uz', 'goog.labs.i18n.ListFormatSymbols_vi', 'goog.labs.i18n.ListFormatSymbols_zh', 'goog.labs.i18n.ListFormatSymbols_zh_CN', 'goog.labs.i18n.ListFormatSymbols_zh_HK', 'goog.labs.i18n.ListFormatSymbols_zh_TW', 'goog.labs.i18n.ListFormatSymbols_zu'], []);
-goog.addDependency('labs/i18n/listsymbolsext.js', ['goog.labs.i18n.ListFormatSymbolsExt', 'goog.labs.i18n.ListFormatSymbols_af_NA', 'goog.labs.i18n.ListFormatSymbols_af_ZA', 'goog.labs.i18n.ListFormatSymbols_agq', 'goog.labs.i18n.ListFormatSymbols_agq_CM', 'goog.labs.i18n.ListFormatSymbols_ak', 'goog.labs.i18n.ListFormatSymbols_ak_GH', 'goog.labs.i18n.ListFormatSymbols_am_ET', 'goog.labs.i18n.ListFormatSymbols_ar_001', 'goog.labs.i18n.ListFormatSymbols_ar_AE', 'goog.labs.i18n.ListFormatSymbols_ar_BH', 'goog.labs.i18n.ListFormatSymbols_ar_DJ', 'goog.labs.i18n.ListFormatSymbols_ar_DZ', 'goog.labs.i18n.ListFormatSymbols_ar_EG', 'goog.labs.i18n.ListFormatSymbols_ar_EH', 'goog.labs.i18n.ListFormatSymbols_ar_ER', 'goog.labs.i18n.ListFormatSymbols_ar_IL', 'goog.labs.i18n.ListFormatSymbols_ar_IQ', 'goog.labs.i18n.ListFormatSymbols_ar_JO', 'goog.labs.i18n.ListFormatSymbols_ar_KM', 'goog.labs.i18n.ListFormatSymbols_ar_KW', 'goog.labs.i18n.ListFormatSymbols_ar_LB', 'goog.labs.i18n.ListFormatSymbols_ar_LY', 'goog.labs.i18n.ListFormatSymbols_ar_MA', 'goog.labs.i18n.ListFormatSymbols_ar_MR', 'goog.labs.i18n.ListFormatSymbols_ar_OM', 'goog.labs.i18n.ListFormatSymbols_ar_PS', 'goog.labs.i18n.ListFormatSymbols_ar_QA', 'goog.labs.i18n.ListFormatSymbols_ar_SA', 'goog.labs.i18n.ListFormatSymbols_ar_SD', 'goog.labs.i18n.ListFormatSymbols_ar_SO', 'goog.labs.i18n.ListFormatSymbols_ar_SS', 'goog.labs.i18n.ListFormatSymbols_ar_SY', 'goog.labs.i18n.ListFormatSymbols_ar_TD', 'goog.labs.i18n.ListFormatSymbols_ar_TN', 'goog.labs.i18n.ListFormatSymbols_ar_YE', 'goog.labs.i18n.ListFormatSymbols_as', 'goog.labs.i18n.ListFormatSymbols_as_IN', 'goog.labs.i18n.ListFormatSymbols_asa', 'goog.labs.i18n.ListFormatSymbols_asa_TZ', 'goog.labs.i18n.ListFormatSymbols_az_Cyrl', 'goog.labs.i18n.ListFormatSymbols_az_Cyrl_AZ', 'goog.labs.i18n.ListFormatSymbols_az_Latn', 'goog.labs.i18n.ListFormatSymbols_az_Latn_AZ', 'goog.labs.i18n.ListFormatSymbols_bas', 'goog.labs.i18n.ListFormatSymbols_bas_CM', 'goog.labs.i18n.ListFormatSymbols_be', 'goog.labs.i18n.ListFormatSymbols_be_BY', 'goog.labs.i18n.ListFormatSymbols_bem', 'goog.labs.i18n.ListFormatSymbols_bem_ZM', 'goog.labs.i18n.ListFormatSymbols_bez', 'goog.labs.i18n.ListFormatSymbols_bez_TZ', 'goog.labs.i18n.ListFormatSymbols_bg_BG', 'goog.labs.i18n.ListFormatSymbols_bm', 'goog.labs.i18n.ListFormatSymbols_bm_ML', 'goog.labs.i18n.ListFormatSymbols_bn_BD', 'goog.labs.i18n.ListFormatSymbols_bn_IN', 'goog.labs.i18n.ListFormatSymbols_bo', 'goog.labs.i18n.ListFormatSymbols_bo_CN', 'goog.labs.i18n.ListFormatSymbols_bo_IN', 'goog.labs.i18n.ListFormatSymbols_br_FR', 'goog.labs.i18n.ListFormatSymbols_brx', 'goog.labs.i18n.ListFormatSymbols_brx_IN', 'goog.labs.i18n.ListFormatSymbols_bs', 'goog.labs.i18n.ListFormatSymbols_bs_Cyrl', 'goog.labs.i18n.ListFormatSymbols_bs_Cyrl_BA', 'goog.labs.i18n.ListFormatSymbols_bs_Latn', 'goog.labs.i18n.ListFormatSymbols_bs_Latn_BA', 'goog.labs.i18n.ListFormatSymbols_ca_AD', 'goog.labs.i18n.ListFormatSymbols_ca_ES', 'goog.labs.i18n.ListFormatSymbols_ca_FR', 'goog.labs.i18n.ListFormatSymbols_ca_IT', 'goog.labs.i18n.ListFormatSymbols_cgg', 'goog.labs.i18n.ListFormatSymbols_cgg_UG', 'goog.labs.i18n.ListFormatSymbols_chr_US', 'goog.labs.i18n.ListFormatSymbols_cs_CZ', 'goog.labs.i18n.ListFormatSymbols_cy_GB', 'goog.labs.i18n.ListFormatSymbols_da_DK', 'goog.labs.i18n.ListFormatSymbols_da_GL', 'goog.labs.i18n.ListFormatSymbols_dav', 'goog.labs.i18n.ListFormatSymbols_dav_KE', 'goog.labs.i18n.ListFormatSymbols_de_BE', 'goog.labs.i18n.ListFormatSymbols_de_DE', 'goog.labs.i18n.ListFormatSymbols_de_LI', 'goog.labs.i18n.ListFormatSymbols_de_LU', 'goog.labs.i18n.ListFormatSymbols_dje', 'goog.labs.i18n.ListFormatSymbols_dje_NE', 'goog.labs.i18n.ListFormatSymbols_dua', 'goog.labs.i18n.ListFormatSymbols_dua_CM', 'goog.labs.i18n.ListFormatSymbols_dyo', 'goog.labs.i18n.ListFormatSymbols_dyo_SN', 'goog.labs.i18n.ListFormatSymbols_dz', 'goog.labs.i18n.ListFormatSymbols_dz_BT', 'goog.labs.i18n.ListFormatSymbols_ebu', 'goog.labs.i18n.ListFormatSymbols_ebu_KE', 'goog.labs.i18n.ListFormatSymbols_ee', 'goog.labs.i18n.ListFormatSymbols_ee_GH', 'goog.labs.i18n.ListFormatSymbols_ee_TG', 'goog.labs.i18n.ListFormatSymbols_el_CY', 'goog.labs.i18n.ListFormatSymbols_el_GR', 'goog.labs.i18n.ListFormatSymbols_en_001', 'goog.labs.i18n.ListFormatSymbols_en_150', 'goog.labs.i18n.ListFormatSymbols_en_AG', 'goog.labs.i18n.ListFormatSymbols_en_AI', 'goog.labs.i18n.ListFormatSymbols_en_AS', 'goog.labs.i18n.ListFormatSymbols_en_BB', 'goog.labs.i18n.ListFormatSymbols_en_BE', 'goog.labs.i18n.ListFormatSymbols_en_BM', 'goog.labs.i18n.ListFormatSymbols_en_BS', 'goog.labs.i18n.ListFormatSymbols_en_BW', 'goog.labs.i18n.ListFormatSymbols_en_BZ', 'goog.labs.i18n.ListFormatSymbols_en_CA', 'goog.labs.i18n.ListFormatSymbols_en_CC', 'goog.labs.i18n.ListFormatSymbols_en_CK', 'goog.labs.i18n.ListFormatSymbols_en_CM', 'goog.labs.i18n.ListFormatSymbols_en_CX', 'goog.labs.i18n.ListFormatSymbols_en_DG', 'goog.labs.i18n.ListFormatSymbols_en_DM', 'goog.labs.i18n.ListFormatSymbols_en_ER', 'goog.labs.i18n.ListFormatSymbols_en_FJ', 'goog.labs.i18n.ListFormatSymbols_en_FK', 'goog.labs.i18n.ListFormatSymbols_en_FM', 'goog.labs.i18n.ListFormatSymbols_en_GD', 'goog.labs.i18n.ListFormatSymbols_en_GG', 'goog.labs.i18n.ListFormatSymbols_en_GH', 'goog.labs.i18n.ListFormatSymbols_en_GI', 'goog.labs.i18n.ListFormatSymbols_en_GM', 'goog.labs.i18n.ListFormatSymbols_en_GU', 'goog.labs.i18n.ListFormatSymbols_en_GY', 'goog.labs.i18n.ListFormatSymbols_en_HK', 'goog.labs.i18n.ListFormatSymbols_en_IM', 'goog.labs.i18n.ListFormatSymbols_en_IO', 'goog.labs.i18n.ListFormatSymbols_en_JE', 'goog.labs.i18n.ListFormatSymbols_en_JM', 'goog.labs.i18n.ListFormatSymbols_en_KE', 'goog.labs.i18n.ListFormatSymbols_en_KI', 'goog.labs.i18n.ListFormatSymbols_en_KN', 'goog.labs.i18n.ListFormatSymbols_en_KY', 'goog.labs.i18n.ListFormatSymbols_en_LC', 'goog.labs.i18n.ListFormatSymbols_en_LR', 'goog.labs.i18n.ListFormatSymbols_en_LS', 'goog.labs.i18n.ListFormatSymbols_en_MG', 'goog.labs.i18n.ListFormatSymbols_en_MH', 'goog.labs.i18n.ListFormatSymbols_en_MO', 'goog.labs.i18n.ListFormatSymbols_en_MP', 'goog.labs.i18n.ListFormatSymbols_en_MS', 'goog.labs.i18n.ListFormatSymbols_en_MT', 'goog.labs.i18n.ListFormatSymbols_en_MU', 'goog.labs.i18n.ListFormatSymbols_en_MW', 'goog.labs.i18n.ListFormatSymbols_en_NA', 'goog.labs.i18n.ListFormatSymbols_en_NF', 'goog.labs.i18n.ListFormatSymbols_en_NG', 'goog.labs.i18n.ListFormatSymbols_en_NR', 'goog.labs.i18n.ListFormatSymbols_en_NU', 'goog.labs.i18n.ListFormatSymbols_en_NZ', 'goog.labs.i18n.ListFormatSymbols_en_PG', 'goog.labs.i18n.ListFormatSymbols_en_PH', 'goog.labs.i18n.ListFormatSymbols_en_PK', 'goog.labs.i18n.ListFormatSymbols_en_PN', 'goog.labs.i18n.ListFormatSymbols_en_PR', 'goog.labs.i18n.ListFormatSymbols_en_PW', 'goog.labs.i18n.ListFormatSymbols_en_RW', 'goog.labs.i18n.ListFormatSymbols_en_SB', 'goog.labs.i18n.ListFormatSymbols_en_SC', 'goog.labs.i18n.ListFormatSymbols_en_SD', 'goog.labs.i18n.ListFormatSymbols_en_SH', 'goog.labs.i18n.ListFormatSymbols_en_SL', 'goog.labs.i18n.ListFormatSymbols_en_SS', 'goog.labs.i18n.ListFormatSymbols_en_SX', 'goog.labs.i18n.ListFormatSymbols_en_SZ', 'goog.labs.i18n.ListFormatSymbols_en_TC', 'goog.labs.i18n.ListFormatSymbols_en_TK', 'goog.labs.i18n.ListFormatSymbols_en_TO', 'goog.labs.i18n.ListFormatSymbols_en_TT', 'goog.labs.i18n.ListFormatSymbols_en_TV', 'goog.labs.i18n.ListFormatSymbols_en_TZ', 'goog.labs.i18n.ListFormatSymbols_en_UG', 'goog.labs.i18n.ListFormatSymbols_en_UM', 'goog.labs.i18n.ListFormatSymbols_en_US_POSIX', 'goog.labs.i18n.ListFormatSymbols_en_VC', 'goog.labs.i18n.ListFormatSymbols_en_VG', 'goog.labs.i18n.ListFormatSymbols_en_VI', 'goog.labs.i18n.ListFormatSymbols_en_VU', 'goog.labs.i18n.ListFormatSymbols_en_WS', 'goog.labs.i18n.ListFormatSymbols_en_ZM', 'goog.labs.i18n.ListFormatSymbols_en_ZW', 'goog.labs.i18n.ListFormatSymbols_eo', 'goog.labs.i18n.ListFormatSymbols_es_AR', 'goog.labs.i18n.ListFormatSymbols_es_BO', 'goog.labs.i18n.ListFormatSymbols_es_CL', 'goog.labs.i18n.ListFormatSymbols_es_CO', 'goog.labs.i18n.ListFormatSymbols_es_CR', 'goog.labs.i18n.ListFormatSymbols_es_CU', 'goog.labs.i18n.ListFormatSymbols_es_DO', 'goog.labs.i18n.ListFormatSymbols_es_EA', 'goog.labs.i18n.ListFormatSymbols_es_EC', 'goog.labs.i18n.ListFormatSymbols_es_GQ', 'goog.labs.i18n.ListFormatSymbols_es_GT', 'goog.labs.i18n.ListFormatSymbols_es_HN', 'goog.labs.i18n.ListFormatSymbols_es_IC', 'goog.labs.i18n.ListFormatSymbols_es_MX', 'goog.labs.i18n.ListFormatSymbols_es_NI', 'goog.labs.i18n.ListFormatSymbols_es_PA', 'goog.labs.i18n.ListFormatSymbols_es_PE', 'goog.labs.i18n.ListFormatSymbols_es_PH', 'goog.labs.i18n.ListFormatSymbols_es_PR', 'goog.labs.i18n.ListFormatSymbols_es_PY', 'goog.labs.i18n.ListFormatSymbols_es_SV', 'goog.labs.i18n.ListFormatSymbols_es_US', 'goog.labs.i18n.ListFormatSymbols_es_UY', 'goog.labs.i18n.ListFormatSymbols_es_VE', 'goog.labs.i18n.ListFormatSymbols_et_EE', 'goog.labs.i18n.ListFormatSymbols_eu_ES', 'goog.labs.i18n.ListFormatSymbols_ewo', 'goog.labs.i18n.ListFormatSymbols_ewo_CM', 'goog.labs.i18n.ListFormatSymbols_fa_AF', 'goog.labs.i18n.ListFormatSymbols_fa_IR', 'goog.labs.i18n.ListFormatSymbols_ff', 'goog.labs.i18n.ListFormatSymbols_ff_SN', 'goog.labs.i18n.ListFormatSymbols_fi_FI', 'goog.labs.i18n.ListFormatSymbols_fil_PH', 'goog.labs.i18n.ListFormatSymbols_fo', 'goog.labs.i18n.ListFormatSymbols_fo_FO', 'goog.labs.i18n.ListFormatSymbols_fr_BE', 'goog.labs.i18n.ListFormatSymbols_fr_BF', 'goog.labs.i18n.ListFormatSymbols_fr_BI', 'goog.labs.i18n.ListFormatSymbols_fr_BJ', 'goog.labs.i18n.ListFormatSymbols_fr_BL', 'goog.labs.i18n.ListFormatSymbols_fr_CD', 'goog.labs.i18n.ListFormatSymbols_fr_CF', 'goog.labs.i18n.ListFormatSymbols_fr_CG', 'goog.labs.i18n.ListFormatSymbols_fr_CH', 'goog.labs.i18n.ListFormatSymbols_fr_CI', 'goog.labs.i18n.ListFormatSymbols_fr_CM', 'goog.labs.i18n.ListFormatSymbols_fr_DJ', 'goog.labs.i18n.ListFormatSymbols_fr_DZ', 'goog.labs.i18n.ListFormatSymbols_fr_FR', 'goog.labs.i18n.ListFormatSymbols_fr_GA', 'goog.labs.i18n.ListFormatSymbols_fr_GF', 'goog.labs.i18n.ListFormatSymbols_fr_GN', 'goog.labs.i18n.ListFormatSymbols_fr_GP', 'goog.labs.i18n.ListFormatSymbols_fr_GQ', 'goog.labs.i18n.ListFormatSymbols_fr_HT', 'goog.labs.i18n.ListFormatSymbols_fr_KM', 'goog.labs.i18n.ListFormatSymbols_fr_LU', 'goog.labs.i18n.ListFormatSymbols_fr_MA', 'goog.labs.i18n.ListFormatSymbols_fr_MC', 'goog.labs.i18n.ListFormatSymbols_fr_MF', 'goog.labs.i18n.ListFormatSymbols_fr_MG', 'goog.labs.i18n.ListFormatSymbols_fr_ML', 'goog.labs.i18n.ListFormatSymbols_fr_MQ', 'goog.labs.i18n.ListFormatSymbols_fr_MR', 'goog.labs.i18n.ListFormatSymbols_fr_MU', 'goog.labs.i18n.ListFormatSymbols_fr_NC', 'goog.labs.i18n.ListFormatSymbols_fr_NE', 'goog.labs.i18n.ListFormatSymbols_fr_PF', 'goog.labs.i18n.ListFormatSymbols_fr_PM', 'goog.labs.i18n.ListFormatSymbols_fr_RE', 'goog.labs.i18n.ListFormatSymbols_fr_RW', 'goog.labs.i18n.ListFormatSymbols_fr_SC', 'goog.labs.i18n.ListFormatSymbols_fr_SN', 'goog.labs.i18n.ListFormatSymbols_fr_SY', 'goog.labs.i18n.ListFormatSymbols_fr_TD', 'goog.labs.i18n.ListFormatSymbols_fr_TG', 'goog.labs.i18n.ListFormatSymbols_fr_TN', 'goog.labs.i18n.ListFormatSymbols_fr_VU', 'goog.labs.i18n.ListFormatSymbols_fr_WF', 'goog.labs.i18n.ListFormatSymbols_fr_YT', 'goog.labs.i18n.ListFormatSymbols_ga', 'goog.labs.i18n.ListFormatSymbols_ga_IE', 'goog.labs.i18n.ListFormatSymbols_gl_ES', 'goog.labs.i18n.ListFormatSymbols_gsw_CH', 'goog.labs.i18n.ListFormatSymbols_gsw_LI', 'goog.labs.i18n.ListFormatSymbols_gu_IN', 'goog.labs.i18n.ListFormatSymbols_guz', 'goog.labs.i18n.ListFormatSymbols_guz_KE', 'goog.labs.i18n.ListFormatSymbols_gv', 'goog.labs.i18n.ListFormatSymbols_gv_IM', 'goog.labs.i18n.ListFormatSymbols_ha', 'goog.labs.i18n.ListFormatSymbols_ha_Latn', 'goog.labs.i18n.ListFormatSymbols_ha_Latn_GH', 'goog.labs.i18n.ListFormatSymbols_ha_Latn_NE', 'goog.labs.i18n.ListFormatSymbols_ha_Latn_NG', 'goog.labs.i18n.ListFormatSymbols_haw_US', 'goog.labs.i18n.ListFormatSymbols_he_IL', 'goog.labs.i18n.ListFormatSymbols_hi_IN', 'goog.labs.i18n.ListFormatSymbols_hr_BA', 'goog.labs.i18n.ListFormatSymbols_hr_HR', 'goog.labs.i18n.ListFormatSymbols_hu_HU', 'goog.labs.i18n.ListFormatSymbols_hy_AM', 'goog.labs.i18n.ListFormatSymbols_id_ID', 'goog.labs.i18n.ListFormatSymbols_ig', 'goog.labs.i18n.ListFormatSymbols_ig_NG', 'goog.labs.i18n.ListFormatSymbols_ii', 'goog.labs.i18n.ListFormatSymbols_ii_CN', 'goog.labs.i18n.ListFormatSymbols_is_IS', 'goog.labs.i18n.ListFormatSymbols_it_CH', 'goog.labs.i18n.ListFormatSymbols_it_IT', 'goog.labs.i18n.ListFormatSymbols_it_SM', 'goog.labs.i18n.ListFormatSymbols_ja_JP', 'goog.labs.i18n.ListFormatSymbols_jgo', 'goog.labs.i18n.ListFormatSymbols_jgo_CM', 'goog.labs.i18n.ListFormatSymbols_jmc', 'goog.labs.i18n.ListFormatSymbols_jmc_TZ', 'goog.labs.i18n.ListFormatSymbols_ka_GE', 'goog.labs.i18n.ListFormatSymbols_kab', 'goog.labs.i18n.ListFormatSymbols_kab_DZ', 'goog.labs.i18n.ListFormatSymbols_kam', 'goog.labs.i18n.ListFormatSymbols_kam_KE', 'goog.labs.i18n.ListFormatSymbols_kde', 'goog.labs.i18n.ListFormatSymbols_kde_TZ', 'goog.labs.i18n.ListFormatSymbols_kea', 'goog.labs.i18n.ListFormatSymbols_kea_CV', 'goog.labs.i18n.ListFormatSymbols_khq', 'goog.labs.i18n.ListFormatSymbols_khq_ML', 'goog.labs.i18n.ListFormatSymbols_ki', 'goog.labs.i18n.ListFormatSymbols_ki_KE', 'goog.labs.i18n.ListFormatSymbols_kk_Cyrl', 'goog.labs.i18n.ListFormatSymbols_kk_Cyrl_KZ', 'goog.labs.i18n.ListFormatSymbols_kkj', 'goog.labs.i18n.ListFormatSymbols_kkj_CM', 'goog.labs.i18n.ListFormatSymbols_kl', 'goog.labs.i18n.ListFormatSymbols_kl_GL', 'goog.labs.i18n.ListFormatSymbols_kln', 'goog.labs.i18n.ListFormatSymbols_kln_KE', 'goog.labs.i18n.ListFormatSymbols_km_KH', 'goog.labs.i18n.ListFormatSymbols_kn_IN', 'goog.labs.i18n.ListFormatSymbols_ko_KP', 'goog.labs.i18n.ListFormatSymbols_ko_KR', 'goog.labs.i18n.ListFormatSymbols_kok', 'goog.labs.i18n.ListFormatSymbols_kok_IN', 'goog.labs.i18n.ListFormatSymbols_ks', 'goog.labs.i18n.ListFormatSymbols_ks_Arab', 'goog.labs.i18n.ListFormatSymbols_ks_Arab_IN', 'goog.labs.i18n.ListFormatSymbols_ksb', 'goog.labs.i18n.ListFormatSymbols_ksb_TZ', 'goog.labs.i18n.ListFormatSymbols_ksf', 'goog.labs.i18n.ListFormatSymbols_ksf_CM', 'goog.labs.i18n.ListFormatSymbols_kw', 'goog.labs.i18n.ListFormatSymbols_kw_GB', 'goog.labs.i18n.ListFormatSymbols_ky_Cyrl', 'goog.labs.i18n.ListFormatSymbols_ky_Cyrl_KG', 'goog.labs.i18n.ListFormatSymbols_lag', 'goog.labs.i18n.ListFormatSymbols_lag_TZ', 'goog.labs.i18n.ListFormatSymbols_lg', 'goog.labs.i18n.ListFormatSymbols_lg_UG', 'goog.labs.i18n.ListFormatSymbols_lkt', 'goog.labs.i18n.ListFormatSymbols_lkt_US', 'goog.labs.i18n.ListFormatSymbols_ln_AO', 'goog.labs.i18n.ListFormatSymbols_ln_CD', 'goog.labs.i18n.ListFormatSymbols_ln_CF', 'goog.labs.i18n.ListFormatSymbols_ln_CG', 'goog.labs.i18n.ListFormatSymbols_lo_LA', 'goog.labs.i18n.ListFormatSymbols_lt_LT', 'goog.labs.i18n.ListFormatSymbols_lu', 'goog.labs.i18n.ListFormatSymbols_lu_CD', 'goog.labs.i18n.ListFormatSymbols_luo', 'goog.labs.i18n.ListFormatSymbols_luo_KE', 'goog.labs.i18n.ListFormatSymbols_luy', 'goog.labs.i18n.ListFormatSymbols_luy_KE', 'goog.labs.i18n.ListFormatSymbols_lv_LV', 'goog.labs.i18n.ListFormatSymbols_mas', 'goog.labs.i18n.ListFormatSymbols_mas_KE', 'goog.labs.i18n.ListFormatSymbols_mas_TZ', 'goog.labs.i18n.ListFormatSymbols_mer', 'goog.labs.i18n.ListFormatSymbols_mer_KE', 'goog.labs.i18n.ListFormatSymbols_mfe', 'goog.labs.i18n.ListFormatSymbols_mfe_MU', 'goog.labs.i18n.ListFormatSymbols_mg', 'goog.labs.i18n.ListFormatSymbols_mg_MG', 'goog.labs.i18n.ListFormatSymbols_mgh', 'goog.labs.i18n.ListFormatSymbols_mgh_MZ', 'goog.labs.i18n.ListFormatSymbols_mgo', 'goog.labs.i18n.ListFormatSymbols_mgo_CM', 'goog.labs.i18n.ListFormatSymbols_mk_MK', 'goog.labs.i18n.ListFormatSymbols_ml_IN', 'goog.labs.i18n.ListFormatSymbols_mn_Cyrl', 'goog.labs.i18n.ListFormatSymbols_mn_Cyrl_MN', 'goog.labs.i18n.ListFormatSymbols_mr_IN', 'goog.labs.i18n.ListFormatSymbols_ms_Latn', 'goog.labs.i18n.ListFormatSymbols_ms_Latn_BN', 'goog.labs.i18n.ListFormatSymbols_ms_Latn_MY', 'goog.labs.i18n.ListFormatSymbols_ms_Latn_SG', 'goog.labs.i18n.ListFormatSymbols_mt_MT', 'goog.labs.i18n.ListFormatSymbols_mua', 'goog.labs.i18n.ListFormatSymbols_mua_CM', 'goog.labs.i18n.ListFormatSymbols_my_MM', 'goog.labs.i18n.ListFormatSymbols_naq', 'goog.labs.i18n.ListFormatSymbols_naq_NA', 'goog.labs.i18n.ListFormatSymbols_nb_NO', 'goog.labs.i18n.ListFormatSymbols_nb_SJ', 'goog.labs.i18n.ListFormatSymbols_nd', 'goog.labs.i18n.ListFormatSymbols_nd_ZW', 'goog.labs.i18n.ListFormatSymbols_ne_IN', 'goog.labs.i18n.ListFormatSymbols_ne_NP', 'goog.labs.i18n.ListFormatSymbols_nl_AW', 'goog.labs.i18n.ListFormatSymbols_nl_BE', 'goog.labs.i18n.ListFormatSymbols_nl_BQ', 'goog.labs.i18n.ListFormatSymbols_nl_CW', 'goog.labs.i18n.ListFormatSymbols_nl_NL', 'goog.labs.i18n.ListFormatSymbols_nl_SR', 'goog.labs.i18n.ListFormatSymbols_nl_SX', 'goog.labs.i18n.ListFormatSymbols_nmg', 'goog.labs.i18n.ListFormatSymbols_nmg_CM', 'goog.labs.i18n.ListFormatSymbols_nn', 'goog.labs.i18n.ListFormatSymbols_nn_NO', 'goog.labs.i18n.ListFormatSymbols_nnh', 'goog.labs.i18n.ListFormatSymbols_nnh_CM', 'goog.labs.i18n.ListFormatSymbols_nus', 'goog.labs.i18n.ListFormatSymbols_nus_SD', 'goog.labs.i18n.ListFormatSymbols_nyn', 'goog.labs.i18n.ListFormatSymbols_nyn_UG', 'goog.labs.i18n.ListFormatSymbols_om', 'goog.labs.i18n.ListFormatSymbols_om_ET', 'goog.labs.i18n.ListFormatSymbols_om_KE', 'goog.labs.i18n.ListFormatSymbols_or_IN', 'goog.labs.i18n.ListFormatSymbols_pa_Arab', 'goog.labs.i18n.ListFormatSymbols_pa_Arab_PK', 'goog.labs.i18n.ListFormatSymbols_pa_Guru', 'goog.labs.i18n.ListFormatSymbols_pa_Guru_IN', 'goog.labs.i18n.ListFormatSymbols_pl_PL', 'goog.labs.i18n.ListFormatSymbols_ps', 'goog.labs.i18n.ListFormatSymbols_ps_AF', 'goog.labs.i18n.ListFormatSymbols_pt_AO', 'goog.labs.i18n.ListFormatSymbols_pt_CV', 'goog.labs.i18n.ListFormatSymbols_pt_GW', 'goog.labs.i18n.ListFormatSymbols_pt_MO', 'goog.labs.i18n.ListFormatSymbols_pt_MZ', 'goog.labs.i18n.ListFormatSymbols_pt_ST', 'goog.labs.i18n.ListFormatSymbols_pt_TL', 'goog.labs.i18n.ListFormatSymbols_rm', 'goog.labs.i18n.ListFormatSymbols_rm_CH', 'goog.labs.i18n.ListFormatSymbols_rn', 'goog.labs.i18n.ListFormatSymbols_rn_BI', 'goog.labs.i18n.ListFormatSymbols_ro_MD', 'goog.labs.i18n.ListFormatSymbols_ro_RO', 'goog.labs.i18n.ListFormatSymbols_rof', 'goog.labs.i18n.ListFormatSymbols_rof_TZ', 'goog.labs.i18n.ListFormatSymbols_ru_BY', 'goog.labs.i18n.ListFormatSymbols_ru_KG', 'goog.labs.i18n.ListFormatSymbols_ru_KZ', 'goog.labs.i18n.ListFormatSymbols_ru_MD', 'goog.labs.i18n.ListFormatSymbols_ru_RU', 'goog.labs.i18n.ListFormatSymbols_ru_UA', 'goog.labs.i18n.ListFormatSymbols_rw', 'goog.labs.i18n.ListFormatSymbols_rw_RW', 'goog.labs.i18n.ListFormatSymbols_rwk', 'goog.labs.i18n.ListFormatSymbols_rwk_TZ', 'goog.labs.i18n.ListFormatSymbols_saq', 'goog.labs.i18n.ListFormatSymbols_saq_KE', 'goog.labs.i18n.ListFormatSymbols_sbp', 'goog.labs.i18n.ListFormatSymbols_sbp_TZ', 'goog.labs.i18n.ListFormatSymbols_seh', 'goog.labs.i18n.ListFormatSymbols_seh_MZ', 'goog.labs.i18n.ListFormatSymbols_ses', 'goog.labs.i18n.ListFormatSymbols_ses_ML', 'goog.labs.i18n.ListFormatSymbols_sg', 'goog.labs.i18n.ListFormatSymbols_sg_CF', 'goog.labs.i18n.ListFormatSymbols_shi', 'goog.labs.i18n.ListFormatSymbols_shi_Latn', 'goog.labs.i18n.ListFormatSymbols_shi_Latn_MA', 'goog.labs.i18n.ListFormatSymbols_shi_Tfng', 'goog.labs.i18n.ListFormatSymbols_shi_Tfng_MA', 'goog.labs.i18n.ListFormatSymbols_si_LK', 'goog.labs.i18n.ListFormatSymbols_sk_SK', 'goog.labs.i18n.ListFormatSymbols_sl_SI', 'goog.labs.i18n.ListFormatSymbols_sn', 'goog.labs.i18n.ListFormatSymbols_sn_ZW', 'goog.labs.i18n.ListFormatSymbols_so', 'goog.labs.i18n.ListFormatSymbols_so_DJ', 'goog.labs.i18n.ListFormatSymbols_so_ET', 'goog.labs.i18n.ListFormatSymbols_so_KE', 'goog.labs.i18n.ListFormatSymbols_so_SO', 'goog.labs.i18n.ListFormatSymbols_sq_AL', 'goog.labs.i18n.ListFormatSymbols_sq_MK', 'goog.labs.i18n.ListFormatSymbols_sq_XK', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_BA', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_ME', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_RS', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_XK', 'goog.labs.i18n.ListFormatSymbols_sr_Latn', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_BA', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_ME', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_RS', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_XK', 'goog.labs.i18n.ListFormatSymbols_sv_AX', 'goog.labs.i18n.ListFormatSymbols_sv_FI', 'goog.labs.i18n.ListFormatSymbols_sv_SE', 'goog.labs.i18n.ListFormatSymbols_sw_KE', 'goog.labs.i18n.ListFormatSymbols_sw_TZ', 'goog.labs.i18n.ListFormatSymbols_sw_UG', 'goog.labs.i18n.ListFormatSymbols_swc', 'goog.labs.i18n.ListFormatSymbols_swc_CD', 'goog.labs.i18n.ListFormatSymbols_ta_IN', 'goog.labs.i18n.ListFormatSymbols_ta_LK', 'goog.labs.i18n.ListFormatSymbols_ta_MY', 'goog.labs.i18n.ListFormatSymbols_ta_SG', 'goog.labs.i18n.ListFormatSymbols_te_IN', 'goog.labs.i18n.ListFormatSymbols_teo', 'goog.labs.i18n.ListFormatSymbols_teo_KE', 'goog.labs.i18n.ListFormatSymbols_teo_UG', 'goog.labs.i18n.ListFormatSymbols_th_TH', 'goog.labs.i18n.ListFormatSymbols_ti', 'goog.labs.i18n.ListFormatSymbols_ti_ER', 'goog.labs.i18n.ListFormatSymbols_ti_ET', 'goog.labs.i18n.ListFormatSymbols_to', 'goog.labs.i18n.ListFormatSymbols_to_TO', 'goog.labs.i18n.ListFormatSymbols_tr_CY', 'goog.labs.i18n.ListFormatSymbols_tr_TR', 'goog.labs.i18n.ListFormatSymbols_twq', 'goog.labs.i18n.ListFormatSymbols_twq_NE', 'goog.labs.i18n.ListFormatSymbols_tzm', 'goog.labs.i18n.ListFormatSymbols_tzm_Latn', 'goog.labs.i18n.ListFormatSymbols_tzm_Latn_MA', 'goog.labs.i18n.ListFormatSymbols_ug', 'goog.labs.i18n.ListFormatSymbols_ug_Arab', 'goog.labs.i18n.ListFormatSymbols_ug_Arab_CN', 'goog.labs.i18n.ListFormatSymbols_uk_UA', 'goog.labs.i18n.ListFormatSymbols_ur_IN', 'goog.labs.i18n.ListFormatSymbols_ur_PK', 'goog.labs.i18n.ListFormatSymbols_uz_Arab', 'goog.labs.i18n.ListFormatSymbols_uz_Arab_AF', 'goog.labs.i18n.ListFormatSymbols_uz_Cyrl', 'goog.labs.i18n.ListFormatSymbols_uz_Cyrl_UZ', 'goog.labs.i18n.ListFormatSymbols_uz_Latn', 'goog.labs.i18n.ListFormatSymbols_uz_Latn_UZ', 'goog.labs.i18n.ListFormatSymbols_vai', 'goog.labs.i18n.ListFormatSymbols_vai_Latn', 'goog.labs.i18n.ListFormatSymbols_vai_Latn_LR', 'goog.labs.i18n.ListFormatSymbols_vai_Vaii', 'goog.labs.i18n.ListFormatSymbols_vai_Vaii_LR', 'goog.labs.i18n.ListFormatSymbols_vi_VN', 'goog.labs.i18n.ListFormatSymbols_vun', 'goog.labs.i18n.ListFormatSymbols_vun_TZ', 'goog.labs.i18n.ListFormatSymbols_xog', 'goog.labs.i18n.ListFormatSymbols_xog_UG', 'goog.labs.i18n.ListFormatSymbols_yav', 'goog.labs.i18n.ListFormatSymbols_yav_CM', 'goog.labs.i18n.ListFormatSymbols_yo', 'goog.labs.i18n.ListFormatSymbols_yo_BJ', 'goog.labs.i18n.ListFormatSymbols_yo_NG', 'goog.labs.i18n.ListFormatSymbols_zgh', 'goog.labs.i18n.ListFormatSymbols_zgh_MA', 'goog.labs.i18n.ListFormatSymbols_zh_Hans', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_CN', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_HK', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_MO', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_SG', 'goog.labs.i18n.ListFormatSymbols_zh_Hant', 'goog.labs.i18n.ListFormatSymbols_zh_Hant_HK', 'goog.labs.i18n.ListFormatSymbols_zh_Hant_MO', 'goog.labs.i18n.ListFormatSymbols_zh_Hant_TW', 'goog.labs.i18n.ListFormatSymbols_zu_ZA'], ['goog.labs.i18n.ListFormatSymbols']);
-goog.addDependency('labs/mock/mock.js', ['goog.labs.mock', 'goog.labs.mock.VerificationError'], ['goog.array', 'goog.asserts', 'goog.debug', 'goog.debug.Error', 'goog.functions', 'goog.object']);
-goog.addDependency('labs/mock/mock_test.js', ['goog.labs.mockTest'], ['goog.array', 'goog.labs.mock', 'goog.labs.mock.VerificationError', 'goog.labs.testing.AnythingMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.string', 'goog.testing.jsunit']);
-goog.addDependency('labs/net/image.js', ['goog.labs.net.image'], ['goog.Promise', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.net.EventType', 'goog.userAgent']);
-goog.addDependency('labs/net/image_test.js', ['goog.labs.net.imageTest'], ['goog.events', 'goog.labs.net.image', 'goog.string', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('labs/net/webchannel.js', ['goog.net.WebChannel'], ['goog.events', 'goog.events.Event']);
-goog.addDependency('labs/net/webchannel/basetestchannel.js', ['goog.labs.net.webChannel.BaseTestChannel'], ['goog.labs.net.webChannel.Channel', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Stat']);
-goog.addDependency('labs/net/webchannel/channel.js', ['goog.labs.net.webChannel.Channel'], []);
-goog.addDependency('labs/net/webchannel/channelrequest.js', ['goog.labs.net.webChannel.ChannelRequest'], ['goog.Timer', 'goog.async.Throttle', 'goog.events.EventHandler', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.ServerReachability', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XmlHttp', 'goog.object', 'goog.uri.utils.StandardQueryParam', 'goog.userAgent']);
-goog.addDependency('labs/net/webchannel/channelrequest_test.js', ['goog.labs.net.webChannel.channelRequestTest'], ['goog.Uri', 'goog.functions', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.ServerReachability', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction']);
-goog.addDependency('labs/net/webchannel/connectionstate.js', ['goog.labs.net.webChannel.ConnectionState'], []);
-goog.addDependency('labs/net/webchannel/forwardchannelrequestpool.js', ['goog.labs.net.webChannel.ForwardChannelRequestPool'], ['goog.array', 'goog.string', 'goog.structs.Set']);
-goog.addDependency('labs/net/webchannel/forwardchannelrequestpool_test.js', ['goog.labs.net.webChannel.forwardChannelRequestPoolTest'], ['goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.ForwardChannelRequestPool', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit']);
-goog.addDependency('labs/net/webchannel/netutils.js', ['goog.labs.net.webChannel.netUtils'], ['goog.Uri', 'goog.labs.net.webChannel.WebChannelDebug']);
-goog.addDependency('labs/net/webchannel/requeststats.js', ['goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Event', 'goog.labs.net.webChannel.requestStats.ServerReachability', 'goog.labs.net.webChannel.requestStats.ServerReachabilityEvent', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.labs.net.webChannel.requestStats.StatEvent', 'goog.labs.net.webChannel.requestStats.TimingEvent'], ['goog.events.Event', 'goog.events.EventTarget']);
-goog.addDependency('labs/net/webchannel/webchannelbase.js', ['goog.labs.net.webChannel.WebChannelBase'], ['goog.Uri', 'goog.array', 'goog.asserts', 'goog.debug.TextFormatter', 'goog.json', 'goog.labs.net.webChannel.BaseTestChannel', 'goog.labs.net.webChannel.Channel', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.ConnectionState', 'goog.labs.net.webChannel.ForwardChannelRequestPool', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.Wire', 'goog.labs.net.webChannel.WireV8', 'goog.labs.net.webChannel.netUtils', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.log', 'goog.net.XhrIo', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.CircularBuffer']);
-goog.addDependency('labs/net/webchannel/webchannelbase_test.js', ['goog.labs.net.webChannel.webChannelBaseTest'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.functions', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.ForwardChannelRequestPool', 'goog.labs.net.webChannel.WebChannelBase', 'goog.labs.net.webChannel.WebChannelBaseTransport', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.Wire', 'goog.labs.net.webChannel.netUtils', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.structs.Map', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit']);
-goog.addDependency('labs/net/webchannel/webchannelbasetransport.js', ['goog.labs.net.webChannel.WebChannelBaseTransport'], ['goog.asserts', 'goog.events.EventTarget', 'goog.labs.net.webChannel.WebChannelBase', 'goog.log', 'goog.net.WebChannel', 'goog.net.WebChannelTransport', 'goog.string.path']);
-goog.addDependency('labs/net/webchannel/webchannelbasetransport_test.js', ['goog.labs.net.webChannel.webChannelBaseTransportTest'], ['goog.events', 'goog.labs.net.webChannel.WebChannelBaseTransport', 'goog.net.WebChannel', 'goog.testing.jsunit']);
-goog.addDependency('labs/net/webchannel/webchanneldebug.js', ['goog.labs.net.webChannel.WebChannelDebug'], ['goog.json', 'goog.log']);
-goog.addDependency('labs/net/webchannel/wire.js', ['goog.labs.net.webChannel.Wire'], []);
-goog.addDependency('labs/net/webchannel/wirev8.js', ['goog.labs.net.webChannel.WireV8'], ['goog.asserts', 'goog.json', 'goog.json.EvalJsonProcessor', 'goog.structs']);
-goog.addDependency('labs/net/webchannel/wirev8_test.js', ['goog.labs.net.webChannel.WireV8Test'], ['goog.labs.net.webChannel.WireV8', 'goog.testing.jsunit']);
-goog.addDependency('labs/net/webchanneltransport.js', ['goog.net.WebChannelTransport'], []);
-goog.addDependency('labs/net/webchanneltransportfactory.js', ['goog.net.createWebChannelTransport'], ['goog.functions', 'goog.labs.net.webChannel.WebChannelBaseTransport']);
-goog.addDependency('labs/net/xhr.js', ['goog.labs.net.xhr', 'goog.labs.net.xhr.Error', 'goog.labs.net.xhr.HttpError', 'goog.labs.net.xhr.Options', 'goog.labs.net.xhr.PostData', 'goog.labs.net.xhr.TimeoutError'], ['goog.Promise', 'goog.debug.Error', 'goog.json', 'goog.net.HttpStatus', 'goog.net.XmlHttp', 'goog.string', 'goog.uri.utils']);
-goog.addDependency('labs/net/xhr_test.js', ['goog.labs.net.xhrTest'], ['goog.Promise', 'goog.labs.net.xhr', 'goog.net.XmlHttp', 'goog.string', 'goog.testing.AsyncTestCase', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('labs/object/object.js', ['goog.labs.object'], []);
-goog.addDependency('labs/object/object_test.js', ['goog.labs.objectTest'], ['goog.labs.object', 'goog.testing.jsunit']);
-goog.addDependency('labs/promise/promise.js', ['goog.labs.Promise', 'goog.labs.Resolver'], ['goog.Promise', 'goog.Thenable', 'goog.promise.Resolver']);
-goog.addDependency('labs/storage/boundedcollectablestorage.js', ['goog.labs.storage.BoundedCollectableStorage'], ['goog.array', 'goog.asserts', 'goog.iter', 'goog.storage.CollectableStorage', 'goog.storage.ErrorCode', 'goog.storage.ExpiringStorage']);
-goog.addDependency('labs/storage/boundedcollectablestorage_test.js', ['goog.labs.storage.BoundedCollectableStorageTest'], ['goog.labs.storage.BoundedCollectableStorage', 'goog.storage.collectableStorageTester', 'goog.storage.storage_test', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']);
-goog.addDependency('labs/structs/map.js', ['goog.labs.structs.Map'], ['goog.array', 'goog.asserts', 'goog.labs.object', 'goog.object']);
-goog.addDependency('labs/structs/map_perf.js', ['goog.labs.structs.mapPerf'], ['goog.dom', 'goog.labs.structs.Map', 'goog.structs.Map', 'goog.testing.PerformanceTable', 'goog.testing.jsunit']);
-goog.addDependency('labs/structs/map_test.js', ['goog.labs.structs.MapTest'], ['goog.labs.structs.Map', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('labs/structs/multimap.js', ['goog.labs.structs.Multimap'], ['goog.array', 'goog.labs.object', 'goog.labs.structs.Map']);
-goog.addDependency('labs/structs/multimap_test.js', ['goog.labs.structs.MultimapTest'], ['goog.labs.structs.Map', 'goog.labs.structs.Multimap', 'goog.testing.jsunit']);
-goog.addDependency('labs/style/pixeldensitymonitor.js', ['goog.labs.style.PixelDensityMonitor', 'goog.labs.style.PixelDensityMonitor.Density', 'goog.labs.style.PixelDensityMonitor.EventType'], ['goog.events', 'goog.events.EventTarget']);
-goog.addDependency('labs/style/pixeldensitymonitor_test.js', ['goog.labs.style.PixelDensityMonitorTest'], ['goog.array', 'goog.dom.DomHelper', 'goog.events', 'goog.labs.style.PixelDensityMonitor', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('labs/testing/assertthat.js', ['goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat'], ['goog.asserts', 'goog.debug.Error', 'goog.labs.testing.Matcher']);
-goog.addDependency('labs/testing/assertthat_test.js', ['goog.labs.testing.assertThatTest'], ['goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('labs/testing/decoratormatcher.js', ['goog.labs.testing.AnythingMatcher'], ['goog.labs.testing.Matcher']);
-goog.addDependency('labs/testing/decoratormatcher_test.js', ['goog.labs.testing.decoratorMatcherTest'], ['goog.labs.testing.AnythingMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']);
-goog.addDependency('labs/testing/dictionarymatcher.js', ['goog.labs.testing.HasEntriesMatcher', 'goog.labs.testing.HasEntryMatcher', 'goog.labs.testing.HasKeyMatcher', 'goog.labs.testing.HasValueMatcher'], ['goog.array', 'goog.asserts', 'goog.labs.testing.Matcher', 'goog.string']);
-goog.addDependency('labs/testing/dictionarymatcher_test.js', ['goog.labs.testing.dictionaryMatcherTest'], ['goog.labs.testing.HasEntryMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']);
-goog.addDependency('labs/testing/environment.js', ['goog.labs.testing.Environment'], ['goog.array', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.TestCase', 'goog.testing.jsunit']);
-goog.addDependency('labs/testing/environment_test.js', ['goog.labs.testing.environmentTest'], ['goog.labs.testing.Environment', 'goog.testing.MockControl', 'goog.testing.TestCase', 'goog.testing.jsunit']);
-goog.addDependency('labs/testing/environment_usage_test.js', ['goog.labs.testing.environmentUsageTest'], ['goog.labs.testing.Environment']);
-goog.addDependency('labs/testing/logicmatcher.js', ['goog.labs.testing.AllOfMatcher', 'goog.labs.testing.AnyOfMatcher', 'goog.labs.testing.IsNotMatcher'], ['goog.array', 'goog.labs.testing.Matcher']);
-goog.addDependency('labs/testing/logicmatcher_test.js', ['goog.labs.testing.logicMatcherTest'], ['goog.labs.testing.AllOfMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']);
-goog.addDependency('labs/testing/matcher.js', ['goog.labs.testing.Matcher'], []);
-goog.addDependency('labs/testing/numbermatcher.js', ['goog.labs.testing.CloseToMatcher', 'goog.labs.testing.EqualToMatcher', 'goog.labs.testing.GreaterThanEqualToMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.labs.testing.LessThanEqualToMatcher', 'goog.labs.testing.LessThanMatcher'], ['goog.asserts', 'goog.labs.testing.Matcher']);
-goog.addDependency('labs/testing/numbermatcher_test.js', ['goog.labs.testing.numberMatcherTest'], ['goog.labs.testing.LessThanMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']);
-goog.addDependency('labs/testing/objectmatcher.js', ['goog.labs.testing.HasPropertyMatcher', 'goog.labs.testing.InstanceOfMatcher', 'goog.labs.testing.IsNullMatcher', 'goog.labs.testing.IsNullOrUndefinedMatcher', 'goog.labs.testing.IsUndefinedMatcher', 'goog.labs.testing.ObjectEqualsMatcher'], ['goog.labs.testing.Matcher', 'goog.string']);
-goog.addDependency('labs/testing/objectmatcher_test.js', ['goog.labs.testing.objectMatcherTest'], ['goog.labs.testing.MatcherError', 'goog.labs.testing.ObjectEqualsMatcher', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']);
-goog.addDependency('labs/testing/stringmatcher.js', ['goog.labs.testing.ContainsStringMatcher', 'goog.labs.testing.EndsWithMatcher', 'goog.labs.testing.EqualToIgnoringCaseMatcher', 'goog.labs.testing.EqualToIgnoringWhitespaceMatcher', 'goog.labs.testing.EqualsMatcher', 'goog.labs.testing.RegexMatcher', 'goog.labs.testing.StartsWithMatcher', 'goog.labs.testing.StringContainsInOrderMatcher'], ['goog.asserts', 'goog.labs.testing.Matcher', 'goog.string']);
-goog.addDependency('labs/testing/stringmatcher_test.js', ['goog.labs.testing.stringMatcherTest'], ['goog.labs.testing.MatcherError', 'goog.labs.testing.StringContainsInOrderMatcher', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']);
-goog.addDependency('labs/useragent/browser.js', ['goog.labs.userAgent.browser'], ['goog.array', 'goog.asserts', 'goog.labs.userAgent.util', 'goog.string']);
-goog.addDependency('labs/useragent/browser_test.js', ['goog.labs.userAgent.browserTest'], ['goog.labs.userAgent.browser', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.jsunit']);
-goog.addDependency('labs/useragent/device.js', ['goog.labs.userAgent.device'], ['goog.labs.userAgent.util']);
-goog.addDependency('labs/useragent/device_test.js', ['goog.labs.userAgent.deviceTest'], ['goog.labs.userAgent.device', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.jsunit']);
-goog.addDependency('labs/useragent/engine.js', ['goog.labs.userAgent.engine'], ['goog.array', 'goog.labs.userAgent.util', 'goog.string']);
-goog.addDependency('labs/useragent/engine_test.js', ['goog.labs.userAgent.engineTest'], ['goog.labs.userAgent.engine', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.jsunit']);
-goog.addDependency('labs/useragent/platform.js', ['goog.labs.userAgent.platform'], ['goog.labs.userAgent.util', 'goog.string']);
-goog.addDependency('labs/useragent/platform_test.js', ['goog.labs.userAgent.platformTest'], ['goog.labs.userAgent.platform', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.jsunit']);
-goog.addDependency('labs/useragent/test_agents.js', ['goog.labs.userAgent.testAgents'], []);
-goog.addDependency('labs/useragent/util.js', ['goog.labs.userAgent.util'], ['goog.string']);
-goog.addDependency('labs/useragent/util_test.js', ['goog.labs.userAgent.utilTest'], ['goog.functions', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('locale/countries.js', ['goog.locale.countries'], []);
-goog.addDependency('locale/countrylanguagenames_test.js', ['goog.locale.countryLanguageNamesTest'], ['goog.locale', 'goog.testing.jsunit']);
-goog.addDependency('locale/defaultlocalenameconstants.js', ['goog.locale.defaultLocaleNameConstants'], []);
-goog.addDependency('locale/genericfontnames.js', ['goog.locale.genericFontNames'], []);
-goog.addDependency('locale/genericfontnames_test.js', ['goog.locale.genericFontNamesTest'], ['goog.locale.genericFontNames', 'goog.testing.jsunit']);
-goog.addDependency('locale/genericfontnamesdata.js', ['goog.locale.genericFontNamesData'], []);
-goog.addDependency('locale/locale.js', ['goog.locale'], ['goog.locale.nativeNameConstants']);
-goog.addDependency('locale/nativenameconstants.js', ['goog.locale.nativeNameConstants'], []);
-goog.addDependency('locale/scriptToLanguages.js', ['goog.locale.scriptToLanguages'], ['goog.locale']);
-goog.addDependency('locale/timezonedetection.js', ['goog.locale.timeZoneDetection'], ['goog.locale', 'goog.locale.TimeZoneFingerprint']);
-goog.addDependency('locale/timezonedetection_test.js', ['goog.locale.timeZoneDetectionTest'], ['goog.locale.timeZoneDetection', 'goog.testing.jsunit']);
-goog.addDependency('locale/timezonefingerprint.js', ['goog.locale.TimeZoneFingerprint'], []);
-goog.addDependency('locale/timezonelist.js', ['goog.locale.TimeZoneList'], ['goog.locale']);
-goog.addDependency('locale/timezonelist_test.js', ['goog.locale.TimeZoneListTest'], ['goog.locale', 'goog.locale.TimeZoneList', 'goog.testing.jsunit']);
-goog.addDependency('log/log.js', ['goog.log', 'goog.log.Level', 'goog.log.LogRecord', 'goog.log.Logger'], ['goog.debug', 'goog.debug.LogManager', 'goog.debug.LogRecord', 'goog.debug.Logger']);
-goog.addDependency('log/log_test.js', ['goog.logTest'], ['goog.debug.LogManager', 'goog.log', 'goog.log.Level', 'goog.testing.jsunit']);
-goog.addDependency('math/affinetransform.js', ['goog.math.AffineTransform'], ['goog.math']);
-goog.addDependency('math/affinetransform_test.js', ['goog.math.AffineTransformTest'], ['goog.array', 'goog.math', 'goog.math.AffineTransform', 'goog.testing.jsunit']);
-goog.addDependency('math/bezier.js', ['goog.math.Bezier'], ['goog.math', 'goog.math.Coordinate']);
-goog.addDependency('math/bezier_test.js', ['goog.math.BezierTest'], ['goog.math', 'goog.math.Bezier', 'goog.math.Coordinate', 'goog.testing.jsunit']);
-goog.addDependency('math/box.js', ['goog.math.Box'], ['goog.math.Coordinate']);
-goog.addDependency('math/box_test.js', ['goog.math.BoxTest'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.testing.jsunit']);
-goog.addDependency('math/coordinate.js', ['goog.math.Coordinate'], ['goog.math']);
-goog.addDependency('math/coordinate3.js', ['goog.math.Coordinate3'], []);
-goog.addDependency('math/coordinate3_test.js', ['goog.math.Coordinate3Test'], ['goog.math.Coordinate3', 'goog.testing.jsunit']);
-goog.addDependency('math/coordinate_test.js', ['goog.math.CoordinateTest'], ['goog.math.Coordinate', 'goog.testing.jsunit']);
-goog.addDependency('math/exponentialbackoff.js', ['goog.math.ExponentialBackoff'], ['goog.asserts']);
-goog.addDependency('math/exponentialbackoff_test.js', ['goog.math.ExponentialBackoffTest'], ['goog.math.ExponentialBackoff', 'goog.testing.jsunit']);
-goog.addDependency('math/integer.js', ['goog.math.Integer'], []);
-goog.addDependency('math/integer_test.js', ['goog.math.IntegerTest'], ['goog.math.Integer', 'goog.testing.jsunit']);
-goog.addDependency('math/interpolator/interpolator1.js', ['goog.math.interpolator.Interpolator1'], []);
-goog.addDependency('math/interpolator/linear1.js', ['goog.math.interpolator.Linear1'], ['goog.array', 'goog.math', 'goog.math.interpolator.Interpolator1']);
-goog.addDependency('math/interpolator/linear1_test.js', ['goog.math.interpolator.Linear1Test'], ['goog.math.interpolator.Linear1', 'goog.testing.jsunit']);
-goog.addDependency('math/interpolator/pchip1.js', ['goog.math.interpolator.Pchip1'], ['goog.math', 'goog.math.interpolator.Spline1']);
-goog.addDependency('math/interpolator/pchip1_test.js', ['goog.math.interpolator.Pchip1Test'], ['goog.math.interpolator.Pchip1', 'goog.testing.jsunit']);
-goog.addDependency('math/interpolator/spline1.js', ['goog.math.interpolator.Spline1'], ['goog.array', 'goog.math', 'goog.math.interpolator.Interpolator1', 'goog.math.tdma']);
-goog.addDependency('math/interpolator/spline1_test.js', ['goog.math.interpolator.Spline1Test'], ['goog.math.interpolator.Spline1', 'goog.testing.jsunit']);
-goog.addDependency('math/line.js', ['goog.math.Line'], ['goog.math', 'goog.math.Coordinate']);
-goog.addDependency('math/line_test.js', ['goog.math.LineTest'], ['goog.math.Coordinate', 'goog.math.Line', 'goog.testing.jsunit']);
-goog.addDependency('math/long.js', ['goog.math.Long'], []);
-goog.addDependency('math/long_test.js', ['goog.math.LongTest'], ['goog.math.Long', 'goog.testing.jsunit']);
-goog.addDependency('math/math.js', ['goog.math'], ['goog.array', 'goog.asserts']);
-goog.addDependency('math/math_test.js', ['goog.mathTest'], ['goog.math', 'goog.testing.jsunit']);
-goog.addDependency('math/matrix.js', ['goog.math.Matrix'], ['goog.array', 'goog.math', 'goog.math.Size', 'goog.string']);
-goog.addDependency('math/matrix_test.js', ['goog.math.MatrixTest'], ['goog.math.Matrix', 'goog.testing.jsunit']);
-goog.addDependency('math/path.js', ['goog.math.Path', 'goog.math.Path.Segment'], ['goog.array', 'goog.math']);
-goog.addDependency('math/path_test.js', ['goog.math.PathTest'], ['goog.array', 'goog.math.AffineTransform', 'goog.math.Path', 'goog.testing.jsunit']);
-goog.addDependency('math/paths.js', ['goog.math.paths'], ['goog.math.Coordinate', 'goog.math.Path']);
-goog.addDependency('math/paths_test.js', ['goog.math.pathsTest'], ['goog.math.Coordinate', 'goog.math.paths', 'goog.testing.jsunit']);
-goog.addDependency('math/range.js', ['goog.math.Range'], ['goog.asserts']);
-goog.addDependency('math/range_test.js', ['goog.math.RangeTest'], ['goog.math.Range', 'goog.testing.jsunit']);
-goog.addDependency('math/rangeset.js', ['goog.math.RangeSet'], ['goog.array', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.math.Range']);
-goog.addDependency('math/rangeset_test.js', ['goog.math.RangeSetTest'], ['goog.iter', 'goog.math.Range', 'goog.math.RangeSet', 'goog.testing.jsunit']);
-goog.addDependency('math/rect.js', ['goog.math.Rect'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.math.Size']);
-goog.addDependency('math/rect_test.js', ['goog.math.RectTest'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.testing.jsunit']);
-goog.addDependency('math/size.js', ['goog.math.Size'], []);
-goog.addDependency('math/size_test.js', ['goog.math.SizeTest'], ['goog.math.Size', 'goog.testing.jsunit']);
-goog.addDependency('math/tdma.js', ['goog.math.tdma'], []);
-goog.addDependency('math/tdma_test.js', ['goog.math.tdmaTest'], ['goog.math.tdma', 'goog.testing.jsunit']);
-goog.addDependency('math/vec2.js', ['goog.math.Vec2'], ['goog.math', 'goog.math.Coordinate']);
-goog.addDependency('math/vec2_test.js', ['goog.math.Vec2Test'], ['goog.math.Vec2', 'goog.testing.jsunit']);
-goog.addDependency('math/vec3.js', ['goog.math.Vec3'], ['goog.math', 'goog.math.Coordinate3']);
-goog.addDependency('math/vec3_test.js', ['goog.math.Vec3Test'], ['goog.math.Coordinate3', 'goog.math.Vec3', 'goog.testing.jsunit']);
-goog.addDependency('memoize/memoize.js', ['goog.memoize'], []);
-goog.addDependency('memoize/memoize_test.js', ['goog.memoizeTest'], ['goog.memoize', 'goog.testing.jsunit']);
-goog.addDependency('messaging/abstractchannel.js', ['goog.messaging.AbstractChannel'], ['goog.Disposable', 'goog.debug', 'goog.json', 'goog.log', 'goog.messaging.MessageChannel']);
-goog.addDependency('messaging/abstractchannel_test.js', ['goog.messaging.AbstractChannelTest'], ['goog.messaging.AbstractChannel', 'goog.testing.MockControl', 'goog.testing.async.MockControl', 'goog.testing.jsunit']);
-goog.addDependency('messaging/bufferedchannel.js', ['goog.messaging.BufferedChannel'], ['goog.Timer', 'goog.Uri', 'goog.debug.Error', 'goog.events', 'goog.log', 'goog.messaging.MessageChannel', 'goog.messaging.MultiChannel']);
-goog.addDependency('messaging/bufferedchannel_test.js', ['goog.messaging.BufferedChannelTest'], ['goog.debug.Console', 'goog.dom', 'goog.log', 'goog.log.Level', 'goog.messaging.BufferedChannel', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.async.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('messaging/deferredchannel.js', ['goog.messaging.DeferredChannel'], ['goog.Disposable', 'goog.async.Deferred', 'goog.messaging.MessageChannel']);
-goog.addDependency('messaging/deferredchannel_test.js', ['goog.messaging.DeferredChannelTest'], ['goog.async.Deferred', 'goog.messaging.DeferredChannel', 'goog.testing.MockControl', 'goog.testing.async.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('messaging/loggerclient.js', ['goog.messaging.LoggerClient'], ['goog.Disposable', 'goog.debug', 'goog.debug.LogManager', 'goog.debug.Logger']);
-goog.addDependency('messaging/loggerclient_test.js', ['goog.messaging.LoggerClientTest'], ['goog.debug', 'goog.debug.Logger', 'goog.messaging.LoggerClient', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('messaging/loggerserver.js', ['goog.messaging.LoggerServer'], ['goog.Disposable', 'goog.log']);
-goog.addDependency('messaging/loggerserver_test.js', ['goog.messaging.LoggerServerTest'], ['goog.debug.LogManager', 'goog.debug.Logger', 'goog.log', 'goog.log.Level', 'goog.messaging.LoggerServer', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('messaging/messagechannel.js', ['goog.messaging.MessageChannel'], []);
-goog.addDependency('messaging/messaging.js', ['goog.messaging'], ['goog.messaging.MessageChannel']);
-goog.addDependency('messaging/messaging_test.js', ['goog.testing.messaging.MockMessageChannelTest'], ['goog.messaging', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('messaging/multichannel.js', ['goog.messaging.MultiChannel', 'goog.messaging.MultiChannel.VirtualChannel'], ['goog.Disposable', 'goog.events.EventHandler', 'goog.log', 'goog.messaging.MessageChannel', 'goog.object']);
-goog.addDependency('messaging/multichannel_test.js', ['goog.messaging.MultiChannelTest'], ['goog.messaging.MultiChannel', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel', 'goog.testing.mockmatchers.IgnoreArgument']);
-goog.addDependency('messaging/portcaller.js', ['goog.messaging.PortCaller'], ['goog.Disposable', 'goog.async.Deferred', 'goog.messaging.DeferredChannel', 'goog.messaging.PortChannel', 'goog.messaging.PortNetwork', 'goog.object']);
-goog.addDependency('messaging/portcaller_test.js', ['goog.messaging.PortCallerTest'], ['goog.events.EventTarget', 'goog.messaging.PortCaller', 'goog.messaging.PortNetwork', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('messaging/portchannel.js', ['goog.messaging.PortChannel'], ['goog.Timer', 'goog.array', 'goog.async.Deferred', 'goog.debug', 'goog.dom', 'goog.dom.DomHelper', 'goog.events', 'goog.events.EventType', 'goog.json', 'goog.log', 'goog.messaging.AbstractChannel', 'goog.messaging.DeferredChannel', 'goog.object', 'goog.string']);
-goog.addDependency('messaging/portnetwork.js', ['goog.messaging.PortNetwork'], []);
-goog.addDependency('messaging/portoperator.js', ['goog.messaging.PortOperator'], ['goog.Disposable', 'goog.asserts', 'goog.log', 'goog.messaging.PortChannel', 'goog.messaging.PortNetwork', 'goog.object']);
-goog.addDependency('messaging/portoperator_test.js', ['goog.messaging.PortOperatorTest'], ['goog.messaging.PortNetwork', 'goog.messaging.PortOperator', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel', 'goog.testing.messaging.MockMessagePort']);
-goog.addDependency('messaging/respondingchannel.js', ['goog.messaging.RespondingChannel'], ['goog.Disposable', 'goog.log', 'goog.messaging.MessageChannel', 'goog.messaging.MultiChannel', 'goog.messaging.MultiChannel.VirtualChannel']);
-goog.addDependency('messaging/respondingchannel_test.js', ['goog.messaging.RespondingChannelTest'], ['goog.messaging.RespondingChannel', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('messaging/testdata/portchannel_worker.js', ['goog.messaging.testdata.portchannel_worker'], ['goog.messaging.PortChannel']);
-goog.addDependency('messaging/testdata/portnetwork_worker1.js', ['goog.messaging.testdata.portnetwork_worker1'], ['goog.messaging.PortCaller', 'goog.messaging.PortChannel']);
-goog.addDependency('messaging/testdata/portnetwork_worker2.js', ['goog.messaging.testdata.portnetwork_worker2'], ['goog.messaging.PortCaller', 'goog.messaging.PortChannel']);
-goog.addDependency('module/abstractmoduleloader.js', ['goog.module.AbstractModuleLoader'], []);
-goog.addDependency('module/basemodule.js', ['goog.module.BaseModule'], ['goog.Disposable']);
-goog.addDependency('module/loader.js', ['goog.module.Loader'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.object']);
-goog.addDependency('module/module.js', ['goog.module'], ['goog.array', 'goog.module.Loader']);
-goog.addDependency('module/moduleinfo.js', ['goog.module.ModuleInfo'], ['goog.Disposable', 'goog.functions', 'goog.module.BaseModule', 'goog.module.ModuleLoadCallback']);
-goog.addDependency('module/moduleinfo_test.js', ['goog.module.ModuleInfoTest'], ['goog.module.BaseModule', 'goog.module.ModuleInfo', 'goog.testing.jsunit']);
-goog.addDependency('module/moduleloadcallback.js', ['goog.module.ModuleLoadCallback'], ['goog.debug.entryPointRegistry', 'goog.debug.errorHandlerWeakDep']);
-goog.addDependency('module/moduleloadcallback_test.js', ['goog.module.ModuleLoadCallbackTest'], ['goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.functions', 'goog.module.ModuleLoadCallback', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('module/moduleloader.js', ['goog.module.ModuleLoader'], ['goog.Timer', 'goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.log', 'goog.module.AbstractModuleLoader', 'goog.net.BulkLoader', 'goog.net.EventType', 'goog.net.jsloader', 'goog.userAgent.product']);
-goog.addDependency('module/moduleloader_test.js', ['goog.module.ModuleLoaderTest'], ['goog.array', 'goog.dom', 'goog.functions', 'goog.module.ModuleLoader', 'goog.module.ModuleManager', 'goog.module.ModuleManager.CallbackType', 'goog.object', 'goog.testing.AsyncTestCase', 'goog.testing.PropertyReplacer', 'goog.testing.events.EventObserver', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent.product']);
-goog.addDependency('module/modulemanager.js', ['goog.module.ModuleManager', 'goog.module.ModuleManager.CallbackType', 'goog.module.ModuleManager.FailureType'], ['goog.Disposable', 'goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.debug.Trace', 'goog.dispose', 'goog.log', 'goog.module.ModuleInfo', 'goog.module.ModuleLoadCallback', 'goog.object']);
-goog.addDependency('module/modulemanager_test.js', ['goog.module.ModuleManagerTest'], ['goog.array', 'goog.functions', 'goog.module.BaseModule', 'goog.module.ModuleManager', 'goog.testing', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('module/testdata/modA_1.js', ['goog.module.testdata.modA_1'], []);
-goog.addDependency('module/testdata/modA_2.js', ['goog.module.testdata.modA_2'], ['goog.module.ModuleManager']);
-goog.addDependency('module/testdata/modB_1.js', ['goog.module.testdata.modB_1'], ['goog.module.ModuleManager']);
-goog.addDependency('net/browserchannel.js', ['goog.net.BrowserChannel', 'goog.net.BrowserChannel.Error', 'goog.net.BrowserChannel.Event', 'goog.net.BrowserChannel.Handler', 'goog.net.BrowserChannel.LogSaver', 'goog.net.BrowserChannel.QueuedMap', 'goog.net.BrowserChannel.ServerReachability', 'goog.net.BrowserChannel.ServerReachabilityEvent', 'goog.net.BrowserChannel.Stat', 'goog.net.BrowserChannel.StatEvent', 'goog.net.BrowserChannel.State', 'goog.net.BrowserChannel.TimingEvent'], ['goog.Uri', 'goog.array', 'goog.asserts', 'goog.debug.TextFormatter', 'goog.events.Event', 'goog.events.EventTarget', 'goog.json', 'goog.json.EvalJsonProcessor', 'goog.log', 'goog.net.BrowserTestChannel', 'goog.net.ChannelDebug', 'goog.net.ChannelRequest', 'goog.net.XhrIo', 'goog.net.tmpnetwork', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.CircularBuffer']);
-goog.addDependency('net/browserchannel_test.js', ['goog.net.BrowserChannelTest'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.functions', 'goog.json', 'goog.net.BrowserChannel', 'goog.net.ChannelDebug', 'goog.net.ChannelRequest', 'goog.net.tmpnetwork', 'goog.structs.Map', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('net/browsertestchannel.js', ['goog.net.BrowserTestChannel'], ['goog.json.EvalJsonProcessor', 'goog.net.ChannelRequest', 'goog.net.ChannelRequest.Error', 'goog.net.tmpnetwork', 'goog.string.Parser', 'goog.userAgent']);
-goog.addDependency('net/bulkloader.js', ['goog.net.BulkLoader'], ['goog.events.EventHandler', 'goog.events.EventTarget', 'goog.log', 'goog.net.BulkLoaderHelper', 'goog.net.EventType', 'goog.net.XhrIo']);
-goog.addDependency('net/bulkloader_test.js', ['goog.net.BulkLoaderTest'], ['goog.events.Event', 'goog.events.EventHandler', 'goog.net.BulkLoader', 'goog.net.EventType', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('net/bulkloaderhelper.js', ['goog.net.BulkLoaderHelper'], ['goog.Disposable', 'goog.log']);
-goog.addDependency('net/channeldebug.js', ['goog.net.ChannelDebug'], ['goog.json', 'goog.log']);
-goog.addDependency('net/channelrequest.js', ['goog.net.ChannelRequest', 'goog.net.ChannelRequest.Error'], ['goog.Timer', 'goog.async.Throttle', 'goog.events.EventHandler', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XmlHttp', 'goog.object', 'goog.userAgent']);
-goog.addDependency('net/channelrequest_test.js', ['goog.net.ChannelRequestTest'], ['goog.Uri', 'goog.functions', 'goog.net.BrowserChannel', 'goog.net.ChannelDebug', 'goog.net.ChannelRequest', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction']);
-goog.addDependency('net/cookies.js', ['goog.net.Cookies', 'goog.net.cookies'], []);
-goog.addDependency('net/cookies_test.js', ['goog.net.cookiesTest'], ['goog.array', 'goog.net.cookies', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('net/corsxmlhttpfactory.js', ['goog.net.CorsXmlHttpFactory', 'goog.net.IeCorsXhrAdapter'], ['goog.net.HttpStatus', 'goog.net.XhrLike', 'goog.net.XmlHttp', 'goog.net.XmlHttpFactory']);
-goog.addDependency('net/corsxmlhttpfactory_test.js', ['goog.net.CorsXmlHttpFactoryTest'], ['goog.net.CorsXmlHttpFactory', 'goog.net.IeCorsXhrAdapter', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('net/crossdomainrpc.js', ['goog.net.CrossDomainRpc'], ['goog.Uri', 'goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.json', 'goog.log', 'goog.net.EventType', 'goog.net.HttpStatus', 'goog.string', 'goog.userAgent']);
-goog.addDependency('net/crossdomainrpc_test.js', ['goog.net.CrossDomainRpcTest'], ['goog.log', 'goog.log.Level', 'goog.net.CrossDomainRpc', 'goog.testing.jsunit']);
-goog.addDependency('net/errorcode.js', ['goog.net.ErrorCode'], []);
-goog.addDependency('net/eventtype.js', ['goog.net.EventType'], []);
-goog.addDependency('net/filedownloader.js', ['goog.net.FileDownloader', 'goog.net.FileDownloader.Error'], ['goog.Disposable', 'goog.asserts', 'goog.async.Deferred', 'goog.crypt.hash32', 'goog.debug.Error', 'goog.events', 'goog.events.EventHandler', 'goog.fs', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.net.EventType', 'goog.net.XhrIo', 'goog.net.XhrIoPool', 'goog.object']);
-goog.addDependency('net/filedownloader_test.js', ['goog.net.FileDownloaderTest'], ['goog.fs.Error', 'goog.net.ErrorCode', 'goog.net.FileDownloader', 'goog.net.XhrIo', 'goog.testing.AsyncTestCase', 'goog.testing.PropertyReplacer', 'goog.testing.fs', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit', 'goog.testing.net.XhrIoPool']);
-goog.addDependency('net/httpstatus.js', ['goog.net.HttpStatus'], []);
-goog.addDependency('net/iframe_xhr_test.js', ['goog.net.iframeXhrTest'], ['goog.Timer', 'goog.debug.Console', 'goog.debug.LogManager', 'goog.debug.Logger', 'goog.events', 'goog.net.IframeIo', 'goog.net.XhrIo', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('net/iframeio.js', ['goog.net.IframeIo', 'goog.net.IframeIo.IncrementalDataEvent'], ['goog.Timer', 'goog.Uri', 'goog.asserts', 'goog.debug', 'goog.dom', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.json', 'goog.log', 'goog.log.Level', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.reflect', 'goog.string', 'goog.structs', 'goog.userAgent']);
-goog.addDependency('net/iframeio_different_base_test.js', ['goog.net.iframeIoDifferentBaseTest'], ['goog.events', 'goog.net.EventType', 'goog.net.IframeIo', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('net/iframeio_test.js', ['goog.net.IframeIoTest'], ['goog.debug', 'goog.debug.DivConsole', 'goog.debug.LogManager', 'goog.dom', 'goog.events', 'goog.events.EventType', 'goog.log', 'goog.log.Level', 'goog.net.IframeIo', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('net/iframeloadmonitor.js', ['goog.net.IframeLoadMonitor'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.userAgent']);
-goog.addDependency('net/iframeloadmonitor_test.js', ['goog.net.IframeLoadMonitorTest'], ['goog.dom', 'goog.events', 'goog.net.IframeLoadMonitor', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('net/imageloader.js', ['goog.net.ImageLoader'], ['goog.array', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.net.EventType', 'goog.object', 'goog.userAgent']);
-goog.addDependency('net/imageloader_test.js', ['goog.net.ImageLoaderTest'], ['goog.Timer', 'goog.array', 'goog.dispose', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.net.EventType', 'goog.net.ImageLoader', 'goog.object', 'goog.string', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('net/ipaddress.js', ['goog.net.IpAddress', 'goog.net.Ipv4Address', 'goog.net.Ipv6Address'], ['goog.array', 'goog.math.Integer', 'goog.object', 'goog.string']);
-goog.addDependency('net/ipaddress_test.js', ['goog.net.IpAddressTest'], ['goog.math.Integer', 'goog.net.IpAddress', 'goog.net.Ipv4Address', 'goog.net.Ipv6Address', 'goog.testing.jsunit']);
-goog.addDependency('net/jsloader.js', ['goog.net.jsloader', 'goog.net.jsloader.Error', 'goog.net.jsloader.ErrorCode', 'goog.net.jsloader.Options'], ['goog.array', 'goog.async.Deferred', 'goog.debug.Error', 'goog.dom', 'goog.dom.TagName']);
-goog.addDependency('net/jsloader_test.js', ['goog.net.jsloaderTest'], ['goog.array', 'goog.dom', 'goog.net.jsloader', 'goog.net.jsloader.ErrorCode', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('net/jsonp.js', ['goog.net.Jsonp'], ['goog.Uri', 'goog.net.jsloader']);
-goog.addDependency('net/jsonp_test.js', ['goog.net.JsonpTest'], ['goog.net.Jsonp', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('net/mockiframeio.js', ['goog.net.MockIFrameIo'], ['goog.events.EventTarget', 'goog.json', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.IframeIo']);
-goog.addDependency('net/multiiframeloadmonitor.js', ['goog.net.MultiIframeLoadMonitor'], ['goog.events', 'goog.net.IframeLoadMonitor']);
-goog.addDependency('net/multiiframeloadmonitor_test.js', ['goog.net.MultiIframeLoadMonitorTest'], ['goog.dom', 'goog.net.IframeLoadMonitor', 'goog.net.MultiIframeLoadMonitor', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('net/networkstatusmonitor.js', ['goog.net.NetworkStatusMonitor'], ['goog.events.Listenable']);
-goog.addDependency('net/networktester.js', ['goog.net.NetworkTester'], ['goog.Timer', 'goog.Uri', 'goog.log']);
-goog.addDependency('net/networktester_test.js', ['goog.net.NetworkTesterTest'], ['goog.Uri', 'goog.net.NetworkTester', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('net/testdata/jsloader_test1.js', ['goog.net.testdata.jsloader_test1'], []);
-goog.addDependency('net/testdata/jsloader_test2.js', ['goog.net.testdata.jsloader_test2'], []);
-goog.addDependency('net/testdata/jsloader_test3.js', ['goog.net.testdata.jsloader_test3'], []);
-goog.addDependency('net/testdata/jsloader_test4.js', ['goog.net.testdata.jsloader_test4'], []);
-goog.addDependency('net/tmpnetwork.js', ['goog.net.tmpnetwork'], ['goog.Uri', 'goog.net.ChannelDebug']);
-goog.addDependency('net/websocket.js', ['goog.net.WebSocket', 'goog.net.WebSocket.ErrorEvent', 'goog.net.WebSocket.EventType', 'goog.net.WebSocket.MessageEvent'], ['goog.Timer', 'goog.asserts', 'goog.debug.entryPointRegistry', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.log']);
-goog.addDependency('net/websocket_test.js', ['goog.net.WebSocketTest'], ['goog.debug.EntryPointMonitor', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.events', 'goog.functions', 'goog.net.WebSocket', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('net/wrapperxmlhttpfactory.js', ['goog.net.WrapperXmlHttpFactory'], ['goog.net.XhrLike', 'goog.net.XmlHttpFactory']);
-goog.addDependency('net/xhrio.js', ['goog.net.XhrIo', 'goog.net.XhrIo.ResponseType'], ['goog.Timer', 'goog.array', 'goog.debug.entryPointRegistry', 'goog.events.EventTarget', 'goog.json', 'goog.log', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.HttpStatus', 'goog.net.XmlHttp', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.Map', 'goog.uri.utils', 'goog.userAgent']);
-goog.addDependency('net/xhrio_test.js', ['goog.net.XhrIoTest'], ['goog.Uri', 'goog.debug.EntryPointMonitor', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.events', 'goog.functions', 'goog.net.EventType', 'goog.net.WrapperXmlHttpFactory', 'goog.net.XhrIo', 'goog.net.XmlHttp', 'goog.object', 'goog.string', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction']);
-goog.addDependency('net/xhriopool.js', ['goog.net.XhrIoPool'], ['goog.net.XhrIo', 'goog.structs.PriorityPool']);
-goog.addDependency('net/xhrlike.js', ['goog.net.XhrLike'], []);
-goog.addDependency('net/xhrmanager.js', ['goog.net.XhrManager', 'goog.net.XhrManager.Event', 'goog.net.XhrManager.Request'], ['goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XhrIo', 'goog.net.XhrIoPool', 'goog.structs.Map']);
-goog.addDependency('net/xhrmanager_test.js', ['goog.net.XhrManagerTest'], ['goog.events', 'goog.net.EventType', 'goog.net.XhrIo', 'goog.net.XhrManager', 'goog.testing.jsunit', 'goog.testing.net.XhrIoPool', 'goog.testing.recordFunction']);
-goog.addDependency('net/xmlhttp.js', ['goog.net.DefaultXmlHttpFactory', 'goog.net.XmlHttp', 'goog.net.XmlHttp.OptionType', 'goog.net.XmlHttp.ReadyState', 'goog.net.XmlHttpDefines'], ['goog.asserts', 'goog.net.WrapperXmlHttpFactory', 'goog.net.XmlHttpFactory']);
-goog.addDependency('net/xmlhttpfactory.js', ['goog.net.XmlHttpFactory'], ['goog.net.XhrLike']);
-goog.addDependency('net/xpc/crosspagechannel.js', ['goog.net.xpc.CrossPageChannel'], ['goog.Uri', 'goog.async.Deferred', 'goog.async.Delay', 'goog.dispose', 'goog.dom', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.json', 'goog.log', 'goog.messaging.AbstractChannel', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.ChannelStates', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.DirectTransport', 'goog.net.xpc.FrameElementMethodTransport', 'goog.net.xpc.IframePollingTransport', 'goog.net.xpc.IframeRelayTransport', 'goog.net.xpc.NativeMessagingTransport', 'goog.net.xpc.NixTransport', 'goog.net.xpc.TransportTypes', 'goog.net.xpc.UriCfgFields', 'goog.string', 'goog.uri.utils', 'goog.userAgent']);
-goog.addDependency('net/xpc/crosspagechannel_test.js', ['goog.net.xpc.CrossPageChannelTest'], ['goog.Disposable', 'goog.Uri', 'goog.async.Deferred', 'goog.dom', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.TransportTypes', 'goog.object', 'goog.testing.AsyncTestCase', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('net/xpc/crosspagechannelrole.js', ['goog.net.xpc.CrossPageChannelRole'], []);
-goog.addDependency('net/xpc/directtransport.js', ['goog.net.xpc.DirectTransport'], ['goog.Timer', 'goog.async.Deferred', 'goog.events.EventHandler', 'goog.log', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.object']);
-goog.addDependency('net/xpc/directtransport_test.js', ['goog.net.xpc.DirectTransportTest'], ['goog.dom', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.TransportTypes', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('net/xpc/frameelementmethodtransport.js', ['goog.net.xpc.FrameElementMethodTransport'], ['goog.log', 'goog.net.xpc', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes']);
-goog.addDependency('net/xpc/iframepollingtransport.js', ['goog.net.xpc.IframePollingTransport', 'goog.net.xpc.IframePollingTransport.Receiver', 'goog.net.xpc.IframePollingTransport.Sender'], ['goog.array', 'goog.dom', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.userAgent']);
-goog.addDependency('net/xpc/iframepollingtransport_test.js', ['goog.net.xpc.IframePollingTransportTest'], ['goog.Timer', 'goog.dom', 'goog.dom.TagName', 'goog.functions', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.TransportTypes', 'goog.object', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('net/xpc/iframerelaytransport.js', ['goog.net.xpc.IframeRelayTransport'], ['goog.dom', 'goog.dom.safe', 'goog.events', 'goog.html.SafeHtml', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.string', 'goog.string.Const', 'goog.userAgent']);
-goog.addDependency('net/xpc/nativemessagingtransport.js', ['goog.net.xpc.NativeMessagingTransport'], ['goog.Timer', 'goog.asserts', 'goog.async.Deferred', 'goog.events', 'goog.events.EventHandler', 'goog.log', 'goog.net.xpc', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes']);
-goog.addDependency('net/xpc/nativemessagingtransport_test.js', ['goog.net.xpc.NativeMessagingTransportTest'], ['goog.dom', 'goog.events', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.NativeMessagingTransport', 'goog.testing.jsunit']);
-goog.addDependency('net/xpc/nixtransport.js', ['goog.net.xpc.NixTransport'], ['goog.log', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.reflect']);
-goog.addDependency('net/xpc/relay.js', ['goog.net.xpc.relay'], []);
-goog.addDependency('net/xpc/transport.js', ['goog.net.xpc.Transport'], ['goog.Disposable', 'goog.dom', 'goog.net.xpc.TransportNames']);
-goog.addDependency('net/xpc/xpc.js', ['goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.ChannelStates', 'goog.net.xpc.TransportNames', 'goog.net.xpc.TransportTypes', 'goog.net.xpc.UriCfgFields'], ['goog.log']);
-goog.addDependency('object/object.js', ['goog.object'], []);
-goog.addDependency('object/object_test.js', ['goog.objectTest'], ['goog.functions', 'goog.object', 'goog.testing.jsunit']);
-goog.addDependency('positioning/absoluteposition.js', ['goog.positioning.AbsolutePosition'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.math.Size', 'goog.positioning', 'goog.positioning.AbstractPosition']);
-goog.addDependency('positioning/abstractposition.js', ['goog.positioning.AbstractPosition'], ['goog.math.Box', 'goog.math.Size', 'goog.positioning.Corner']);
-goog.addDependency('positioning/anchoredposition.js', ['goog.positioning.AnchoredPosition'], ['goog.math.Box', 'goog.positioning', 'goog.positioning.AbstractPosition']);
-goog.addDependency('positioning/anchoredposition_test.js', ['goog.positioning.AnchoredPositionTest'], ['goog.dom', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.style', 'goog.testing.jsunit']);
-goog.addDependency('positioning/anchoredviewportposition.js', ['goog.positioning.AnchoredViewportPosition'], ['goog.math.Box', 'goog.positioning', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus']);
-goog.addDependency('positioning/anchoredviewportposition_test.js', ['goog.positioning.AnchoredViewportPositionTest'], ['goog.dom', 'goog.math.Box', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.Corner', 'goog.positioning.OverflowStatus', 'goog.style', 'goog.testing.jsunit']);
-goog.addDependency('positioning/clientposition.js', ['goog.positioning.ClientPosition'], ['goog.asserts', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Size', 'goog.positioning', 'goog.positioning.AbstractPosition', 'goog.style']);
-goog.addDependency('positioning/clientposition_test.js', ['goog.positioning.clientPositionTest'], ['goog.dom', 'goog.positioning.ClientPosition', 'goog.style', 'goog.testing.jsunit']);
-goog.addDependency('positioning/menuanchoredposition.js', ['goog.positioning.MenuAnchoredPosition'], ['goog.math.Box', 'goog.math.Size', 'goog.positioning', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow']);
-goog.addDependency('positioning/menuanchoredposition_test.js', ['goog.positioning.MenuAnchoredPositionTest'], ['goog.dom', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.testing.jsunit']);
-goog.addDependency('positioning/positioning.js', ['goog.positioning', 'goog.positioning.Corner', 'goog.positioning.CornerBit', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Size', 'goog.style', 'goog.style.bidi']);
-goog.addDependency('positioning/positioning_test.js', ['goog.positioningTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']);
-goog.addDependency('positioning/viewportclientposition.js', ['goog.positioning.ViewportClientPosition'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.math.Size', 'goog.positioning.ClientPosition']);
-goog.addDependency('positioning/viewportclientposition_test.js', ['goog.positioning.ViewportClientPositionTest'], ['goog.dom', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.ViewportClientPosition', 'goog.style', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('positioning/viewportposition.js', ['goog.positioning.ViewportPosition'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.math.Size', 'goog.positioning.AbstractPosition']);
-goog.addDependency('promise/promise.js', ['goog.Promise'], ['goog.Thenable', 'goog.asserts', 'goog.async.run', 'goog.async.throwException', 'goog.debug.Error', 'goog.promise.Resolver']);
-goog.addDependency('promise/promise_test.js', ['goog.PromiseTest'], ['goog.Promise', 'goog.Thenable', 'goog.functions', 'goog.testing.AsyncTestCase', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('promise/resolver.js', ['goog.promise.Resolver'], []);
-goog.addDependency('promise/testsuiteadapter.js', ['goog.promise.testSuiteAdapter'], ['goog.Promise']);
-goog.addDependency('promise/thenable.js', ['goog.Thenable'], []);
-goog.addDependency('proto/proto.js', ['goog.proto'], ['goog.proto.Serializer']);
-goog.addDependency('proto/serializer.js', ['goog.proto.Serializer'], ['goog.json.Serializer', 'goog.string']);
-goog.addDependency('proto/serializer_test.js', ['goog.protoTest'], ['goog.proto', 'goog.testing.jsunit']);
-goog.addDependency('proto2/descriptor.js', ['goog.proto2.Descriptor', 'goog.proto2.Metadata'], ['goog.array', 'goog.asserts', 'goog.object', 'goog.string']);
-goog.addDependency('proto2/descriptor_test.js', ['goog.proto2.DescriptorTest'], ['goog.proto2.Descriptor', 'goog.testing.jsunit']);
-goog.addDependency('proto2/fielddescriptor.js', ['goog.proto2.FieldDescriptor'], ['goog.asserts', 'goog.string']);
-goog.addDependency('proto2/fielddescriptor_test.js', ['goog.proto2.FieldDescriptorTest'], ['goog.proto2.FieldDescriptor', 'goog.proto2.Message', 'goog.testing.jsunit']);
-goog.addDependency('proto2/lazydeserializer.js', ['goog.proto2.LazyDeserializer'], ['goog.asserts', 'goog.proto2.Message', 'goog.proto2.Serializer']);
-goog.addDependency('proto2/message.js', ['goog.proto2.Message'], ['goog.asserts', 'goog.proto2.Descriptor', 'goog.proto2.FieldDescriptor']);
-goog.addDependency('proto2/message_test.js', ['goog.proto2.MessageTest'], ['goog.testing.jsunit', 'proto2.TestAllTypes', 'proto2.TestAllTypes.NestedEnum', 'proto2.TestAllTypes.NestedMessage', 'proto2.TestAllTypes.OptionalGroup', 'proto2.TestAllTypes.RepeatedGroup']);
-goog.addDependency('proto2/objectserializer.js', ['goog.proto2.ObjectSerializer'], ['goog.asserts', 'goog.proto2.Serializer', 'goog.string']);
-goog.addDependency('proto2/objectserializer_test.js', ['goog.proto2.ObjectSerializerTest'], ['goog.proto2.ObjectSerializer', 'goog.proto2.Serializer', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'proto2.TestAllTypes']);
-goog.addDependency('proto2/package_test.pb.js', ['someprotopackage.TestPackageTypes'], ['goog.proto2.Message', 'proto2.TestAllTypes']);
-goog.addDependency('proto2/pbliteserializer.js', ['goog.proto2.PbLiteSerializer'], ['goog.asserts', 'goog.proto2.FieldDescriptor', 'goog.proto2.LazyDeserializer', 'goog.proto2.Serializer']);
-goog.addDependency('proto2/pbliteserializer_test.js', ['goog.proto2.PbLiteSerializerTest'], ['goog.proto2.PbLiteSerializer', 'goog.testing.jsunit', 'proto2.TestAllTypes']);
-goog.addDependency('proto2/proto_test.js', ['goog.proto2.messageTest'], ['goog.proto2.FieldDescriptor', 'goog.testing.jsunit', 'proto2.TestAllTypes', 'someprotopackage.TestPackageTypes']);
-goog.addDependency('proto2/serializer.js', ['goog.proto2.Serializer'], ['goog.asserts', 'goog.proto2.FieldDescriptor', 'goog.proto2.Message']);
-goog.addDependency('proto2/test.pb.js', ['proto2.TestAllTypes', 'proto2.TestAllTypes.NestedEnum', 'proto2.TestAllTypes.NestedMessage', 'proto2.TestAllTypes.OptionalGroup', 'proto2.TestAllTypes.RepeatedGroup', 'proto2.TestDefaultChild', 'proto2.TestDefaultParent'], ['goog.proto2.Message']);
-goog.addDependency('proto2/textformatserializer.js', ['goog.proto2.TextFormatSerializer'], ['goog.array', 'goog.asserts', 'goog.json', 'goog.math', 'goog.object', 'goog.proto2.FieldDescriptor', 'goog.proto2.Message', 'goog.proto2.Serializer', 'goog.string']);
-goog.addDependency('proto2/textformatserializer_test.js', ['goog.proto2.TextFormatSerializerTest'], ['goog.proto2.ObjectSerializer', 'goog.proto2.TextFormatSerializer', 'goog.testing.jsunit', 'proto2.TestAllTypes']);
-goog.addDependency('proto2/util.js', ['goog.proto2.Util'], ['goog.asserts']);
-goog.addDependency('pubsub/pubsub.js', ['goog.pubsub.PubSub'], ['goog.Disposable', 'goog.array']);
-goog.addDependency('pubsub/pubsub_test.js', ['goog.pubsub.PubSubTest'], ['goog.array', 'goog.pubsub.PubSub', 'goog.testing.jsunit']);
-goog.addDependency('pubsub/topicid.js', ['goog.pubsub.TopicId'], []);
-goog.addDependency('pubsub/typedpubsub.js', ['goog.pubsub.TypedPubSub'], ['goog.Disposable', 'goog.pubsub.PubSub']);
-goog.addDependency('pubsub/typedpubsub_test.js', ['goog.pubsub.TypedPubSubTest'], ['goog.array', 'goog.pubsub.TopicId', 'goog.pubsub.TypedPubSub', 'goog.testing.jsunit']);
-goog.addDependency('reflect/reflect.js', ['goog.reflect'], []);
-goog.addDependency('result/deferredadaptor.js', ['goog.result.DeferredAdaptor'], ['goog.async.Deferred', 'goog.result', 'goog.result.Result']);
-goog.addDependency('result/dependentresult.js', ['goog.result.DependentResult'], ['goog.result.Result']);
-goog.addDependency('result/result_interface.js', ['goog.result.Result'], ['goog.Thenable']);
-goog.addDependency('result/resultutil.js', ['goog.result'], ['goog.array', 'goog.result.DependentResult', 'goog.result.Result', 'goog.result.SimpleResult']);
-goog.addDependency('result/simpleresult.js', ['goog.result.SimpleResult', 'goog.result.SimpleResult.StateError'], ['goog.Promise', 'goog.Thenable', 'goog.debug.Error', 'goog.result.Result']);
-goog.addDependency('soy/data.js', ['goog.soy.data', 'goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind'], ['goog.html.SafeHtml', 'goog.html.uncheckedconversions', 'goog.string.Const']);
-goog.addDependency('soy/data_test.js', ['goog.soy.dataTest'], ['goog.html.SafeHtml', 'goog.soy.testHelper', 'goog.testing.jsunit']);
-goog.addDependency('soy/renderer.js', ['goog.soy.InjectedDataSupplier', 'goog.soy.Renderer'], ['goog.asserts', 'goog.dom', 'goog.soy', 'goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind']);
-goog.addDependency('soy/renderer_test.js', ['goog.soy.RendererTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.html.SafeHtml', 'goog.i18n.bidi.Dir', 'goog.soy.Renderer', 'goog.soy.data.SanitizedContentKind', 'goog.soy.testHelper', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('soy/soy.js', ['goog.soy'], ['goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind', 'goog.string']);
-goog.addDependency('soy/soy_test.js', ['goog.soyTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.functions', 'goog.soy', 'goog.soy.testHelper', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('soy/soy_testhelper.js', ['goog.soy.testHelper'], ['goog.dom', 'goog.dom.TagName', 'goog.i18n.bidi.Dir', 'goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind', 'goog.string', 'goog.userAgent']);
-goog.addDependency('spell/spellcheck.js', ['goog.spell.SpellCheck', 'goog.spell.SpellCheck.WordChangedEvent'], ['goog.Timer', 'goog.events.EventTarget', 'goog.structs.Set']);
-goog.addDependency('spell/spellcheck_test.js', ['goog.spell.SpellCheckTest'], ['goog.spell.SpellCheck', 'goog.testing.jsunit']);
-goog.addDependency('stats/basicstat.js', ['goog.stats.BasicStat'], ['goog.array', 'goog.iter', 'goog.log', 'goog.object', 'goog.string.format', 'goog.structs.CircularBuffer']);
-goog.addDependency('stats/basicstat_test.js', ['goog.stats.BasicStatTest'], ['goog.array', 'goog.stats.BasicStat', 'goog.string.format', 'goog.testing.PseudoRandom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('storage/collectablestorage.js', ['goog.storage.CollectableStorage'], ['goog.array', 'goog.iter', 'goog.storage.ErrorCode', 'goog.storage.ExpiringStorage', 'goog.storage.RichStorage']);
-goog.addDependency('storage/collectablestorage_test.js', ['goog.storage.CollectableStorageTest'], ['goog.storage.CollectableStorage', 'goog.storage.collectableStorageTester', 'goog.storage.storage_test', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']);
-goog.addDependency('storage/collectablestoragetester.js', ['goog.storage.collectableStorageTester'], ['goog.testing.asserts']);
-goog.addDependency('storage/encryptedstorage.js', ['goog.storage.EncryptedStorage'], ['goog.crypt', 'goog.crypt.Arc4', 'goog.crypt.Sha1', 'goog.crypt.base64', 'goog.json', 'goog.json.Serializer', 'goog.storage.CollectableStorage', 'goog.storage.ErrorCode', 'goog.storage.RichStorage', 'goog.storage.RichStorage.Wrapper', 'goog.storage.mechanism.IterableMechanism']);
-goog.addDependency('storage/encryptedstorage_test.js', ['goog.storage.EncryptedStorageTest'], ['goog.json', 'goog.storage.EncryptedStorage', 'goog.storage.ErrorCode', 'goog.storage.RichStorage', 'goog.storage.collectableStorageTester', 'goog.storage.storage_test', 'goog.testing.MockClock', 'goog.testing.PseudoRandom', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']);
-goog.addDependency('storage/errorcode.js', ['goog.storage.ErrorCode'], []);
-goog.addDependency('storage/expiringstorage.js', ['goog.storage.ExpiringStorage'], ['goog.storage.RichStorage', 'goog.storage.RichStorage.Wrapper', 'goog.storage.mechanism.Mechanism']);
-goog.addDependency('storage/expiringstorage_test.js', ['goog.storage.ExpiringStorageTest'], ['goog.storage.ExpiringStorage', 'goog.storage.storage_test', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']);
-goog.addDependency('storage/mechanism/errorcode.js', ['goog.storage.mechanism.ErrorCode'], []);
-goog.addDependency('storage/mechanism/errorhandlingmechanism.js', ['goog.storage.mechanism.ErrorHandlingMechanism'], ['goog.storage.mechanism.Mechanism']);
-goog.addDependency('storage/mechanism/errorhandlingmechanism_test.js', ['goog.storage.mechanism.ErrorHandlingMechanismTest'], ['goog.storage.mechanism.ErrorHandlingMechanism', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('storage/mechanism/html5localstorage.js', ['goog.storage.mechanism.HTML5LocalStorage'], ['goog.storage.mechanism.HTML5WebStorage']);
-goog.addDependency('storage/mechanism/html5localstorage_test.js', ['goog.storage.mechanism.HTML5LocalStorageTest'], ['goog.storage.mechanism.HTML5LocalStorage', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('storage/mechanism/html5sessionstorage.js', ['goog.storage.mechanism.HTML5SessionStorage'], ['goog.storage.mechanism.HTML5WebStorage']);
-goog.addDependency('storage/mechanism/html5sessionstorage_test.js', ['goog.storage.mechanism.HTML5SessionStorageTest'], ['goog.storage.mechanism.HTML5SessionStorage', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('storage/mechanism/html5webstorage.js', ['goog.storage.mechanism.HTML5WebStorage'], ['goog.asserts', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.storage.mechanism.ErrorCode', 'goog.storage.mechanism.IterableMechanism']);
-goog.addDependency('storage/mechanism/html5webstorage_test.js', ['goog.storage.mechanism.HTML5MockStorage', 'goog.storage.mechanism.HTML5WebStorageTest', 'goog.storage.mechanism.MockThrowableStorage'], ['goog.storage.mechanism.ErrorCode', 'goog.storage.mechanism.HTML5WebStorage', 'goog.testing.jsunit']);
-goog.addDependency('storage/mechanism/ieuserdata.js', ['goog.storage.mechanism.IEUserData'], ['goog.asserts', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.storage.mechanism.ErrorCode', 'goog.storage.mechanism.IterableMechanism', 'goog.structs.Map', 'goog.userAgent']);
-goog.addDependency('storage/mechanism/ieuserdata_test.js', ['goog.storage.mechanism.IEUserDataTest'], ['goog.storage.mechanism.IEUserData', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('storage/mechanism/iterablemechanism.js', ['goog.storage.mechanism.IterableMechanism'], ['goog.array', 'goog.asserts', 'goog.iter', 'goog.iter.Iterator', 'goog.storage.mechanism.Mechanism']);
-goog.addDependency('storage/mechanism/iterablemechanismtester.js', ['goog.storage.mechanism.iterableMechanismTester'], ['goog.iter.Iterator', 'goog.storage.mechanism.IterableMechanism', 'goog.testing.asserts']);
-goog.addDependency('storage/mechanism/mechanism.js', ['goog.storage.mechanism.Mechanism'], []);
-goog.addDependency('storage/mechanism/mechanismfactory.js', ['goog.storage.mechanism.mechanismfactory'], ['goog.storage.mechanism.HTML5LocalStorage', 'goog.storage.mechanism.HTML5SessionStorage', 'goog.storage.mechanism.IEUserData', 'goog.storage.mechanism.IterableMechanism', 'goog.storage.mechanism.PrefixedMechanism']);
-goog.addDependency('storage/mechanism/mechanismfactory_test.js', ['goog.storage.mechanism.mechanismfactoryTest'], ['goog.storage.mechanism.mechanismfactory', 'goog.testing.jsunit']);
-goog.addDependency('storage/mechanism/mechanismseparationtester.js', ['goog.storage.mechanism.mechanismSeparationTester'], ['goog.iter.Iterator', 'goog.storage.mechanism.IterableMechanism', 'goog.testing.asserts']);
-goog.addDependency('storage/mechanism/mechanismsharingtester.js', ['goog.storage.mechanism.mechanismSharingTester'], ['goog.iter.Iterator', 'goog.storage.mechanism.IterableMechanism', 'goog.testing.asserts']);
-goog.addDependency('storage/mechanism/mechanismtester.js', ['goog.storage.mechanism.mechanismTester'], ['goog.storage.mechanism.ErrorCode', 'goog.storage.mechanism.HTML5LocalStorage', 'goog.storage.mechanism.Mechanism', 'goog.testing.asserts', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('storage/mechanism/prefixedmechanism.js', ['goog.storage.mechanism.PrefixedMechanism'], ['goog.iter.Iterator', 'goog.storage.mechanism.IterableMechanism']);
-goog.addDependency('storage/mechanism/prefixedmechanism_test.js', ['goog.storage.mechanism.PrefixedMechanismTest'], ['goog.storage.mechanism.HTML5LocalStorage', 'goog.storage.mechanism.PrefixedMechanism', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.testing.jsunit']);
-goog.addDependency('storage/richstorage.js', ['goog.storage.RichStorage', 'goog.storage.RichStorage.Wrapper'], ['goog.storage.ErrorCode', 'goog.storage.Storage', 'goog.storage.mechanism.Mechanism']);
-goog.addDependency('storage/richstorage_test.js', ['goog.storage.RichStorageTest'], ['goog.storage.ErrorCode', 'goog.storage.RichStorage', 'goog.storage.storage_test', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']);
-goog.addDependency('storage/storage.js', ['goog.storage.Storage'], ['goog.json', 'goog.storage.ErrorCode']);
-goog.addDependency('storage/storage_test.js', ['goog.storage.storage_test'], ['goog.storage.Storage', 'goog.structs.Map', 'goog.testing.asserts']);
-goog.addDependency('string/const.js', ['goog.string.Const'], ['goog.asserts', 'goog.string.TypedString']);
-goog.addDependency('string/const_test.js', ['goog.string.constTest'], ['goog.string.Const', 'goog.testing.jsunit']);
-goog.addDependency('string/linkify.js', ['goog.string.linkify'], ['goog.string']);
-goog.addDependency('string/linkify_test.js', ['goog.string.linkifyTest'], ['goog.string', 'goog.string.linkify', 'goog.testing.dom', 'goog.testing.jsunit']);
-goog.addDependency('string/newlines.js', ['goog.string.newlines', 'goog.string.newlines.Line'], ['goog.array']);
-goog.addDependency('string/newlines_test.js', ['goog.string.newlinesTest'], ['goog.string.newlines', 'goog.testing.jsunit']);
-goog.addDependency('string/parser.js', ['goog.string.Parser'], []);
-goog.addDependency('string/path.js', ['goog.string.path'], ['goog.array', 'goog.string']);
-goog.addDependency('string/path_test.js', ['goog.string.pathTest'], ['goog.string.path', 'goog.testing.jsunit']);
-goog.addDependency('string/string.js', ['goog.string', 'goog.string.Unicode'], []);
-goog.addDependency('string/string_test.js', ['goog.stringTest'], ['goog.functions', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']);
-goog.addDependency('string/stringbuffer.js', ['goog.string.StringBuffer'], []);
-goog.addDependency('string/stringbuffer_test.js', ['goog.string.StringBufferTest'], ['goog.string.StringBuffer', 'goog.testing.jsunit']);
-goog.addDependency('string/stringformat.js', ['goog.string.format'], ['goog.string']);
-goog.addDependency('string/stringformat_test.js', ['goog.string.formatTest'], ['goog.string.format', 'goog.testing.jsunit']);
-goog.addDependency('string/stringifier.js', ['goog.string.Stringifier'], []);
-goog.addDependency('string/typedstring.js', ['goog.string.TypedString'], []);
-goog.addDependency('structs/avltree.js', ['goog.structs.AvlTree', 'goog.structs.AvlTree.Node'], ['goog.structs.Collection']);
-goog.addDependency('structs/avltree_test.js', ['goog.structs.AvlTreeTest'], ['goog.array', 'goog.structs.AvlTree', 'goog.testing.jsunit']);
-goog.addDependency('structs/circularbuffer.js', ['goog.structs.CircularBuffer'], []);
-goog.addDependency('structs/circularbuffer_test.js', ['goog.structs.CircularBufferTest'], ['goog.structs.CircularBuffer', 'goog.testing.jsunit']);
-goog.addDependency('structs/collection.js', ['goog.structs.Collection'], []);
-goog.addDependency('structs/collection_test.js', ['goog.structs.CollectionTest'], ['goog.structs.AvlTree', 'goog.structs.Set', 'goog.testing.jsunit']);
-goog.addDependency('structs/heap.js', ['goog.structs.Heap'], ['goog.array', 'goog.object', 'goog.structs.Node']);
-goog.addDependency('structs/heap_test.js', ['goog.structs.HeapTest'], ['goog.structs', 'goog.structs.Heap', 'goog.testing.jsunit']);
-goog.addDependency('structs/inversionmap.js', ['goog.structs.InversionMap'], ['goog.array']);
-goog.addDependency('structs/inversionmap_test.js', ['goog.structs.InversionMapTest'], ['goog.structs.InversionMap', 'goog.testing.jsunit']);
-goog.addDependency('structs/linkedmap.js', ['goog.structs.LinkedMap'], ['goog.structs.Map']);
-goog.addDependency('structs/linkedmap_test.js', ['goog.structs.LinkedMapTest'], ['goog.structs.LinkedMap', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('structs/map.js', ['goog.structs.Map'], ['goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.object']);
-goog.addDependency('structs/map_test.js', ['goog.structs.MapTest'], ['goog.iter', 'goog.structs', 'goog.structs.Map', 'goog.testing.jsunit']);
-goog.addDependency('structs/node.js', ['goog.structs.Node'], []);
-goog.addDependency('structs/pool.js', ['goog.structs.Pool'], ['goog.Disposable', 'goog.structs.Queue', 'goog.structs.Set']);
-goog.addDependency('structs/pool_test.js', ['goog.structs.PoolTest'], ['goog.structs.Pool', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('structs/prioritypool.js', ['goog.structs.PriorityPool'], ['goog.structs.Pool', 'goog.structs.PriorityQueue']);
-goog.addDependency('structs/prioritypool_test.js', ['goog.structs.PriorityPoolTest'], ['goog.structs.PriorityPool', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('structs/priorityqueue.js', ['goog.structs.PriorityQueue'], ['goog.structs.Heap']);
-goog.addDependency('structs/priorityqueue_test.js', ['goog.structs.PriorityQueueTest'], ['goog.structs', 'goog.structs.PriorityQueue', 'goog.testing.jsunit']);
-goog.addDependency('structs/quadtree.js', ['goog.structs.QuadTree', 'goog.structs.QuadTree.Node', 'goog.structs.QuadTree.Point'], ['goog.math.Coordinate']);
-goog.addDependency('structs/quadtree_test.js', ['goog.structs.QuadTreeTest'], ['goog.structs', 'goog.structs.QuadTree', 'goog.testing.jsunit']);
-goog.addDependency('structs/queue.js', ['goog.structs.Queue'], ['goog.array']);
-goog.addDependency('structs/queue_test.js', ['goog.structs.QueueTest'], ['goog.structs.Queue', 'goog.testing.jsunit']);
-goog.addDependency('structs/set.js', ['goog.structs.Set'], ['goog.structs', 'goog.structs.Collection', 'goog.structs.Map']);
-goog.addDependency('structs/set_test.js', ['goog.structs.SetTest'], ['goog.iter', 'goog.structs', 'goog.structs.Set', 'goog.testing.jsunit']);
-goog.addDependency('structs/simplepool.js', ['goog.structs.SimplePool'], ['goog.Disposable']);
-goog.addDependency('structs/stringset.js', ['goog.structs.StringSet'], ['goog.asserts', 'goog.iter']);
-goog.addDependency('structs/stringset_test.js', ['goog.structs.StringSetTest'], ['goog.array', 'goog.iter', 'goog.structs.StringSet', 'goog.testing.asserts', 'goog.testing.jsunit']);
-goog.addDependency('structs/structs.js', ['goog.structs'], ['goog.array', 'goog.object']);
-goog.addDependency('structs/structs_test.js', ['goog.structsTest'], ['goog.array', 'goog.structs', 'goog.structs.Map', 'goog.structs.Set', 'goog.testing.jsunit']);
-goog.addDependency('structs/treenode.js', ['goog.structs.TreeNode'], ['goog.array', 'goog.asserts', 'goog.structs.Node']);
-goog.addDependency('structs/treenode_test.js', ['goog.structs.TreeNodeTest'], ['goog.structs.TreeNode', 'goog.testing.jsunit']);
-goog.addDependency('structs/trie.js', ['goog.structs.Trie'], ['goog.object', 'goog.structs']);
-goog.addDependency('structs/trie_test.js', ['goog.structs.TrieTest'], ['goog.object', 'goog.structs', 'goog.structs.Trie', 'goog.testing.jsunit']);
-goog.addDependency('structs/weak/weak.js', ['goog.structs.weak'], ['goog.userAgent']);
-goog.addDependency('structs/weak/weak_test.js', ['goog.structs.weakTest'], ['goog.array', 'goog.structs.weak', 'goog.testing.jsunit']);
-goog.addDependency('style/bidi.js', ['goog.style.bidi'], ['goog.dom', 'goog.style', 'goog.userAgent']);
-goog.addDependency('style/bidi_test.js', ['goog.style.bidiTest'], ['goog.dom', 'goog.style', 'goog.style.bidi', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('style/cursor.js', ['goog.style.cursor'], ['goog.userAgent']);
-goog.addDependency('style/cursor_test.js', ['goog.style.cursorTest'], ['goog.style.cursor', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('style/style.js', ['goog.style'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.vendor', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.object', 'goog.string', 'goog.userAgent']);
-goog.addDependency('style/style_test.js', ['goog.style_test'], ['goog.array', 'goog.color', 'goog.dom', 'goog.events.BrowserEvent', 'goog.labs.userAgent.util', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.object', 'goog.string', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.MockUserAgent', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion', 'goog.userAgentTestUtil', 'goog.userAgentTestUtil.UserAgents']);
-goog.addDependency('style/style_webkit_scrollbars_test.js', ['goog.style.webkitScrollbarsTest'], ['goog.asserts', 'goog.style', 'goog.styleScrollbarTester', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('style/stylescrollbartester.js', ['goog.styleScrollbarTester'], ['goog.dom', 'goog.style', 'goog.testing.asserts']);
-goog.addDependency('style/transform.js', ['goog.style.transform'], ['goog.functions', 'goog.math.Coordinate', 'goog.style', 'goog.userAgent', 'goog.userAgent.product.isVersion']);
-goog.addDependency('style/transform_test.js', ['goog.style.transformTest'], ['goog.dom', 'goog.style.transform', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product.isVersion']);
-goog.addDependency('style/transition.js', ['goog.style.transition', 'goog.style.transition.Css3Property'], ['goog.array', 'goog.asserts', 'goog.dom.safe', 'goog.dom.vendor', 'goog.functions', 'goog.html.SafeHtml', 'goog.style', 'goog.userAgent']);
-goog.addDependency('style/transition_test.js', ['goog.style.transitionTest'], ['goog.style', 'goog.style.transition', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('testing/asserts.js', ['goog.testing.JsUnitException', 'goog.testing.asserts'], ['goog.testing.stacktrace']);
-goog.addDependency('testing/asserts_test.js', ['goog.testing.assertsTest'], ['goog.array', 'goog.dom', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.structs.Map', 'goog.structs.Set', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('testing/async/mockcontrol.js', ['goog.testing.async.MockControl'], ['goog.asserts', 'goog.async.Deferred', 'goog.debug', 'goog.testing.asserts', 'goog.testing.mockmatchers.IgnoreArgument']);
-goog.addDependency('testing/async/mockcontrol_test.js', ['goog.testing.async.MockControlTest'], ['goog.async.Deferred', 'goog.testing.MockControl', 'goog.testing.asserts', 'goog.testing.async.MockControl', 'goog.testing.jsunit']);
-goog.addDependency('testing/asynctestcase.js', ['goog.testing.AsyncTestCase', 'goog.testing.AsyncTestCase.ControlBreakingException'], ['goog.testing.TestCase', 'goog.testing.TestCase.Test', 'goog.testing.asserts']);
-goog.addDependency('testing/asynctestcase_async_test.js', ['goog.testing.AsyncTestCaseAsyncTest'], ['goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('testing/asynctestcase_noasync_test.js', ['goog.testing.AsyncTestCaseSyncTest'], ['goog.testing.AsyncTestCase', 'goog.testing.jsunit']);
-goog.addDependency('testing/asynctestcase_test.js', ['goog.testing.AsyncTestCaseTest'], ['goog.debug.Error', 'goog.testing.AsyncTestCase', 'goog.testing.asserts', 'goog.testing.jsunit']);
-goog.addDependency('testing/benchmark.js', ['goog.testing.benchmark'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.PerformanceTable', 'goog.testing.PerformanceTimer', 'goog.testing.TestCase']);
-goog.addDependency('testing/continuationtestcase.js', ['goog.testing.ContinuationTestCase', 'goog.testing.ContinuationTestCase.Step', 'goog.testing.ContinuationTestCase.Test'], ['goog.array', 'goog.events.EventHandler', 'goog.testing.TestCase', 'goog.testing.TestCase.Test', 'goog.testing.asserts']);
-goog.addDependency('testing/continuationtestcase_test.js', ['goog.testing.ContinuationTestCaseTest'], ['goog.events', 'goog.events.EventTarget', 'goog.testing.ContinuationTestCase', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit']);
-goog.addDependency('testing/deferredtestcase.js', ['goog.testing.DeferredTestCase'], ['goog.async.Deferred', 'goog.testing.AsyncTestCase', 'goog.testing.TestCase']);
-goog.addDependency('testing/deferredtestcase_test.js', ['goog.testing.DeferredTestCaseTest'], ['goog.async.Deferred', 'goog.testing.DeferredTestCase', 'goog.testing.TestCase', 'goog.testing.TestRunner', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('testing/dom.js', ['goog.testing.dom'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeIterator', 'goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.iter', 'goog.object', 'goog.string', 'goog.style', 'goog.testing.asserts', 'goog.userAgent']);
-goog.addDependency('testing/dom_test.js', ['goog.testing.domTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('testing/editor/dom.js', ['goog.testing.editor.dom'], ['goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagWalkType', 'goog.iter', 'goog.string', 'goog.testing.asserts']);
-goog.addDependency('testing/editor/dom_test.js', ['goog.testing.editor.domTest'], ['goog.dom', 'goog.dom.TagName', 'goog.functions', 'goog.testing.editor.dom', 'goog.testing.jsunit']);
-goog.addDependency('testing/editor/fieldmock.js', ['goog.testing.editor.FieldMock'], ['goog.dom', 'goog.dom.Range', 'goog.editor.Field', 'goog.testing.LooseMock', 'goog.testing.mockmatchers']);
-goog.addDependency('testing/editor/testhelper.js', ['goog.testing.editor.TestHelper'], ['goog.Disposable', 'goog.dom', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.editor.node', 'goog.editor.plugins.AbstractBubblePlugin', 'goog.testing.dom']);
-goog.addDependency('testing/editor/testhelper_test.js', ['goog.testing.editor.TestHelperTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.node', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('testing/events/eventobserver.js', ['goog.testing.events.EventObserver'], ['goog.array']);
-goog.addDependency('testing/events/eventobserver_test.js', ['goog.testing.events.EventObserverTest'], ['goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.testing.events.EventObserver', 'goog.testing.jsunit']);
-goog.addDependency('testing/events/events.js', ['goog.testing.events', 'goog.testing.events.Event'], ['goog.Disposable', 'goog.asserts', 'goog.dom.NodeType', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.object', 'goog.style', 'goog.userAgent']);
-goog.addDependency('testing/events/events_test.js', ['goog.testing.eventsTest'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Coordinate', 'goog.string', 'goog.style', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']);
-goog.addDependency('testing/events/matchers.js', ['goog.testing.events.EventMatcher'], ['goog.events.Event', 'goog.testing.mockmatchers.ArgumentMatcher']);
-goog.addDependency('testing/events/matchers_test.js', ['goog.testing.events.EventMatcherTest'], ['goog.events.Event', 'goog.testing.events.EventMatcher', 'goog.testing.jsunit']);
-goog.addDependency('testing/events/onlinehandler.js', ['goog.testing.events.OnlineHandler'], ['goog.events.EventTarget', 'goog.net.NetworkStatusMonitor']);
-goog.addDependency('testing/events/onlinehandler_test.js', ['goog.testing.events.OnlineHandlerTest'], ['goog.events', 'goog.net.NetworkStatusMonitor', 'goog.testing.events.EventObserver', 'goog.testing.events.OnlineHandler', 'goog.testing.jsunit']);
-goog.addDependency('testing/expectedfailures.js', ['goog.testing.ExpectedFailures'], ['goog.debug.DivConsole', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.log', 'goog.style', 'goog.testing.JsUnitException', 'goog.testing.TestCase', 'goog.testing.asserts']);
-goog.addDependency('testing/expectedfailures_test.js', ['goog.testing.ExpectedFailuresTest'], ['goog.debug.Logger', 'goog.testing.ExpectedFailures', 'goog.testing.JsUnitException', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/blob.js', ['goog.testing.fs.Blob'], ['goog.crypt.base64']);
-goog.addDependency('testing/fs/blob_test.js', ['goog.testing.fs.BlobTest'], ['goog.testing.fs.Blob', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/directoryentry_test.js', ['goog.testing.fs.DirectoryEntryTest'], ['goog.array', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.testing.AsyncTestCase', 'goog.testing.MockClock', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/entry.js', ['goog.testing.fs.DirectoryEntry', 'goog.testing.fs.Entry', 'goog.testing.fs.FileEntry'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.fs.DirectoryEntry', 'goog.fs.DirectoryEntryImpl', 'goog.fs.Entry', 'goog.fs.Error', 'goog.fs.FileEntry', 'goog.functions', 'goog.object', 'goog.string', 'goog.testing.fs.File', 'goog.testing.fs.FileWriter']);
-goog.addDependency('testing/fs/entry_test.js', ['goog.testing.fs.EntryTest'], ['goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.testing.AsyncTestCase', 'goog.testing.MockClock', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/file.js', ['goog.testing.fs.File'], ['goog.testing.fs.Blob']);
-goog.addDependency('testing/fs/fileentry_test.js', ['goog.testing.fs.FileEntryTest'], ['goog.testing.AsyncTestCase', 'goog.testing.MockClock', 'goog.testing.fs.FileEntry', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/filereader.js', ['goog.testing.fs.FileReader'], ['goog.Timer', 'goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.testing.fs.ProgressEvent']);
-goog.addDependency('testing/fs/filereader_test.js', ['goog.testing.fs.FileReaderTest'], ['goog.Timer', 'goog.async.Deferred', 'goog.events', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.fs.FileSaver', 'goog.testing.AsyncTestCase', 'goog.testing.fs.FileReader', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/filesystem.js', ['goog.testing.fs.FileSystem'], ['goog.fs.FileSystem', 'goog.testing.fs.DirectoryEntry']);
-goog.addDependency('testing/fs/filewriter.js', ['goog.testing.fs.FileWriter'], ['goog.Timer', 'goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.string', 'goog.testing.fs.ProgressEvent']);
-goog.addDependency('testing/fs/filewriter_test.js', ['goog.testing.fs.FileWriterTest'], ['goog.async.Deferred', 'goog.events', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.testing.AsyncTestCase', 'goog.testing.MockClock', 'goog.testing.fs.Blob', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/fs.js', ['goog.testing.fs'], ['goog.Timer', 'goog.array', 'goog.async.Deferred', 'goog.fs', 'goog.testing.fs.Blob', 'goog.testing.fs.FileSystem']);
-goog.addDependency('testing/fs/fs_test.js', ['goog.testing.fsTest'], ['goog.testing.AsyncTestCase', 'goog.testing.fs', 'goog.testing.fs.Blob', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/integration_test.js', ['goog.testing.fs.integrationTest'], ['goog.async.Deferred', 'goog.async.DeferredList', 'goog.events', 'goog.fs', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.testing.AsyncTestCase', 'goog.testing.PropertyReplacer', 'goog.testing.fs', 'goog.testing.jsunit']);
-goog.addDependency('testing/fs/progressevent.js', ['goog.testing.fs.ProgressEvent'], ['goog.events.Event']);
-goog.addDependency('testing/functionmock.js', ['goog.testing', 'goog.testing.FunctionMock', 'goog.testing.GlobalFunctionMock', 'goog.testing.MethodMock'], ['goog.object', 'goog.testing.LooseMock', 'goog.testing.Mock', 'goog.testing.MockInterface', 'goog.testing.PropertyReplacer', 'goog.testing.StrictMock']);
-goog.addDependency('testing/functionmock_test.js', ['goog.testing.FunctionMockTest'], ['goog.array', 'goog.string', 'goog.testing', 'goog.testing.FunctionMock', 'goog.testing.Mock', 'goog.testing.StrictMock', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.mockmatchers']);
-goog.addDependency('testing/graphics.js', ['goog.testing.graphics'], ['goog.graphics.Path.Segment', 'goog.testing.asserts']);
-goog.addDependency('testing/i18n/asserts.js', ['goog.testing.i18n.asserts'], ['goog.testing.jsunit']);
-goog.addDependency('testing/i18n/asserts_test.js', ['goog.testing.i18n.assertsTest'], ['goog.testing.ExpectedFailures', 'goog.testing.i18n.asserts']);
-goog.addDependency('testing/jsunit.js', ['goog.testing.jsunit'], ['goog.testing.TestCase', 'goog.testing.TestRunner']);
-goog.addDependency('testing/loosemock.js', ['goog.testing.LooseExpectationCollection', 'goog.testing.LooseMock'], ['goog.array', 'goog.structs.Map', 'goog.testing.Mock']);
-goog.addDependency('testing/loosemock_test.js', ['goog.testing.LooseMockTest'], ['goog.testing.LooseMock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.mockmatchers']);
-goog.addDependency('testing/messaging/mockmessagechannel.js', ['goog.testing.messaging.MockMessageChannel'], ['goog.messaging.AbstractChannel', 'goog.testing.asserts']);
-goog.addDependency('testing/messaging/mockmessageevent.js', ['goog.testing.messaging.MockMessageEvent'], ['goog.events.BrowserEvent', 'goog.events.EventType', 'goog.testing.events']);
-goog.addDependency('testing/messaging/mockmessageport.js', ['goog.testing.messaging.MockMessagePort'], ['goog.events.EventTarget']);
-goog.addDependency('testing/messaging/mockportnetwork.js', ['goog.testing.messaging.MockPortNetwork'], ['goog.messaging.PortNetwork', 'goog.testing.messaging.MockMessageChannel']);
-goog.addDependency('testing/mock.js', ['goog.testing.Mock', 'goog.testing.MockExpectation'], ['goog.array', 'goog.object', 'goog.testing.JsUnitException', 'goog.testing.MockInterface', 'goog.testing.mockmatchers']);
-goog.addDependency('testing/mock_test.js', ['goog.testing.MockTest'], ['goog.array', 'goog.testing', 'goog.testing.Mock', 'goog.testing.MockControl', 'goog.testing.MockExpectation', 'goog.testing.jsunit']);
-goog.addDependency('testing/mockclassfactory.js', ['goog.testing.MockClassFactory', 'goog.testing.MockClassRecord'], ['goog.array', 'goog.object', 'goog.testing.LooseMock', 'goog.testing.StrictMock', 'goog.testing.TestCase', 'goog.testing.mockmatchers']);
-goog.addDependency('testing/mockclassfactory_test.js', ['fake.BaseClass', 'fake.ChildClass', 'goog.testing.MockClassFactoryTest'], ['goog.testing', 'goog.testing.MockClassFactory', 'goog.testing.jsunit']);
-goog.addDependency('testing/mockclock.js', ['goog.testing.MockClock'], ['goog.Disposable', 'goog.async.run', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.watchers']);
-goog.addDependency('testing/mockclock_test.js', ['goog.testing.MockClockTest'], ['goog.events', 'goog.functions', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
-goog.addDependency('testing/mockcontrol.js', ['goog.testing.MockControl'], ['goog.array', 'goog.testing', 'goog.testing.LooseMock', 'goog.testing.StrictMock']);
-goog.addDependency('testing/mockcontrol_test.js', ['goog.testing.MockControlTest'], ['goog.testing.Mock', 'goog.testing.MockControl', 'goog.testing.jsunit']);
-goog.addDependency('testing/mockinterface.js', ['goog.testing.MockInterface'], []);
-goog.addDependency('testing/mockmatchers.js', ['goog.testing.mockmatchers', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.testing.mockmatchers.IgnoreArgument', 'goog.testing.mockmatchers.InstanceOf', 'goog.testing.mockmatchers.ObjectEquals', 'goog.testing.mockmatchers.RegexpMatch', 'goog.testing.mockmatchers.SaveArgument', 'goog.testing.mockmatchers.TypeOf'], ['goog.array', 'goog.dom', 'goog.testing.asserts']);
-goog.addDependency('testing/mockmatchers_test.js', ['goog.testing.mockmatchersTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.testing.mockmatchers.ArgumentMatcher']);
-goog.addDependency('testing/mockrandom.js', ['goog.testing.MockRandom'], ['goog.Disposable']);
-goog.addDependency('testing/mockrandom_test.js', ['goog.testing.MockRandomTest'], ['goog.testing.MockRandom', 'goog.testing.jsunit']);
-goog.addDependency('testing/mockrange.js', ['goog.testing.MockRange'], ['goog.dom.AbstractRange', 'goog.testing.LooseMock']);
-goog.addDependency('testing/mockrange_test.js', ['goog.testing.MockRangeTest'], ['goog.testing.MockRange', 'goog.testing.jsunit']);
-goog.addDependency('testing/mockstorage.js', ['goog.testing.MockStorage'], ['goog.structs.Map']);
-goog.addDependency('testing/mockstorage_test.js', ['goog.testing.MockStorageTest'], ['goog.testing.MockStorage', 'goog.testing.jsunit']);
-goog.addDependency('testing/mockuseragent.js', ['goog.testing.MockUserAgent'], ['goog.Disposable', 'goog.labs.userAgent.util', 'goog.testing.PropertyReplacer', 'goog.userAgent']);
-goog.addDependency('testing/mockuseragent_test.js', ['goog.testing.MockUserAgentTest'], ['goog.dispose', 'goog.testing.MockUserAgent', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('testing/multitestrunner.js', ['goog.testing.MultiTestRunner', 'goog.testing.MultiTestRunner.TestFrame'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.functions', 'goog.string', 'goog.ui.Component', 'goog.ui.ServerChart', 'goog.ui.TableSorter']);
-goog.addDependency('testing/net/xhrio.js', ['goog.testing.net.XhrIo'], ['goog.array', 'goog.dom.xml', 'goog.events', 'goog.events.EventTarget', 'goog.json', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.HttpStatus', 'goog.net.XhrIo', 'goog.net.XmlHttp', 'goog.object', 'goog.structs.Map']);
-goog.addDependency('testing/net/xhrio_test.js', ['goog.testing.net.XhrIoTest'], ['goog.dom.xml', 'goog.events', 'goog.events.Event', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XmlHttp', 'goog.object', 'goog.testing.MockControl', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.mockmatchers.InstanceOf', 'goog.testing.net.XhrIo']);
-goog.addDependency('testing/net/xhriopool.js', ['goog.testing.net.XhrIoPool'], ['goog.net.XhrIoPool', 'goog.testing.net.XhrIo']);
-goog.addDependency('testing/objectpropertystring.js', ['goog.testing.ObjectPropertyString'], []);
-goog.addDependency('testing/performancetable.js', ['goog.testing.PerformanceTable'], ['goog.dom', 'goog.testing.PerformanceTimer']);
-goog.addDependency('testing/performancetimer.js', ['goog.testing.PerformanceTimer', 'goog.testing.PerformanceTimer.Task'], ['goog.array', 'goog.async.Deferred', 'goog.math']);
-goog.addDependency('testing/performancetimer_test.js', ['goog.testing.PerformanceTimerTest'], ['goog.async.Deferred', 'goog.dom', 'goog.math', 'goog.testing.MockClock', 'goog.testing.PerformanceTimer', 'goog.testing.jsunit']);
-goog.addDependency('testing/propertyreplacer.js', ['goog.testing.PropertyReplacer'], ['goog.testing.ObjectPropertyString', 'goog.userAgent']);
-goog.addDependency('testing/propertyreplacer_test.js', ['goog.testing.PropertyReplacerTest'], ['goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit']);
-goog.addDependency('testing/proto2/proto2.js', ['goog.testing.proto2'], ['goog.proto2.Message', 'goog.proto2.ObjectSerializer', 'goog.testing.asserts']);
-goog.addDependency('testing/proto2/proto2_test.js', ['goog.testing.proto2Test'], ['goog.testing.jsunit', 'goog.testing.proto2', 'proto2.TestAllTypes']);
-goog.addDependency('testing/pseudorandom.js', ['goog.testing.PseudoRandom'], ['goog.Disposable']);
-goog.addDependency('testing/pseudorandom_test.js', ['goog.testing.PseudoRandomTest'], ['goog.testing.PseudoRandom', 'goog.testing.jsunit']);
-goog.addDependency('testing/recordfunction.js', ['goog.testing.FunctionCall', 'goog.testing.recordConstructor', 'goog.testing.recordFunction'], ['goog.testing.asserts']);
-goog.addDependency('testing/recordfunction_test.js', ['goog.testing.recordFunctionTest'], ['goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordConstructor', 'goog.testing.recordFunction']);
-goog.addDependency('testing/shardingtestcase.js', ['goog.testing.ShardingTestCase'], ['goog.asserts', 'goog.testing.TestCase']);
-goog.addDependency('testing/shardingtestcase_test.js', ['goog.testing.ShardingTestCaseTest'], ['goog.testing.ShardingTestCase', 'goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit']);
-goog.addDependency('testing/singleton.js', ['goog.testing.singleton'], []);
-goog.addDependency('testing/singleton_test.js', ['goog.testing.singletonTest'], ['goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.singleton']);
-goog.addDependency('testing/stacktrace.js', ['goog.testing.stacktrace', 'goog.testing.stacktrace.Frame'], []);
-goog.addDependency('testing/stacktrace_test.js', ['goog.testing.stacktraceTest'], ['goog.functions', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.StrictMock', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.stacktrace', 'goog.testing.stacktrace.Frame', 'goog.userAgent']);
-goog.addDependency('testing/storage/fakemechanism.js', ['goog.testing.storage.FakeMechanism'], ['goog.storage.mechanism.IterableMechanism', 'goog.structs.Map']);
-goog.addDependency('testing/strictmock.js', ['goog.testing.StrictMock'], ['goog.array', 'goog.testing.Mock']);
-goog.addDependency('testing/strictmock_test.js', ['goog.testing.StrictMockTest'], ['goog.testing.StrictMock', 'goog.testing.jsunit']);
-goog.addDependency('testing/style/layoutasserts.js', ['goog.testing.style.layoutasserts'], ['goog.style', 'goog.testing.asserts', 'goog.testing.style']);
-goog.addDependency('testing/style/layoutasserts_test.js', ['goog.testing.style.layoutassertsTest'], ['goog.dom', 'goog.style', 'goog.testing.jsunit', 'goog.testing.style.layoutasserts']);
-goog.addDependency('testing/style/style.js', ['goog.testing.style'], ['goog.dom', 'goog.math.Rect', 'goog.style']);
-goog.addDependency('testing/style/style_test.js', ['goog.testing.styleTest'], ['goog.dom', 'goog.style', 'goog.testing.jsunit', 'goog.testing.style']);
-goog.addDependency('testing/testcase.js', ['goog.testing.TestCase', 'goog.testing.TestCase.Error', 'goog.testing.TestCase.Order', 'goog.testing.TestCase.Result', 'goog.testing.TestCase.Test'], ['goog.object', 'goog.testing.asserts', 'goog.testing.stacktrace']);
-goog.addDependency('testing/testqueue.js', ['goog.testing.TestQueue'], []);
-goog.addDependency('testing/testrunner.js', ['goog.testing.TestRunner'], ['goog.testing.TestCase']);
-goog.addDependency('testing/ui/rendererasserts.js', ['goog.testing.ui.rendererasserts'], ['goog.testing.asserts']);
-goog.addDependency('testing/ui/rendererasserts_test.js', ['goog.testing.ui.rendererassertsTest'], ['goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.ControlRenderer']);
-goog.addDependency('testing/ui/rendererharness.js', ['goog.testing.ui.RendererHarness'], ['goog.Disposable', 'goog.dom.NodeType', 'goog.testing.asserts', 'goog.testing.dom']);
-goog.addDependency('testing/ui/style.js', ['goog.testing.ui.style'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.testing.asserts']);
-goog.addDependency('testing/ui/style_test.js', ['goog.testing.ui.styleTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.testing.ui.style']);
-goog.addDependency('testing/watchers.js', ['goog.testing.watchers'], []);
-goog.addDependency('timer/timer.js', ['goog.Timer'], ['goog.events.EventTarget']);
-goog.addDependency('timer/timer_test.js', ['goog.TimerTest'], ['goog.Timer', 'goog.events', 'goog.testing.MockClock', 'goog.testing.jsunit']);
-goog.addDependency('tweak/entries.js', ['goog.tweak.BaseEntry', 'goog.tweak.BasePrimitiveSetting', 'goog.tweak.BaseSetting', 'goog.tweak.BooleanGroup', 'goog.tweak.BooleanInGroupSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.ButtonAction', 'goog.tweak.NumericSetting', 'goog.tweak.StringSetting'], ['goog.array', 'goog.asserts', 'goog.log', 'goog.object']);
-goog.addDependency('tweak/entries_test.js', ['goog.tweak.BaseEntryTest'], ['goog.testing.MockControl', 'goog.testing.jsunit', 'goog.tweak.testhelpers']);
-goog.addDependency('tweak/registry.js', ['goog.tweak.Registry'], ['goog.asserts', 'goog.log', 'goog.object', 'goog.string', 'goog.tweak.BaseEntry', 'goog.uri.utils']);
-goog.addDependency('tweak/registry_test.js', ['goog.tweak.RegistryTest'], ['goog.asserts.AssertionError', 'goog.testing.jsunit', 'goog.tweak', 'goog.tweak.testhelpers']);
-goog.addDependency('tweak/testhelpers.js', ['goog.tweak.testhelpers'], ['goog.tweak', 'goog.tweak.BooleanGroup', 'goog.tweak.BooleanInGroupSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.ButtonAction', 'goog.tweak.NumericSetting', 'goog.tweak.Registry', 'goog.tweak.StringSetting']);
-goog.addDependency('tweak/tweak.js', ['goog.tweak', 'goog.tweak.ConfigParams'], ['goog.asserts', 'goog.tweak.BaseSetting', 'goog.tweak.BooleanGroup', 'goog.tweak.BooleanInGroupSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.ButtonAction', 'goog.tweak.NumericSetting', 'goog.tweak.Registry', 'goog.tweak.StringSetting']);
-goog.addDependency('tweak/tweakui.js', ['goog.tweak.EntriesPanel', 'goog.tweak.TweakUi'], ['goog.array', 'goog.asserts', 'goog.dom.DomHelper', 'goog.object', 'goog.style', 'goog.tweak', 'goog.ui.Zippy', 'goog.userAgent']);
-goog.addDependency('tweak/tweakui_test.js', ['goog.tweak.TweakUiTest'], ['goog.dom', 'goog.string', 'goog.testing.jsunit', 'goog.tweak', 'goog.tweak.TweakUi', 'goog.tweak.testhelpers']);
-goog.addDependency('ui/abstractspellchecker.js', ['goog.ui.AbstractSpellChecker', 'goog.ui.AbstractSpellChecker.AsyncResult'], ['goog.a11y.aria', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.dom.selection', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.spell.SpellCheck', 'goog.structs.Set', 'goog.style', 'goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.MenuSeparator', 'goog.ui.PopupMenu']);
-goog.addDependency('ui/ac/ac.js', ['goog.ui.ac'], ['goog.ui.ac.ArrayMatcher', 'goog.ui.ac.AutoComplete', 'goog.ui.ac.InputHandler', 'goog.ui.ac.Renderer']);
-goog.addDependency('ui/ac/arraymatcher.js', ['goog.ui.ac.ArrayMatcher'], ['goog.string']);
-goog.addDependency('ui/ac/autocomplete.js', ['goog.ui.ac.AutoComplete', 'goog.ui.ac.AutoComplete.EventType'], ['goog.array', 'goog.asserts', 'goog.events', 'goog.events.EventTarget', 'goog.object']);
-goog.addDependency('ui/ac/cachingmatcher.js', ['goog.ui.ac.CachingMatcher'], ['goog.array', 'goog.async.Throttle', 'goog.ui.ac.ArrayMatcher', 'goog.ui.ac.RenderOptions']);
-goog.addDependency('ui/ac/inputhandler.js', ['goog.ui.ac.InputHandler'], ['goog.Disposable', 'goog.Timer', 'goog.a11y.aria', 'goog.dom', 'goog.dom.selection', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.string', 'goog.userAgent', 'goog.userAgent.product']);
-goog.addDependency('ui/ac/remote.js', ['goog.ui.ac.Remote'], ['goog.ui.ac.AutoComplete', 'goog.ui.ac.InputHandler', 'goog.ui.ac.RemoteArrayMatcher', 'goog.ui.ac.Renderer']);
-goog.addDependency('ui/ac/remotearraymatcher.js', ['goog.ui.ac.RemoteArrayMatcher'], ['goog.Disposable', 'goog.Uri', 'goog.events', 'goog.json', 'goog.net.EventType', 'goog.net.XhrIo']);
-goog.addDependency('ui/ac/renderer.js', ['goog.ui.ac.Renderer', 'goog.ui.ac.Renderer.CustomRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dispose', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.fx.dom.FadeInAndShow', 'goog.fx.dom.FadeOutAndHide', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.string', 'goog.style', 'goog.ui.IdGenerator', 'goog.ui.ac.AutoComplete']);
-goog.addDependency('ui/ac/renderoptions.js', ['goog.ui.ac.RenderOptions'], []);
-goog.addDependency('ui/ac/richinputhandler.js', ['goog.ui.ac.RichInputHandler'], ['goog.ui.ac.InputHandler']);
-goog.addDependency('ui/ac/richremote.js', ['goog.ui.ac.RichRemote'], ['goog.ui.ac.AutoComplete', 'goog.ui.ac.Remote', 'goog.ui.ac.Renderer', 'goog.ui.ac.RichInputHandler', 'goog.ui.ac.RichRemoteArrayMatcher']);
-goog.addDependency('ui/ac/richremotearraymatcher.js', ['goog.ui.ac.RichRemoteArrayMatcher'], ['goog.json', 'goog.ui.ac.RemoteArrayMatcher']);
-goog.addDependency('ui/activitymonitor.js', ['goog.ui.ActivityMonitor'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType']);
-goog.addDependency('ui/advancedtooltip.js', ['goog.ui.AdvancedTooltip'], ['goog.events', 'goog.events.EventType', 'goog.math.Box', 'goog.math.Coordinate', 'goog.style', 'goog.ui.Tooltip', 'goog.userAgent']);
-goog.addDependency('ui/animatedzippy.js', ['goog.ui.AnimatedZippy'], ['goog.dom', 'goog.events', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.fx.easing', 'goog.ui.Zippy', 'goog.ui.ZippyEvent']);
-goog.addDependency('ui/attachablemenu.js', ['goog.ui.AttachableMenu'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.Event', 'goog.events.KeyCodes', 'goog.string', 'goog.style', 'goog.ui.ItemEvent', 'goog.ui.MenuBase', 'goog.ui.PopupBase', 'goog.userAgent']);
-goog.addDependency('ui/bidiinput.js', ['goog.ui.BidiInput'], ['goog.dom', 'goog.events', 'goog.events.InputHandler', 'goog.i18n.bidi', 'goog.ui.Component']);
-goog.addDependency('ui/bubble.js', ['goog.ui.Bubble'], ['goog.Timer', 'goog.events', 'goog.events.EventType', 'goog.math.Box', 'goog.positioning', 'goog.positioning.AbsolutePosition', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.CornerBit', 'goog.style', 'goog.ui.Component', 'goog.ui.Popup']);
-goog.addDependency('ui/button.js', ['goog.ui.Button', 'goog.ui.Button.Side'], ['goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.ui.ButtonRenderer', 'goog.ui.ButtonSide', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.NativeButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/buttonrenderer.js', ['goog.ui.ButtonRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.ui.ButtonSide', 'goog.ui.Component', 'goog.ui.ControlRenderer']);
-goog.addDependency('ui/buttonside.js', ['goog.ui.ButtonSide'], []);
-goog.addDependency('ui/charcounter.js', ['goog.ui.CharCounter', 'goog.ui.CharCounter.Display'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.InputHandler']);
-goog.addDependency('ui/charpicker.js', ['goog.ui.CharPicker'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.i18n.CharListDecompressor', 'goog.i18n.uChar', 'goog.structs.Set', 'goog.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.ContainerScroller', 'goog.ui.FlatButtonRenderer', 'goog.ui.HoverCard', 'goog.ui.LabelInput', 'goog.ui.Menu', 'goog.ui.MenuButton', 'goog.ui.MenuItem', 'goog.ui.Tooltip']);
-goog.addDependency('ui/checkbox.js', ['goog.ui.Checkbox', 'goog.ui.Checkbox.State'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.ui.CheckboxRenderer', 'goog.ui.Component.EventType', 'goog.ui.Component.State', 'goog.ui.Control', 'goog.ui.registry']);
-goog.addDependency('ui/checkboxmenuitem.js', ['goog.ui.CheckBoxMenuItem'], ['goog.ui.MenuItem', 'goog.ui.registry']);
-goog.addDependency('ui/checkboxrenderer.js', ['goog.ui.CheckboxRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom.classlist', 'goog.object', 'goog.ui.ControlRenderer']);
-goog.addDependency('ui/colorbutton.js', ['goog.ui.ColorButton'], ['goog.ui.Button', 'goog.ui.ColorButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/colorbuttonrenderer.js', ['goog.ui.ColorButtonRenderer'], ['goog.asserts', 'goog.dom.classlist', 'goog.functions', 'goog.ui.ColorMenuButtonRenderer']);
-goog.addDependency('ui/colormenubutton.js', ['goog.ui.ColorMenuButton'], ['goog.array', 'goog.object', 'goog.ui.ColorMenuButtonRenderer', 'goog.ui.ColorPalette', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.MenuButton', 'goog.ui.registry']);
-goog.addDependency('ui/colormenubuttonrenderer.js', ['goog.ui.ColorMenuButtonRenderer'], ['goog.asserts', 'goog.color', 'goog.dom.classlist', 'goog.ui.MenuButtonRenderer', 'goog.userAgent']);
-goog.addDependency('ui/colorpalette.js', ['goog.ui.ColorPalette'], ['goog.array', 'goog.color', 'goog.style', 'goog.ui.Palette', 'goog.ui.PaletteRenderer']);
-goog.addDependency('ui/colorpicker.js', ['goog.ui.ColorPicker', 'goog.ui.ColorPicker.EventType'], ['goog.ui.ColorPalette', 'goog.ui.Component']);
-goog.addDependency('ui/colorsplitbehavior.js', ['goog.ui.ColorSplitBehavior'], ['goog.ui.ColorMenuButton', 'goog.ui.SplitBehavior']);
-goog.addDependency('ui/combobox.js', ['goog.ui.ComboBox', 'goog.ui.ComboBoxItem'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.log', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.ItemEvent', 'goog.ui.LabelInput', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.ui.MenuSeparator', 'goog.ui.registry', 'goog.userAgent']);
-goog.addDependency('ui/component.js', ['goog.ui.Component', 'goog.ui.Component.Error', 'goog.ui.Component.EventType', 'goog.ui.Component.State'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.object', 'goog.style', 'goog.ui.IdGenerator']);
-goog.addDependency('ui/container.js', ['goog.ui.Container', 'goog.ui.Container.EventType', 'goog.ui.Container.Orientation'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.object', 'goog.style', 'goog.ui.Component', 'goog.ui.ContainerRenderer', 'goog.ui.Control']);
-goog.addDependency('ui/containerrenderer.js', ['goog.ui.ContainerRenderer'], ['goog.a11y.aria', 'goog.array', 'goog.asserts', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.string', 'goog.style', 'goog.ui.registry', 'goog.userAgent']);
-goog.addDependency('ui/containerscroller.js', ['goog.ui.ContainerScroller'], ['goog.Disposable', 'goog.Timer', 'goog.events.EventHandler', 'goog.style', 'goog.ui.Component', 'goog.ui.Container']);
-goog.addDependency('ui/control.js', ['goog.ui.Control'], ['goog.array', 'goog.dom', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.string', 'goog.ui.Component', 'goog.ui.ControlContent', 'goog.ui.ControlRenderer', 'goog.ui.decorate', 'goog.ui.registry', 'goog.userAgent']);
-goog.addDependency('ui/controlcontent.js', ['goog.ui.ControlContent'], []);
-goog.addDependency('ui/controlrenderer.js', ['goog.ui.ControlRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.object', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.userAgent']);
-goog.addDependency('ui/cookieeditor.js', ['goog.ui.CookieEditor'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.events.EventType', 'goog.net.cookies', 'goog.string', 'goog.style', 'goog.ui.Component']);
-goog.addDependency('ui/css3buttonrenderer.js', ['goog.ui.Css3ButtonRenderer'], ['goog.asserts', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.Button', 'goog.ui.ButtonRenderer', 'goog.ui.Component', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']);
-goog.addDependency('ui/css3menubuttonrenderer.js', ['goog.ui.Css3MenuButtonRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.MenuButton', 'goog.ui.MenuButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/cssnames.js', ['goog.ui.INLINE_BLOCK_CLASSNAME'], []);
-goog.addDependency('ui/custombutton.js', ['goog.ui.CustomButton'], ['goog.ui.Button', 'goog.ui.CustomButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/custombuttonrenderer.js', ['goog.ui.CustomButtonRenderer'], ['goog.a11y.aria.Role', 'goog.asserts', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.string', 'goog.ui.ButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME']);
-goog.addDependency('ui/customcolorpalette.js', ['goog.ui.CustomColorPalette'], ['goog.color', 'goog.dom', 'goog.dom.classlist', 'goog.ui.ColorPalette', 'goog.ui.Component']);
-goog.addDependency('ui/datepicker.js', ['goog.ui.DatePicker', 'goog.ui.DatePicker.Events', 'goog.ui.DatePickerEvent'], ['goog.a11y.aria', 'goog.asserts', 'goog.date.Date', 'goog.date.DateRange', 'goog.date.Interval', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyHandler', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimePatterns', 'goog.i18n.DateTimeSymbols', 'goog.style', 'goog.ui.Component', 'goog.ui.DefaultDatePickerRenderer', 'goog.ui.IdGenerator']);
-goog.addDependency('ui/datepickerrenderer.js', ['goog.ui.DatePickerRenderer'], []);
-goog.addDependency('ui/decorate.js', ['goog.ui.decorate'], ['goog.ui.registry']);
-goog.addDependency('ui/defaultdatepickerrenderer.js', ['goog.ui.DefaultDatePickerRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.ui.DatePickerRenderer']);
-goog.addDependency('ui/dialog.js', ['goog.ui.Dialog', 'goog.ui.Dialog.ButtonSet', 'goog.ui.Dialog.ButtonSet.DefaultButtons', 'goog.ui.Dialog.DefaultButtonCaptions', 'goog.ui.Dialog.DefaultButtonKeys', 'goog.ui.Dialog.Event', 'goog.ui.Dialog.EventType'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.dom.safe', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.fx.Dragger', 'goog.html.SafeHtml', 'goog.html.legacyconversions', 'goog.math.Rect', 'goog.string', 'goog.structs.Map', 'goog.style', 'goog.ui.ModalPopup']);
-goog.addDependency('ui/dimensionpicker.js', ['goog.ui.DimensionPicker'], ['goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Size', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.DimensionPickerRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/dimensionpickerrenderer.js', ['goog.ui.DimensionPickerRenderer'], ['goog.a11y.aria.Announcer', 'goog.a11y.aria.LivePriority', 'goog.dom', 'goog.dom.TagName', 'goog.i18n.bidi', 'goog.style', 'goog.ui.ControlRenderer', 'goog.userAgent']);
-goog.addDependency('ui/dragdropdetector.js', ['goog.ui.DragDropDetector', 'goog.ui.DragDropDetector.EventType', 'goog.ui.DragDropDetector.ImageDropEvent', 'goog.ui.DragDropDetector.LinkDropEvent'], ['goog.dom', 'goog.dom.TagName', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.string', 'goog.style', 'goog.userAgent']);
-goog.addDependency('ui/drilldownrow.js', ['goog.ui.DrilldownRow'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.ui.Component']);
-goog.addDependency('ui/editor/abstractdialog.js', ['goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder', 'goog.ui.editor.AbstractDialog.EventType'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventTarget', 'goog.string', 'goog.ui.Dialog']);
-goog.addDependency('ui/editor/bubble.js', ['goog.ui.editor.Bubble'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.ViewportSizeMonitor', 'goog.dom.classlist', 'goog.editor.style', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.functions', 'goog.log', 'goog.math.Box', 'goog.object', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.PopupBase', 'goog.userAgent']);
-goog.addDependency('ui/editor/defaulttoolbar.js', ['goog.ui.editor.ButtonDescriptor', 'goog.ui.editor.DefaultToolbar'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.editor.Command', 'goog.style', 'goog.ui.editor.ToolbarFactory', 'goog.ui.editor.messages', 'goog.userAgent']);
-goog.addDependency('ui/editor/equationeditordialog.js', ['goog.ui.editor.EquationEditorDialog'], ['goog.editor.Command', 'goog.ui.Dialog', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.EquationEditorOkEvent', 'goog.ui.equation.TexEditor']);
-goog.addDependency('ui/editor/equationeditorokevent.js', ['goog.ui.editor.EquationEditorOkEvent'], ['goog.events.Event', 'goog.ui.editor.AbstractDialog']);
-goog.addDependency('ui/editor/linkdialog.js', ['goog.ui.editor.LinkDialog', 'goog.ui.editor.LinkDialog.BeforeTestLinkEvent', 'goog.ui.editor.LinkDialog.EventType', 'goog.ui.editor.LinkDialog.OkEvent'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.editor.BrowserFeature', 'goog.editor.Link', 'goog.editor.focus', 'goog.editor.node', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.html.SafeHtml', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.LinkButtonRenderer', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.TabPane', 'goog.ui.editor.messages', 'goog.userAgent', 'goog.window']);
-goog.addDependency('ui/editor/messages.js', ['goog.ui.editor.messages'], ['goog.html.uncheckedconversions', 'goog.string.Const']);
-goog.addDependency('ui/editor/tabpane.js', ['goog.ui.editor.TabPane'], ['goog.asserts', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.Tab', 'goog.ui.TabBar']);
-goog.addDependency('ui/editor/toolbarcontroller.js', ['goog.ui.editor.ToolbarController'], ['goog.editor.Field', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.ui.Component']);
-goog.addDependency('ui/editor/toolbarfactory.js', ['goog.ui.editor.ToolbarFactory'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.ui.Component', 'goog.ui.Container', 'goog.ui.Option', 'goog.ui.Toolbar', 'goog.ui.ToolbarButton', 'goog.ui.ToolbarColorMenuButton', 'goog.ui.ToolbarMenuButton', 'goog.ui.ToolbarRenderer', 'goog.ui.ToolbarSelect', 'goog.userAgent']);
-goog.addDependency('ui/emoji/emoji.js', ['goog.ui.emoji.Emoji'], []);
-goog.addDependency('ui/emoji/emojipalette.js', ['goog.ui.emoji.EmojiPalette'], ['goog.events.EventType', 'goog.net.ImageLoader', 'goog.ui.Palette', 'goog.ui.emoji.Emoji', 'goog.ui.emoji.EmojiPaletteRenderer']);
-goog.addDependency('ui/emoji/emojipaletterenderer.js', ['goog.ui.emoji.EmojiPaletteRenderer'], ['goog.a11y.aria', 'goog.asserts', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.style', 'goog.ui.PaletteRenderer', 'goog.ui.emoji.Emoji']);
-goog.addDependency('ui/emoji/emojipicker.js', ['goog.ui.emoji.EmojiPicker'], ['goog.log', 'goog.style', 'goog.ui.Component', 'goog.ui.TabPane', 'goog.ui.emoji.Emoji', 'goog.ui.emoji.EmojiPalette', 'goog.ui.emoji.EmojiPaletteRenderer', 'goog.ui.emoji.ProgressiveEmojiPaletteRenderer']);
-goog.addDependency('ui/emoji/popupemojipicker.js', ['goog.ui.emoji.PopupEmojiPicker'], ['goog.events.EventType', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.ui.Component', 'goog.ui.Popup', 'goog.ui.emoji.EmojiPicker']);
-goog.addDependency('ui/emoji/progressiveemojipaletterenderer.js', ['goog.ui.emoji.ProgressiveEmojiPaletteRenderer'], ['goog.style', 'goog.ui.emoji.EmojiPaletteRenderer']);
-goog.addDependency('ui/emoji/spriteinfo.js', ['goog.ui.emoji.SpriteInfo'], []);
-goog.addDependency('ui/equation/arrowpalette.js', ['goog.ui.equation.ArrowPalette'], ['goog.math.Size', 'goog.ui.equation.Palette']);
-goog.addDependency('ui/equation/changeevent.js', ['goog.ui.equation.ChangeEvent'], ['goog.events.Event']);
-goog.addDependency('ui/equation/comparisonpalette.js', ['goog.ui.equation.ComparisonPalette'], ['goog.math.Size', 'goog.ui.equation.Palette']);
-goog.addDependency('ui/equation/editorpane.js', ['goog.ui.equation.EditorPane'], ['goog.style', 'goog.ui.Component']);
-goog.addDependency('ui/equation/equationeditor.js', ['goog.ui.equation.EquationEditor'], ['goog.events', 'goog.ui.Component', 'goog.ui.TabBar', 'goog.ui.equation.ImageRenderer', 'goog.ui.equation.TexPane']);
-goog.addDependency('ui/equation/equationeditordialog.js', ['goog.ui.equation.EquationEditorDialog'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.ui.Dialog', 'goog.ui.equation.EquationEditor', 'goog.ui.equation.PaletteManager', 'goog.ui.equation.TexEditor']);
-goog.addDependency('ui/equation/greekpalette.js', ['goog.ui.equation.GreekPalette'], ['goog.math.Size', 'goog.ui.equation.Palette']);
-goog.addDependency('ui/equation/imagerenderer.js', ['goog.ui.equation.ImageRenderer'], ['goog.asserts', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.string', 'goog.uri.utils']);
-goog.addDependency('ui/equation/mathpalette.js', ['goog.ui.equation.MathPalette'], ['goog.math.Size', 'goog.ui.equation.Palette']);
-goog.addDependency('ui/equation/menupalette.js', ['goog.ui.equation.MenuPalette', 'goog.ui.equation.MenuPaletteRenderer'], ['goog.math.Size', 'goog.ui.PaletteRenderer', 'goog.ui.equation.Palette', 'goog.ui.equation.PaletteRenderer']);
-goog.addDependency('ui/equation/palette.js', ['goog.ui.equation.Palette', 'goog.ui.equation.PaletteEvent', 'goog.ui.equation.PaletteRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.events.Event', 'goog.ui.Palette', 'goog.ui.PaletteRenderer']);
-goog.addDependency('ui/equation/palettemanager.js', ['goog.ui.equation.PaletteManager'], ['goog.Timer', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.ui.equation.ArrowPalette', 'goog.ui.equation.ComparisonPalette', 'goog.ui.equation.GreekPalette', 'goog.ui.equation.MathPalette', 'goog.ui.equation.MenuPalette', 'goog.ui.equation.Palette', 'goog.ui.equation.SymbolPalette']);
-goog.addDependency('ui/equation/symbolpalette.js', ['goog.ui.equation.SymbolPalette'], ['goog.math.Size', 'goog.ui.equation.Palette']);
-goog.addDependency('ui/equation/texeditor.js', ['goog.ui.equation.TexEditor'], ['goog.ui.Component', 'goog.ui.equation.ImageRenderer', 'goog.ui.equation.TexPane']);
-goog.addDependency('ui/equation/texpane.js', ['goog.ui.equation.TexPane'], ['goog.Timer', 'goog.dom', 'goog.dom.TagName', 'goog.dom.selection', 'goog.events', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.style', 'goog.ui.equation.ChangeEvent', 'goog.ui.equation.EditorPane', 'goog.ui.equation.ImageRenderer', 'goog.ui.equation.Palette', 'goog.ui.equation.PaletteEvent']);
-goog.addDependency('ui/filteredmenu.js', ['goog.ui.FilteredMenu'], ['goog.a11y.aria', 'goog.a11y.aria.AutoCompleteValues', 'goog.a11y.aria.State', 'goog.dom', 'goog.events', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.object', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.FilterObservingMenuItem', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']);
-goog.addDependency('ui/filterobservingmenuitem.js', ['goog.ui.FilterObservingMenuItem'], ['goog.ui.FilterObservingMenuItemRenderer', 'goog.ui.MenuItem', 'goog.ui.registry']);
-goog.addDependency('ui/filterobservingmenuitemrenderer.js', ['goog.ui.FilterObservingMenuItemRenderer'], ['goog.ui.MenuItemRenderer']);
-goog.addDependency('ui/flatbuttonrenderer.js', ['goog.ui.FlatButtonRenderer'], ['goog.a11y.aria.Role', 'goog.asserts', 'goog.dom.classlist', 'goog.ui.Button', 'goog.ui.ButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']);
-goog.addDependency('ui/flatmenubuttonrenderer.js', ['goog.ui.FlatMenuButtonRenderer'], ['goog.dom', 'goog.style', 'goog.ui.FlatButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.Menu', 'goog.ui.MenuButton', 'goog.ui.MenuRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/formpost.js', ['goog.ui.FormPost'], ['goog.array', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.ui.Component']);
-goog.addDependency('ui/gauge.js', ['goog.ui.Gauge', 'goog.ui.GaugeColoredRange'], ['goog.a11y.aria', 'goog.asserts', 'goog.events', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.fx.easing', 'goog.graphics', 'goog.graphics.Font', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.math', 'goog.ui.Component', 'goog.ui.GaugeTheme']);
-goog.addDependency('ui/gaugetheme.js', ['goog.ui.GaugeTheme'], ['goog.graphics.LinearGradient', 'goog.graphics.SolidFill', 'goog.graphics.Stroke']);
-goog.addDependency('ui/hovercard.js', ['goog.ui.HoverCard', 'goog.ui.HoverCard.EventType', 'goog.ui.HoverCard.TriggerEvent'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.ui.AdvancedTooltip', 'goog.ui.PopupBase', 'goog.ui.Tooltip']);
-goog.addDependency('ui/hsvapalette.js', ['goog.ui.HsvaPalette'], ['goog.array', 'goog.color.alpha', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.ui.HsvPalette']);
-goog.addDependency('ui/hsvpalette.js', ['goog.ui.HsvPalette'], ['goog.color', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.style', 'goog.style.bidi', 'goog.ui.Component', 'goog.userAgent']);
-goog.addDependency('ui/idgenerator.js', ['goog.ui.IdGenerator'], []);
-goog.addDependency('ui/idletimer.js', ['goog.ui.IdleTimer'], ['goog.Timer', 'goog.events', 'goog.events.EventTarget', 'goog.structs.Set', 'goog.ui.ActivityMonitor']);
-goog.addDependency('ui/iframemask.js', ['goog.ui.IframeMask'], ['goog.Disposable', 'goog.Timer', 'goog.dom', 'goog.dom.iframe', 'goog.events.EventHandler', 'goog.style']);
-goog.addDependency('ui/imagelessbuttonrenderer.js', ['goog.ui.ImagelessButtonRenderer'], ['goog.dom.classlist', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.CustomButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']);
-goog.addDependency('ui/imagelessmenubuttonrenderer.js', ['goog.ui.ImagelessMenuButtonRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.MenuButton', 'goog.ui.MenuButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/inputdatepicker.js', ['goog.ui.InputDatePicker'], ['goog.date.DateTime', 'goog.dom', 'goog.string', 'goog.ui.Component', 'goog.ui.DatePicker', 'goog.ui.PopupBase', 'goog.ui.PopupDatePicker']);
-goog.addDependency('ui/itemevent.js', ['goog.ui.ItemEvent'], ['goog.events.Event']);
-goog.addDependency('ui/keyboardshortcuthandler.js', ['goog.ui.KeyboardShortcutEvent', 'goog.ui.KeyboardShortcutHandler', 'goog.ui.KeyboardShortcutHandler.EventType'], ['goog.Timer', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyNames', 'goog.object', 'goog.userAgent']);
-goog.addDependency('ui/labelinput.js', ['goog.ui.LabelInput'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.ui.Component', 'goog.userAgent']);
-goog.addDependency('ui/linkbuttonrenderer.js', ['goog.ui.LinkButtonRenderer'], ['goog.ui.Button', 'goog.ui.FlatButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/media/flashobject.js', ['goog.ui.media.FlashObject', 'goog.ui.media.FlashObject.ScriptAccessLevel', 'goog.ui.media.FlashObject.Wmodes'], ['goog.asserts', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.log', 'goog.object', 'goog.string', 'goog.structs.Map', 'goog.style', 'goog.ui.Component', 'goog.userAgent', 'goog.userAgent.flash']);
-goog.addDependency('ui/media/flickr.js', ['goog.ui.media.FlickrSet', 'goog.ui.media.FlickrSetModel'], ['goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']);
-goog.addDependency('ui/media/googlevideo.js', ['goog.ui.media.GoogleVideo', 'goog.ui.media.GoogleVideoModel'], ['goog.string', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']);
-goog.addDependency('ui/media/media.js', ['goog.ui.media.Media', 'goog.ui.media.MediaRenderer'], ['goog.style', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.ControlRenderer']);
-goog.addDependency('ui/media/mediamodel.js', ['goog.ui.media.MediaModel', 'goog.ui.media.MediaModel.Category', 'goog.ui.media.MediaModel.Credit', 'goog.ui.media.MediaModel.Credit.Role', 'goog.ui.media.MediaModel.Credit.Scheme', 'goog.ui.media.MediaModel.Medium', 'goog.ui.media.MediaModel.MimeType', 'goog.ui.media.MediaModel.Player', 'goog.ui.media.MediaModel.SubTitle', 'goog.ui.media.MediaModel.Thumbnail'], ['goog.array']);
-goog.addDependency('ui/media/mp3.js', ['goog.ui.media.Mp3'], ['goog.string', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaRenderer']);
-goog.addDependency('ui/media/photo.js', ['goog.ui.media.Photo'], ['goog.ui.media.Media', 'goog.ui.media.MediaRenderer']);
-goog.addDependency('ui/media/picasa.js', ['goog.ui.media.PicasaAlbum', 'goog.ui.media.PicasaAlbumModel'], ['goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']);
-goog.addDependency('ui/media/vimeo.js', ['goog.ui.media.Vimeo', 'goog.ui.media.VimeoModel'], ['goog.string', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']);
-goog.addDependency('ui/media/youtube.js', ['goog.ui.media.Youtube', 'goog.ui.media.YoutubeModel'], ['goog.string', 'goog.ui.Component', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']);
-goog.addDependency('ui/menu.js', ['goog.ui.Menu', 'goog.ui.Menu.EventType'], ['goog.math.Coordinate', 'goog.string', 'goog.style', 'goog.ui.Component.EventType', 'goog.ui.Component.State', 'goog.ui.Container', 'goog.ui.Container.Orientation', 'goog.ui.MenuHeader', 'goog.ui.MenuItem', 'goog.ui.MenuRenderer', 'goog.ui.MenuSeparator']);
-goog.addDependency('ui/menubar.js', ['goog.ui.menuBar'], ['goog.ui.Container', 'goog.ui.MenuBarRenderer']);
-goog.addDependency('ui/menubardecorator.js', ['goog.ui.menuBarDecorator'], ['goog.ui.MenuBarRenderer', 'goog.ui.menuBar', 'goog.ui.registry']);
-goog.addDependency('ui/menubarrenderer.js', ['goog.ui.MenuBarRenderer'], ['goog.a11y.aria.Role', 'goog.ui.Container', 'goog.ui.ContainerRenderer']);
-goog.addDependency('ui/menubase.js', ['goog.ui.MenuBase'], ['goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyHandler', 'goog.ui.Popup']);
-goog.addDependency('ui/menubutton.js', ['goog.ui.MenuButton'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.math.Box', 'goog.math.Rect', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.positioning.Overflow', 'goog.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.IdGenerator', 'goog.ui.Menu', 'goog.ui.MenuButtonRenderer', 'goog.ui.MenuRenderer', 'goog.ui.registry', 'goog.userAgent', 'goog.userAgent.product']);
-goog.addDependency('ui/menubuttonrenderer.js', ['goog.ui.MenuButtonRenderer'], ['goog.dom', 'goog.style', 'goog.ui.CustomButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.Menu', 'goog.ui.MenuRenderer']);
-goog.addDependency('ui/menuheader.js', ['goog.ui.MenuHeader'], ['goog.ui.Component', 'goog.ui.Control', 'goog.ui.MenuHeaderRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/menuheaderrenderer.js', ['goog.ui.MenuHeaderRenderer'], ['goog.ui.ControlRenderer']);
-goog.addDependency('ui/menuitem.js', ['goog.ui.MenuItem'], ['goog.a11y.aria.Role', 'goog.array', 'goog.dom', 'goog.dom.classlist', 'goog.math.Coordinate', 'goog.string', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.MenuItemRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/menuitemrenderer.js', ['goog.ui.MenuItemRenderer'], ['goog.a11y.aria.Role', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.ui.Component', 'goog.ui.ControlRenderer']);
-goog.addDependency('ui/menurenderer.js', ['goog.ui.MenuRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.ui.ContainerRenderer', 'goog.ui.Separator']);
-goog.addDependency('ui/menuseparator.js', ['goog.ui.MenuSeparator'], ['goog.ui.MenuSeparatorRenderer', 'goog.ui.Separator', 'goog.ui.registry']);
-goog.addDependency('ui/menuseparatorrenderer.js', ['goog.ui.MenuSeparatorRenderer'], ['goog.dom', 'goog.dom.classlist', 'goog.ui.ControlContent', 'goog.ui.ControlRenderer']);
-goog.addDependency('ui/mockactivitymonitor.js', ['goog.ui.MockActivityMonitor'], ['goog.events.EventType', 'goog.ui.ActivityMonitor']);
-goog.addDependency('ui/mockactivitymonitor_test.js', ['goog.ui.MockActivityMonitorTest'], ['goog.events', 'goog.functions', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.ActivityMonitor', 'goog.ui.MockActivityMonitor']);
-goog.addDependency('ui/modalpopup.js', ['goog.ui.ModalPopup'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.dom.iframe', 'goog.events', 'goog.events.EventType', 'goog.events.FocusHandler', 'goog.fx.Transition', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.PopupBase', 'goog.userAgent']);
-goog.addDependency('ui/nativebuttonrenderer.js', ['goog.ui.NativeButtonRenderer'], ['goog.asserts', 'goog.dom.classlist', 'goog.events.EventType', 'goog.ui.ButtonRenderer', 'goog.ui.Component']);
-goog.addDependency('ui/option.js', ['goog.ui.Option'], ['goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.registry']);
-goog.addDependency('ui/palette.js', ['goog.ui.Palette'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Size', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.PaletteRenderer', 'goog.ui.SelectionModel']);
-goog.addDependency('ui/paletterenderer.js', ['goog.ui.PaletteRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeIterator', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.iter', 'goog.style', 'goog.ui.ControlRenderer', 'goog.userAgent']);
-goog.addDependency('ui/plaintextspellchecker.js', ['goog.ui.PlainTextSpellChecker'], ['goog.Timer', 'goog.a11y.aria', 'goog.asserts', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.spell.SpellCheck', 'goog.style', 'goog.ui.AbstractSpellChecker', 'goog.ui.Component', 'goog.userAgent']);
-goog.addDependency('ui/popup.js', ['goog.ui.Popup', 'goog.ui.Popup.AbsolutePosition', 'goog.ui.Popup.AnchoredPosition', 'goog.ui.Popup.AnchoredViewPortPosition', 'goog.ui.Popup.ClientPosition', 'goog.ui.Popup.Corner', 'goog.ui.Popup.Overflow', 'goog.ui.Popup.ViewPortClientPosition', 'goog.ui.Popup.ViewPortPosition'], ['goog.math.Box', 'goog.positioning.AbsolutePosition', 'goog.positioning.AnchoredPosition', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.ClientPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.ViewportClientPosition', 'goog.positioning.ViewportPosition', 'goog.style', 'goog.ui.PopupBase']);
-goog.addDependency('ui/popupbase.js', ['goog.ui.PopupBase', 'goog.ui.PopupBase.EventType', 'goog.ui.PopupBase.Type'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.fx.Transition', 'goog.style', 'goog.userAgent']);
-goog.addDependency('ui/popupcolorpicker.js', ['goog.ui.PopupColorPicker'], ['goog.asserts', 'goog.dom.classlist', 'goog.events.EventType', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.ui.ColorPicker', 'goog.ui.Component', 'goog.ui.Popup']);
-goog.addDependency('ui/popupdatepicker.js', ['goog.ui.PopupDatePicker'], ['goog.events.EventType', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.style', 'goog.ui.Component', 'goog.ui.DatePicker', 'goog.ui.Popup', 'goog.ui.PopupBase']);
-goog.addDependency('ui/popupmenu.js', ['goog.ui.PopupMenu'], ['goog.events.EventType', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.positioning.Overflow', 'goog.positioning.ViewportClientPosition', 'goog.structs.Map', 'goog.style', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.PopupBase', 'goog.userAgent']);
-goog.addDependency('ui/progressbar.js', ['goog.ui.ProgressBar', 'goog.ui.ProgressBar.Orientation'], ['goog.a11y.aria', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.ui.Component', 'goog.ui.RangeModel', 'goog.userAgent']);
-goog.addDependency('ui/prompt.js', ['goog.ui.Prompt'], ['goog.Timer', 'goog.dom', 'goog.events', 'goog.events.EventType', 'goog.functions', 'goog.html.SafeHtml', 'goog.html.legacyconversions', 'goog.ui.Component', 'goog.ui.Dialog', 'goog.userAgent']);
-goog.addDependency('ui/rangemodel.js', ['goog.ui.RangeModel'], ['goog.events.EventTarget', 'goog.ui.Component']);
-goog.addDependency('ui/ratings.js', ['goog.ui.Ratings', 'goog.ui.Ratings.EventType'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom.classlist', 'goog.events.EventType', 'goog.ui.Component']);
-goog.addDependency('ui/registry.js', ['goog.ui.registry'], ['goog.asserts', 'goog.dom.classlist']);
-goog.addDependency('ui/richtextspellchecker.js', ['goog.ui.RichTextSpellChecker'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.math.Coordinate', 'goog.spell.SpellCheck', 'goog.string.StringBuffer', 'goog.style', 'goog.ui.AbstractSpellChecker', 'goog.ui.Component', 'goog.ui.PopupMenu']);
-goog.addDependency('ui/roundedpanel.js', ['goog.ui.BaseRoundedPanel', 'goog.ui.CssRoundedPanel', 'goog.ui.GraphicsRoundedPanel', 'goog.ui.RoundedPanel', 'goog.ui.RoundedPanel.Corner'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.graphics', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.graphics.Stroke', 'goog.math', 'goog.math.Coordinate', 'goog.style', 'goog.ui.Component', 'goog.userAgent']);
-goog.addDependency('ui/roundedtabrenderer.js', ['goog.ui.RoundedTabRenderer'], ['goog.dom', 'goog.ui.Tab', 'goog.ui.TabBar', 'goog.ui.TabRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/scrollfloater.js', ['goog.ui.ScrollFloater', 'goog.ui.ScrollFloater.EventType'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.userAgent']);
-goog.addDependency('ui/select.js', ['goog.ui.Select'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.events.EventType', 'goog.ui.Component', 'goog.ui.IdGenerator', 'goog.ui.MenuButton', 'goog.ui.MenuItem', 'goog.ui.MenuRenderer', 'goog.ui.SelectionModel', 'goog.ui.registry']);
-goog.addDependency('ui/selectionmenubutton.js', ['goog.ui.SelectionMenuButton', 'goog.ui.SelectionMenuButton.SelectionState'], ['goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.ui.MenuButton', 'goog.ui.MenuItem', 'goog.ui.registry']);
-goog.addDependency('ui/selectionmodel.js', ['goog.ui.SelectionModel'], ['goog.array', 'goog.events.EventTarget', 'goog.events.EventType']);
-goog.addDependency('ui/separator.js', ['goog.ui.Separator'], ['goog.a11y.aria', 'goog.asserts', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.MenuSeparatorRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/serverchart.js', ['goog.ui.ServerChart', 'goog.ui.ServerChart.AxisDisplayType', 'goog.ui.ServerChart.ChartType', 'goog.ui.ServerChart.EncodingType', 'goog.ui.ServerChart.Event', 'goog.ui.ServerChart.LegendPosition', 'goog.ui.ServerChart.MaximumValue', 'goog.ui.ServerChart.MultiAxisAlignment', 'goog.ui.ServerChart.MultiAxisType', 'goog.ui.ServerChart.UriParam', 'goog.ui.ServerChart.UriTooLongEvent'], ['goog.Uri', 'goog.array', 'goog.asserts', 'goog.events.Event', 'goog.string', 'goog.ui.Component']);
-goog.addDependency('ui/slider.js', ['goog.ui.Slider', 'goog.ui.Slider.Orientation'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.dom', 'goog.ui.SliderBase']);
-goog.addDependency('ui/sliderbase.js', ['goog.ui.SliderBase', 'goog.ui.SliderBase.AnimationFactory', 'goog.ui.SliderBase.Orientation'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.events.MouseWheelHandler', 'goog.functions', 'goog.fx.AnimationParallelQueue', 'goog.fx.Dragger', 'goog.fx.Transition', 'goog.fx.dom.ResizeHeight', 'goog.fx.dom.ResizeWidth', 'goog.fx.dom.Slide', 'goog.math', 'goog.math.Coordinate', 'goog.style', 'goog.style.bidi', 'goog.ui.Component', 'goog.ui.RangeModel']);
-goog.addDependency('ui/splitbehavior.js', ['goog.ui.SplitBehavior', 'goog.ui.SplitBehavior.DefaultHandlers'], ['goog.Disposable', 'goog.asserts', 'goog.dispose', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.ui.ButtonSide', 'goog.ui.Component', 'goog.ui.decorate', 'goog.ui.registry']);
-goog.addDependency('ui/splitpane.js', ['goog.ui.SplitPane', 'goog.ui.SplitPane.Orientation'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Rect', 'goog.math.Size', 'goog.style', 'goog.ui.Component', 'goog.userAgent']);
-goog.addDependency('ui/style/app/buttonrenderer.js', ['goog.ui.style.app.ButtonRenderer'], ['goog.dom.classlist', 'goog.ui.Button', 'goog.ui.CustomButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']);
-goog.addDependency('ui/style/app/menubuttonrenderer.js', ['goog.ui.style.app.MenuButtonRenderer'], ['goog.a11y.aria.Role', 'goog.array', 'goog.dom', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuRenderer', 'goog.ui.style.app.ButtonRenderer']);
-goog.addDependency('ui/style/app/primaryactionbuttonrenderer.js', ['goog.ui.style.app.PrimaryActionButtonRenderer'], ['goog.ui.Button', 'goog.ui.registry', 'goog.ui.style.app.ButtonRenderer']);
-goog.addDependency('ui/submenu.js', ['goog.ui.SubMenu'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.KeyCodes', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.Corner', 'goog.style', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.ui.SubMenuRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/submenurenderer.js', ['goog.ui.SubMenuRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItemRenderer']);
-goog.addDependency('ui/tab.js', ['goog.ui.Tab'], ['goog.ui.Component', 'goog.ui.Control', 'goog.ui.TabRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/tabbar.js', ['goog.ui.TabBar', 'goog.ui.TabBar.Location'], ['goog.ui.Component.EventType', 'goog.ui.Container', 'goog.ui.Container.Orientation', 'goog.ui.Tab', 'goog.ui.TabBarRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/tabbarrenderer.js', ['goog.ui.TabBarRenderer'], ['goog.a11y.aria.Role', 'goog.object', 'goog.ui.ContainerRenderer']);
-goog.addDependency('ui/tablesorter.js', ['goog.ui.TableSorter', 'goog.ui.TableSorter.EventType'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.functions', 'goog.ui.Component']);
-goog.addDependency('ui/tabpane.js', ['goog.ui.TabPane', 'goog.ui.TabPane.Events', 'goog.ui.TabPane.TabLocation', 'goog.ui.TabPane.TabPage', 'goog.ui.TabPaneEvent'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.style']);
-goog.addDependency('ui/tabrenderer.js', ['goog.ui.TabRenderer'], ['goog.a11y.aria.Role', 'goog.ui.Component', 'goog.ui.ControlRenderer']);
-goog.addDependency('ui/textarea.js', ['goog.ui.Textarea', 'goog.ui.Textarea.EventType'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventType', 'goog.style', 'goog.ui.Control', 'goog.ui.TextareaRenderer', 'goog.userAgent']);
-goog.addDependency('ui/textarearenderer.js', ['goog.ui.TextareaRenderer'], ['goog.dom.TagName', 'goog.ui.Component', 'goog.ui.ControlRenderer']);
-goog.addDependency('ui/togglebutton.js', ['goog.ui.ToggleButton'], ['goog.ui.Button', 'goog.ui.Component', 'goog.ui.CustomButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/toolbar.js', ['goog.ui.Toolbar'], ['goog.ui.Container', 'goog.ui.ToolbarRenderer']);
-goog.addDependency('ui/toolbarbutton.js', ['goog.ui.ToolbarButton'], ['goog.ui.Button', 'goog.ui.ToolbarButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/toolbarbuttonrenderer.js', ['goog.ui.ToolbarButtonRenderer'], ['goog.ui.CustomButtonRenderer']);
-goog.addDependency('ui/toolbarcolormenubutton.js', ['goog.ui.ToolbarColorMenuButton'], ['goog.ui.ColorMenuButton', 'goog.ui.ToolbarColorMenuButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/toolbarcolormenubuttonrenderer.js', ['goog.ui.ToolbarColorMenuButtonRenderer'], ['goog.asserts', 'goog.dom.classlist', 'goog.ui.ColorMenuButtonRenderer', 'goog.ui.MenuButtonRenderer', 'goog.ui.ToolbarMenuButtonRenderer']);
-goog.addDependency('ui/toolbarmenubutton.js', ['goog.ui.ToolbarMenuButton'], ['goog.ui.MenuButton', 'goog.ui.ToolbarMenuButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/toolbarmenubuttonrenderer.js', ['goog.ui.ToolbarMenuButtonRenderer'], ['goog.ui.MenuButtonRenderer']);
-goog.addDependency('ui/toolbarrenderer.js', ['goog.ui.ToolbarRenderer'], ['goog.a11y.aria.Role', 'goog.ui.Container', 'goog.ui.ContainerRenderer', 'goog.ui.Separator', 'goog.ui.ToolbarSeparatorRenderer']);
-goog.addDependency('ui/toolbarselect.js', ['goog.ui.ToolbarSelect'], ['goog.ui.Select', 'goog.ui.ToolbarMenuButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/toolbarseparator.js', ['goog.ui.ToolbarSeparator'], ['goog.ui.Separator', 'goog.ui.ToolbarSeparatorRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/toolbarseparatorrenderer.js', ['goog.ui.ToolbarSeparatorRenderer'], ['goog.asserts', 'goog.dom.classlist', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.MenuSeparatorRenderer']);
-goog.addDependency('ui/toolbartogglebutton.js', ['goog.ui.ToolbarToggleButton'], ['goog.ui.ToggleButton', 'goog.ui.ToolbarButtonRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/tooltip.js', ['goog.ui.Tooltip', 'goog.ui.Tooltip.CursorTooltipPosition', 'goog.ui.Tooltip.ElementTooltipPosition', 'goog.ui.Tooltip.State'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.dom.safe', 'goog.events', 'goog.events.EventType', 'goog.html.legacyconversions', 'goog.math.Box', 'goog.math.Coordinate', 'goog.positioning', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus', 'goog.positioning.ViewportPosition', 'goog.structs.Set', 'goog.style', 'goog.ui.Popup', 'goog.ui.PopupBase']);
-goog.addDependency('ui/tree/basenode.js', ['goog.ui.tree.BaseNode', 'goog.ui.tree.BaseNode.EventType'], ['goog.Timer', 'goog.a11y.aria', 'goog.asserts', 'goog.dom.safe', 'goog.events.Event', 'goog.events.KeyCodes', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.html.legacyconversions', 'goog.string', 'goog.string.StringBuffer', 'goog.style', 'goog.ui.Component']);
-goog.addDependency('ui/tree/treecontrol.js', ['goog.ui.tree.TreeControl'], ['goog.a11y.aria', 'goog.asserts', 'goog.dom.classlist', 'goog.events.EventType', 'goog.events.FocusHandler', 'goog.events.KeyHandler', 'goog.html.SafeHtml', 'goog.log', 'goog.ui.tree.BaseNode', 'goog.ui.tree.TreeNode', 'goog.ui.tree.TypeAhead', 'goog.userAgent']);
-goog.addDependency('ui/tree/treenode.js', ['goog.ui.tree.TreeNode'], ['goog.ui.tree.BaseNode']);
-goog.addDependency('ui/tree/typeahead.js', ['goog.ui.tree.TypeAhead', 'goog.ui.tree.TypeAhead.Offset'], ['goog.array', 'goog.events.KeyCodes', 'goog.string', 'goog.structs.Trie']);
-goog.addDependency('ui/tristatemenuitem.js', ['goog.ui.TriStateMenuItem', 'goog.ui.TriStateMenuItem.State'], ['goog.dom.classlist', 'goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.TriStateMenuItemRenderer', 'goog.ui.registry']);
-goog.addDependency('ui/tristatemenuitemrenderer.js', ['goog.ui.TriStateMenuItemRenderer'], ['goog.asserts', 'goog.dom.classlist', 'goog.ui.MenuItemRenderer']);
-goog.addDependency('ui/twothumbslider.js', ['goog.ui.TwoThumbSlider'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.dom', 'goog.ui.SliderBase']);
-goog.addDependency('ui/zippy.js', ['goog.ui.Zippy', 'goog.ui.Zippy.Events', 'goog.ui.ZippyEvent'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.classlist', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.style']);
-goog.addDependency('uri/uri.js', ['goog.Uri', 'goog.Uri.QueryData'], ['goog.array', 'goog.string', 'goog.structs', 'goog.structs.Map', 'goog.uri.utils', 'goog.uri.utils.ComponentIndex', 'goog.uri.utils.StandardQueryParam']);
-goog.addDependency('uri/uri_test.js', ['goog.UriTest'], ['goog.Uri', 'goog.testing.jsunit']);
-goog.addDependency('uri/utils.js', ['goog.uri.utils', 'goog.uri.utils.ComponentIndex', 'goog.uri.utils.QueryArray', 'goog.uri.utils.QueryValue', 'goog.uri.utils.StandardQueryParam'], ['goog.asserts', 'goog.string', 'goog.userAgent']);
-goog.addDependency('uri/utils_test.js', ['goog.uri.utilsTest'], ['goog.functions', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.uri.utils']);
-goog.addDependency('useragent/adobereader.js', ['goog.userAgent.adobeReader'], ['goog.string', 'goog.userAgent']);
-goog.addDependency('useragent/adobereader_test.js', ['goog.userAgent.adobeReaderTest'], ['goog.testing.jsunit', 'goog.userAgent.adobeReader']);
-goog.addDependency('useragent/flash.js', ['goog.userAgent.flash'], ['goog.string']);
-goog.addDependency('useragent/flash_test.js', ['goog.userAgent.flashTest'], ['goog.testing.jsunit', 'goog.userAgent.flash']);
-goog.addDependency('useragent/iphoto.js', ['goog.userAgent.iphoto'], ['goog.string', 'goog.userAgent']);
-goog.addDependency('useragent/jscript.js', ['goog.userAgent.jscript'], ['goog.string']);
-goog.addDependency('useragent/jscript_test.js', ['goog.userAgent.jscriptTest'], ['goog.testing.jsunit', 'goog.userAgent.jscript']);
-goog.addDependency('useragent/keyboard.js', ['goog.userAgent.keyboard'], ['goog.userAgent', 'goog.userAgent.product']);
-goog.addDependency('useragent/keyboard_test.js', ['goog.userAgent.keyboardTest'], ['goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.jsunit', 'goog.userAgent.keyboard', 'goog.userAgentTestUtil']);
-goog.addDependency('useragent/picasa.js', ['goog.userAgent.picasa'], ['goog.string', 'goog.userAgent']);
-goog.addDependency('useragent/platform.js', ['goog.userAgent.platform'], ['goog.userAgent']);
-goog.addDependency('useragent/platform_test.js', ['goog.userAgent.platformTest'], ['goog.testing.MockUserAgent', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.platform']);
-goog.addDependency('useragent/product.js', ['goog.userAgent.product'], ['goog.userAgent']);
-goog.addDependency('useragent/product_isversion.js', ['goog.userAgent.product.isVersion'], ['goog.userAgent.product']);
-goog.addDependency('useragent/product_test.js', ['goog.userAgent.productTest'], ['goog.array', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion', 'goog.userAgentTestUtil']);
-goog.addDependency('useragent/useragent.js', ['goog.userAgent'], ['goog.labs.userAgent.browser', 'goog.labs.userAgent.engine', 'goog.labs.userAgent.util', 'goog.string']);
-goog.addDependency('useragent/useragent_quirks_test.js', ['goog.userAgentQuirksTest'], ['goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency('useragent/useragent_test.js', ['goog.userAgentTest'], ['goog.array', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgentTestUtil']);
-goog.addDependency('useragent/useragenttestutil.js', ['goog.userAgentTestUtil', 'goog.userAgentTestUtil.UserAgents'], ['goog.labs.userAgent.browser', 'goog.labs.userAgent.engine', 'goog.userAgent', 'goog.userAgent.keyboard', 'goog.userAgent.platform', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
-goog.addDependency('vec/float32array.js', ['goog.vec.Float32Array'], []);
-goog.addDependency('vec/float64array.js', ['goog.vec.Float64Array'], []);
-goog.addDependency('vec/mat3.js', ['goog.vec.Mat3'], ['goog.vec']);
-goog.addDependency('vec/mat3d.js', ['goog.vec.mat3d', 'goog.vec.mat3d.Type'], ['goog.vec']);
-goog.addDependency('vec/mat3f.js', ['goog.vec.mat3f', 'goog.vec.mat3f.Type'], ['goog.vec']);
-goog.addDependency('vec/mat4.js', ['goog.vec.Mat4'], ['goog.vec', 'goog.vec.Vec3', 'goog.vec.Vec4']);
-goog.addDependency('vec/mat4d.js', ['goog.vec.mat4d', 'goog.vec.mat4d.Type'], ['goog.vec', 'goog.vec.vec3d', 'goog.vec.vec4d']);
-goog.addDependency('vec/mat4f.js', ['goog.vec.mat4f', 'goog.vec.mat4f.Type'], ['goog.vec', 'goog.vec.vec3f', 'goog.vec.vec4f']);
-goog.addDependency('vec/matrix3.js', ['goog.vec.Matrix3'], []);
-goog.addDependency('vec/matrix4.js', ['goog.vec.Matrix4'], ['goog.vec', 'goog.vec.Vec3', 'goog.vec.Vec4']);
-goog.addDependency('vec/quaternion.js', ['goog.vec.Quaternion'], ['goog.vec', 'goog.vec.Vec3', 'goog.vec.Vec4']);
-goog.addDependency('vec/ray.js', ['goog.vec.Ray'], ['goog.vec.Vec3']);
-goog.addDependency('vec/vec.js', ['goog.vec', 'goog.vec.AnyType', 'goog.vec.ArrayType', 'goog.vec.Float32', 'goog.vec.Float64', 'goog.vec.Number'], ['goog.vec.Float32Array', 'goog.vec.Float64Array']);
-goog.addDependency('vec/vec2.js', ['goog.vec.Vec2'], ['goog.vec']);
-goog.addDependency('vec/vec2d.js', ['goog.vec.vec2d', 'goog.vec.vec2d.Type'], ['goog.vec']);
-goog.addDependency('vec/vec2f.js', ['goog.vec.vec2f', 'goog.vec.vec2f.Type'], ['goog.vec']);
-goog.addDependency('vec/vec3.js', ['goog.vec.Vec3'], ['goog.vec']);
-goog.addDependency('vec/vec3d.js', ['goog.vec.vec3d', 'goog.vec.vec3d.Type'], ['goog.vec']);
-goog.addDependency('vec/vec3f.js', ['goog.vec.vec3f', 'goog.vec.vec3f.Type'], ['goog.vec']);
-goog.addDependency('vec/vec4.js', ['goog.vec.Vec4'], ['goog.vec']);
-goog.addDependency('vec/vec4d.js', ['goog.vec.vec4d', 'goog.vec.vec4d.Type'], ['goog.vec']);
-goog.addDependency('vec/vec4f.js', ['goog.vec.vec4f', 'goog.vec.vec4f.Type'], ['goog.vec']);
-goog.addDependency('webgl/webgl.js', ['goog.webgl'], []);
-goog.addDependency('window/window.js', ['goog.window'], ['goog.string', 'goog.userAgent']);
-goog.addDependency('window/window_test.js', ['goog.windowTest'], ['goog.dom', 'goog.events', 'goog.string', 'goog.testing.AsyncTestCase', 'goog.testing.jsunit', 'goog.window']);
+/**
+ * @fileoverview Custom exports file.
+ * @suppress {checkVars}
+ */
 
 goog.require('ol');
 goog.require('ol.Attribution');
@@ -100740,6 +112607,7 @@ goog.require('ol.FeatureOverlay');
 goog.require('ol.Geolocation');
 goog.require('ol.GeolocationProperty');
 goog.require('ol.Graticule');
+goog.require('ol.Image');
 goog.require('ol.ImageTile');
 goog.require('ol.Kinetic');
 goog.require('ol.Map');
@@ -100769,6 +112637,7 @@ goog.require('ol.control.Attribution');
 goog.require('ol.control.Control');
 goog.require('ol.control.FullScreen');
 goog.require('ol.control.MousePosition');
+goog.require('ol.control.OverviewMap');
 goog.require('ol.control.Rotate');
 goog.require('ol.control.ScaleLine');
 goog.require('ol.control.ScaleLineProperty');
@@ -100788,6 +112657,9 @@ goog.require('ol.extent.Relationship');
 goog.require('ol.feature');
 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');
@@ -100799,6 +112671,7 @@ 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.geom.Circle');
 goog.require('ol.geom.Geometry');
 goog.require('ol.geom.GeometryCollection');
@@ -100822,12 +112695,15 @@ goog.require('ol.interaction.DragRotate');
 goog.require('ol.interaction.DragRotateAndZoom');
 goog.require('ol.interaction.DragZoom');
 goog.require('ol.interaction.Draw');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.InteractionProperty');
 goog.require('ol.interaction.KeyboardPan');
 goog.require('ol.interaction.KeyboardZoom');
 goog.require('ol.interaction.Modify');
 goog.require('ol.interaction.MouseWheelZoom');
 goog.require('ol.interaction.PinchRotate');
 goog.require('ol.interaction.PinchZoom');
+goog.require('ol.interaction.Pointer');
 goog.require('ol.interaction.Select');
 goog.require('ol.layer.Base');
 goog.require('ol.layer.Group');
@@ -100848,12 +112724,14 @@ goog.require('ol.proj.common');
 goog.require('ol.render.Event');
 goog.require('ol.render.EventType');
 goog.require('ol.render.canvas.Immediate');
+goog.require('ol.render.webgl.Immediate');
 goog.require('ol.source.BingMaps');
 goog.require('ol.source.Cluster');
 goog.require('ol.source.FormatVector');
 goog.require('ol.source.GPX');
 goog.require('ol.source.GeoJSON');
 goog.require('ol.source.IGC');
+goog.require('ol.source.Image');
 goog.require('ol.source.ImageCanvas');
 goog.require('ol.source.ImageMapGuide');
 goog.require('ol.source.ImageStatic');
@@ -100873,6 +112751,7 @@ goog.require('ol.source.TileDebug');
 goog.require('ol.source.TileImage');
 goog.require('ol.source.TileJSON');
 goog.require('ol.source.TileOptions');
+goog.require('ol.source.TileUTFGrid');
 goog.require('ol.source.TileVector');
 goog.require('ol.source.TileWMS');
 goog.require('ol.source.TopoJSON');
@@ -100883,6 +112762,8 @@ goog.require('ol.source.WMTS');
 goog.require('ol.source.WMTSRequestEncoding');
 goog.require('ol.source.XYZ');
 goog.require('ol.source.Zoomify');
+goog.require('ol.style.Atlas');
+goog.require('ol.style.AtlasManager');
 goog.require('ol.style.Circle');
 goog.require('ol.style.Fill');
 goog.require('ol.style.Icon');
@@ -100891,36 +112772,44 @@ goog.require('ol.style.IconImageCache');
 goog.require('ol.style.IconOrigin');
 goog.require('ol.style.Image');
 goog.require('ol.style.ImageState');
+goog.require('ol.style.RegularShape');
 goog.require('ol.style.Stroke');
 goog.require('ol.style.Style');
 goog.require('ol.style.Text');
+goog.require('ol.style.defaultGeometryFunction');
 goog.require('ol.tilegrid.TileGrid');
 goog.require('ol.tilegrid.WMTS');
 goog.require('ol.tilegrid.XYZ');
 goog.require('ol.tilegrid.Zoomify');
 goog.require('ol.tilejson');
 goog.require('ol.webgl.Context');
+goog.require('ol.xml');
 
 
 goog.exportSymbol(
     'ol.animation.bounce',
-    ol.animation.bounce);
+    ol.animation.bounce,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.animation.pan',
-    ol.animation.pan);
+    ol.animation.pan,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.animation.rotate',
-    ol.animation.rotate);
+    ol.animation.rotate,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.animation.zoom',
-    ol.animation.zoom);
+    ol.animation.zoom,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.Attribution',
-    ol.Attribution);
+    ol.Attribution,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Attribution.prototype,
@@ -100934,7 +112823,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.Collection',
-    ol.Collection);
+    ol.Collection,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Collection.prototype,
@@ -100998,31 +112888,38 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.coordinate.add',
-    ol.coordinate.add);
+    ol.coordinate.add,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.coordinate.createStringXY',
-    ol.coordinate.createStringXY);
+    ol.coordinate.createStringXY,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.coordinate.format',
-    ol.coordinate.format);
+    ol.coordinate.format,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.coordinate.rotate',
-    ol.coordinate.rotate);
+    ol.coordinate.rotate,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.coordinate.toStringHDMS',
-    ol.coordinate.toStringHDMS);
+    ol.coordinate.toStringHDMS,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.coordinate.toStringXY',
-    ol.coordinate.toStringXY);
+    ol.coordinate.toStringXY,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.DeviceOrientation',
-    ol.DeviceOrientation);
+    ol.DeviceOrientation,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.DeviceOrientation.prototype,
@@ -101056,99 +112953,133 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.easing.easeIn',
-    ol.easing.easeIn);
+    ol.easing.easeIn,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.easing.easeOut',
-    ol.easing.easeOut);
+    ol.easing.easeOut,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.easing.inAndOut',
-    ol.easing.inAndOut);
+    ol.easing.inAndOut,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.easing.linear',
-    ol.easing.linear);
+    ol.easing.linear,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.easing.upAndDown',
-    ol.easing.upAndDown);
+    ol.easing.upAndDown,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.boundingExtent',
-    ol.extent.boundingExtent);
+    ol.extent.boundingExtent,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.buffer',
-    ol.extent.buffer);
+    ol.extent.buffer,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.containsCoordinate',
-    ol.extent.containsCoordinate);
+    ol.extent.containsCoordinate,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.containsExtent',
-    ol.extent.containsExtent);
+    ol.extent.containsExtent,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.extent.containsXY',
+    ol.extent.containsXY,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.createEmpty',
-    ol.extent.createEmpty);
+    ol.extent.createEmpty,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.equals',
-    ol.extent.equals);
+    ol.extent.equals,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.extend',
-    ol.extent.extend);
+    ol.extent.extend,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getBottomLeft',
-    ol.extent.getBottomLeft);
+    ol.extent.getBottomLeft,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getBottomRight',
-    ol.extent.getBottomRight);
+    ol.extent.getBottomRight,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getCenter',
-    ol.extent.getCenter);
+    ol.extent.getCenter,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getHeight',
-    ol.extent.getHeight);
+    ol.extent.getHeight,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.extent.getIntersection',
+    ol.extent.getIntersection,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getSize',
-    ol.extent.getSize);
+    ol.extent.getSize,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getTopLeft',
-    ol.extent.getTopLeft);
+    ol.extent.getTopLeft,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getTopRight',
-    ol.extent.getTopRight);
+    ol.extent.getTopRight,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.getWidth',
-    ol.extent.getWidth);
+    ol.extent.getWidth,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.intersects',
-    ol.extent.intersects);
+    ol.extent.intersects,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.isEmpty',
-    ol.extent.isEmpty);
+    ol.extent.isEmpty,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.extent.applyTransform',
-    ol.extent.applyTransform);
+    ol.extent.applyTransform,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.Feature',
-    ol.Feature);
+    ol.Feature,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Feature.prototype,
@@ -101202,7 +113133,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.FeatureOverlay',
-    ol.FeatureOverlay);
+    ol.FeatureOverlay,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.FeatureOverlay.prototype,
@@ -101246,7 +113178,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.Geolocation',
-    ol.Geolocation);
+    ol.Geolocation,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Geolocation.prototype,
@@ -101315,7 +113248,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.Graticule',
-    ol.Graticule);
+    ol.Graticule,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Graticule.prototype,
@@ -101339,27 +113273,38 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.has.DEVICE_PIXEL_RATIO',
-    ol.has.DEVICE_PIXEL_RATIO);
+    ol.has.DEVICE_PIXEL_RATIO,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.has.CANVAS',
-    ol.has.CANVAS);
+    ol.has.CANVAS,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.has.DEVICE_ORIENTATION',
-    ol.has.DEVICE_ORIENTATION);
+    ol.has.DEVICE_ORIENTATION,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.has.GEOLOCATION',
-    ol.has.GEOLOCATION);
+    ol.has.GEOLOCATION,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.has.TOUCH',
-    ol.has.TOUCH);
+    ol.has.TOUCH,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.has.WEBGL',
-    ol.has.WEBGL);
+    ol.has.WEBGL,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.Image.prototype,
+    'getImage',
+    ol.Image.prototype.getImage);
 
 goog.exportProperty(
     ol.ImageTile.prototype,
@@ -101368,23 +113313,28 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.Kinetic',
-    ol.Kinetic);
+    ol.Kinetic,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.loadingstrategy.all',
-    ol.loadingstrategy.all);
+    ol.loadingstrategy.all,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.loadingstrategy.bbox',
-    ol.loadingstrategy.bbox);
+    ol.loadingstrategy.bbox,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.loadingstrategy.createTile',
-    ol.loadingstrategy.createTile);
+    ol.loadingstrategy.createTile,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.Map',
-    ol.Map);
+    ol.Map,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Map.prototype,
@@ -101431,6 +113381,11 @@ goog.exportProperty(
     'getTarget',
     ol.Map.prototype.getTarget);
 
+goog.exportProperty(
+    ol.Map.prototype,
+    'getTargetElement',
+    ol.Map.prototype.getTargetElement);
+
 goog.exportProperty(
     ol.Map.prototype,
     'getCoordinateFromPixel',
@@ -101576,6 +113531,11 @@ goog.exportProperty(
     'key',
     ol.ObjectEvent.prototype.key);
 
+goog.exportProperty(
+    ol.ObjectEvent.prototype,
+    'oldValue',
+    ol.ObjectEvent.prototype.oldValue);
+
 goog.exportProperty(
     ol.ObjectAccessor.prototype,
     'transform',
@@ -101583,7 +113543,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.Object',
-    ol.Object);
+    ol.Object,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Object.prototype,
@@ -101627,12 +113588,18 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.Observable',
-    ol.Observable);
+    ol.Observable,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.Observable.unByKey',
+    ol.Observable.unByKey,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Observable.prototype,
-    'dispatchChangeEvent',
-    ol.Observable.prototype.dispatchChangeEvent);
+    'changed',
+    ol.Observable.prototype.changed);
 
 goog.exportProperty(
     ol.Observable.prototype,
@@ -101659,13 +113626,20 @@ goog.exportProperty(
     'unByKey',
     ol.Observable.prototype.unByKey);
 
+goog.exportSymbol(
+    'ol.WEBGL_MAX_TEXTURE_SIZE',
+    ol.WEBGL_MAX_TEXTURE_SIZE,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.inherits',
-    ol.inherits);
+    ol.inherits,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.Overlay',
-    ol.Overlay);
+    ol.Overlay,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Overlay.prototype,
@@ -101724,7 +113698,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.View',
-    ol.View);
+    ol.View,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.View.prototype,
@@ -101816,9 +113791,20 @@ goog.exportProperty(
     'setZoom',
     ol.View.prototype.setZoom);
 
+goog.exportSymbol(
+    'ol.xml.getAllTextContent',
+    ol.xml.getAllTextContent,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.xml.parse',
+    ol.xml.parse,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.webgl.Context',
-    ol.webgl.Context);
+    ol.webgl.Context,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.webgl.Context.prototype,
@@ -101832,7 +113818,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.tilegrid.TileGrid',
-    ol.tilegrid.TileGrid);
+    ol.tilegrid.TileGrid,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.tilegrid.TileGrid.prototype,
@@ -101859,6 +113846,16 @@ goog.exportProperty(
     'getResolutions',
     ol.tilegrid.TileGrid.prototype.getResolutions);
 
+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',
@@ -101866,7 +113863,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.tilegrid.WMTS',
-    ol.tilegrid.WMTS);
+    ol.tilegrid.WMTS,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.tilegrid.WMTS.prototype,
@@ -101875,15 +113873,23 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.tilegrid.XYZ',
-    ol.tilegrid.XYZ);
+    ol.tilegrid.XYZ,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.tilegrid.Zoomify',
-    ol.tilegrid.Zoomify);
+    ol.tilegrid.Zoomify,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.style.AtlasManager',
+    ol.style.AtlasManager,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.style.Circle',
-    ol.style.Circle);
+    ol.style.Circle,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.style.Circle.prototype,
@@ -101922,16 +113928,23 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.style.Fill',
-    ol.style.Fill);
+    ol.style.Fill,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.style.Fill.prototype,
     'getColor',
     ol.style.Fill.prototype.getColor);
 
+goog.exportProperty(
+    ol.style.Fill.prototype,
+    'setColor',
+    ol.style.Fill.prototype.setColor);
+
 goog.exportSymbol(
     'ol.style.Icon',
-    ol.style.Icon);
+    ol.style.Icon,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.style.Icon.prototype,
@@ -101958,6 +113971,21 @@ goog.exportProperty(
     'getSize',
     ol.style.Icon.prototype.getSize);
 
+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',
@@ -101968,9 +113996,85 @@ goog.exportProperty(
     'getScale',
     ol.style.Image.prototype.getScale);
 
+goog.exportProperty(
+    ol.style.Image.prototype,
+    'getSnapToPixel',
+    ol.style.Image.prototype.getSnapToPixel);
+
+goog.exportProperty(
+    ol.style.Image.prototype,
+    'getImage',
+    ol.style.Image.prototype.getImage);
+
+goog.exportProperty(
+    ol.style.Image.prototype,
+    'setRotation',
+    ol.style.Image.prototype.setRotation);
+
+goog.exportProperty(
+    ol.style.Image.prototype,
+    'setScale',
+    ol.style.Image.prototype.setScale);
+
+goog.exportSymbol(
+    'ol.style.RegularShape',
+    ol.style.RegularShape,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getAnchor',
+    ol.style.RegularShape.prototype.getAnchor);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getAngle',
+    ol.style.RegularShape.prototype.getAngle);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getFill',
+    ol.style.RegularShape.prototype.getFill);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getImage',
+    ol.style.RegularShape.prototype.getImage);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getOrigin',
+    ol.style.RegularShape.prototype.getOrigin);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getPoints',
+    ol.style.RegularShape.prototype.getPoints);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getRadius',
+    ol.style.RegularShape.prototype.getRadius);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getRadius2',
+    ol.style.RegularShape.prototype.getRadius2);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getSize',
+    ol.style.RegularShape.prototype.getSize);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'getStroke',
+    ol.style.RegularShape.prototype.getStroke);
+
 goog.exportSymbol(
     'ol.style.Stroke',
-    ol.style.Stroke);
+    ol.style.Stroke,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.style.Stroke.prototype,
@@ -102002,9 +114106,50 @@ goog.exportProperty(
     '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);
+    ol.style.Style,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'getGeometry',
+    ol.style.Style.prototype.getGeometry);
+
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'getGeometryFunction',
+    ol.style.Style.prototype.getGeometryFunction);
 
 goog.exportProperty(
     ol.style.Style.prototype,
@@ -102031,9 +114176,20 @@ goog.exportProperty(
     '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);
+    ol.style.Text,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.style.Text.prototype,
@@ -102085,21 +114241,65 @@ goog.exportProperty(
     'getTextBaseline',
     ol.style.Text.prototype.getTextBaseline);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'setFont',
+    ol.style.Text.prototype.setFont);
+
+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);
+    ol.Sphere,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.BingMaps',
-    ol.source.BingMaps);
+    ol.source.BingMaps,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.BingMaps.TOS_ATTRIBUTION',
-    ol.source.BingMaps.TOS_ATTRIBUTION);
+    ol.source.BingMaps.TOS_ATTRIBUTION,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.Cluster',
-    ol.source.Cluster);
+    ol.source.Cluster,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.FormatVector.prototype,
@@ -102108,23 +114308,28 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.GeoJSON',
-    ol.source.GeoJSON);
+    ol.source.GeoJSON,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.GPX',
-    ol.source.GPX);
+    ol.source.GPX,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.IGC',
-    ol.source.IGC);
+    ol.source.IGC,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.ImageCanvas',
-    ol.source.ImageCanvas);
+    ol.source.ImageCanvas,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.ImageMapGuide',
-    ol.source.ImageMapGuide);
+    ol.source.ImageMapGuide,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.ImageMapGuide.prototype,
@@ -102136,22 +114341,45 @@ goog.exportProperty(
     'updateParams',
     ol.source.ImageMapGuide.prototype.updateParams);
 
+goog.exportSymbol(
+    'ol.source.Image',
+    ol.source.Image,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.source.ImageStatic',
-    ol.source.ImageStatic);
+    ol.source.ImageStatic,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.ImageVector',
-    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);
+    ol.source.ImageWMS,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.ImageWMS.prototype,
@@ -102180,37 +114408,44 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.KML',
-    ol.source.KML);
+    ol.source.KML,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.MapQuest',
-    ol.source.MapQuest);
+    ol.source.MapQuest,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.OSM',
-    ol.source.OSM);
-
-goog.exportSymbol(
-    'ol.source.OSM.DATA_ATTRIBUTION',
-    ol.source.OSM.DATA_ATTRIBUTION);
+    ol.source.OSM,
+    OPENLAYERS);
 
 goog.exportSymbol(
-    'ol.source.OSM.TILE_ATTRIBUTION',
-    ol.source.OSM.TILE_ATTRIBUTION);
+    'ol.source.OSM.ATTRIBUTION',
+    ol.source.OSM.ATTRIBUTION,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.OSMXML',
-    ol.source.OSMXML);
+    ol.source.OSMXML,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.ServerVector',
-    ol.source.ServerVector);
+    ol.source.ServerVector,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.ServerVector.prototype,
     'readFeatures',
     ol.source.ServerVector.prototype.readFeatures);
 
+goog.exportSymbol(
+    'ol.source.Source',
+    ol.source.Source,
+    OPENLAYERS);
+
 goog.exportProperty(
     ol.source.Source.prototype,
     'getAttributions',
@@ -102233,19 +114468,23 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.Stamen',
-    ol.source.Stamen);
+    ol.source.Stamen,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.StaticVector',
-    ol.source.StaticVector);
+    ol.source.StaticVector,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.TileDebug',
-    ol.source.TileDebug);
+    ol.source.TileDebug,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.TileImage',
-    ol.source.TileImage);
+    ol.source.TileImage,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.TileImage.prototype,
@@ -102269,20 +114508,53 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.TileJSON',
-    ol.source.TileJSON);
+    ol.source.TileJSON,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.source.Tile',
+    ol.source.Tile,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.Tile.prototype,
     'getTileGrid',
     ol.source.Tile.prototype.getTileGrid);
 
+goog.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.TileVector',
-    ol.source.TileVector);
+    ol.source.TileVector,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.source.TileVector.prototype,
+    'getFeatures',
+    ol.source.TileVector.prototype.getFeatures);
+
+goog.exportProperty(
+    ol.source.TileVector.prototype,
+    'getFeaturesAtCoordinateAndResolution',
+    ol.source.TileVector.prototype.getFeaturesAtCoordinateAndResolution);
 
 goog.exportSymbol(
     'ol.source.TileWMS',
-    ol.source.TileWMS);
+    ol.source.TileWMS,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.TileWMS.prototype,
@@ -102316,11 +114588,13 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.TopoJSON',
-    ol.source.TopoJSON);
+    ol.source.TopoJSON,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.source.Vector',
-    ol.source.Vector);
+    ol.source.Vector,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.Vector.prototype,
@@ -102347,6 +114621,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.Vector.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.Vector.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.Vector.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.Vector.prototype,
     'getFeatures',
@@ -102384,7 +114663,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.WMTS',
-    ol.source.WMTS);
+    ol.source.WMTS,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.WMTS.prototype,
@@ -102398,7 +114678,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.XYZ',
-    ol.source.XYZ);
+    ol.source.XYZ,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.source.XYZ.prototype,
@@ -102412,7 +114693,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.source.Zoomify',
-    ol.source.Zoomify);
+    ol.source.Zoomify,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.render.Event.prototype,
@@ -102434,6 +114716,76 @@ goog.exportProperty(
     'glContext',
     ol.render.Event.prototype.glContext);
 
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawAsync',
+    ol.render.webgl.Immediate.prototype.drawAsync);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawCircleGeometry',
+    ol.render.webgl.Immediate.prototype.drawCircleGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawFeature',
+    ol.render.webgl.Immediate.prototype.drawFeature);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawGeometryCollectionGeometry',
+    ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawPointGeometry',
+    ol.render.webgl.Immediate.prototype.drawPointGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawLineStringGeometry',
+    ol.render.webgl.Immediate.prototype.drawLineStringGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawMultiLineStringGeometry',
+    ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawMultiPointGeometry',
+    ol.render.webgl.Immediate.prototype.drawMultiPointGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawMultiPolygonGeometry',
+    ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawPolygonGeometry',
+    ol.render.webgl.Immediate.prototype.drawPolygonGeometry);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'drawText',
+    ol.render.webgl.Immediate.prototype.drawText);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'setFillStrokeStyle',
+    ol.render.webgl.Immediate.prototype.setFillStrokeStyle);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'setImageStyle',
+    ol.render.webgl.Immediate.prototype.setImageStyle);
+
+goog.exportProperty(
+    ol.render.webgl.Immediate.prototype,
+    'setTextStyle',
+    ol.render.webgl.Immediate.prototype.setTextStyle);
+
 goog.exportProperty(
     ol.render.canvas.Immediate.prototype,
     'drawAsync',
@@ -102496,15 +114848,18 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.proj.common.add',
-    ol.proj.common.add);
+    ol.proj.common.add,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.METERS_PER_UNIT',
-    ol.proj.METERS_PER_UNIT);
+    ol.proj.METERS_PER_UNIT,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.Projection',
-    ol.proj.Projection);
+    ol.proj.Projection,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.proj.Projection.prototype,
@@ -102548,35 +114903,43 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.proj.addEquivalentProjections',
-    ol.proj.addEquivalentProjections);
+    ol.proj.addEquivalentProjections,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.addProjection',
-    ol.proj.addProjection);
+    ol.proj.addProjection,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.addCoordinateTransforms',
-    ol.proj.addCoordinateTransforms);
+    ol.proj.addCoordinateTransforms,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.get',
-    ol.proj.get);
+    ol.proj.get,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.getTransform',
-    ol.proj.getTransform);
+    ol.proj.getTransform,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.transform',
-    ol.proj.transform);
+    ol.proj.transform,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.proj.transformExtent',
-    ol.proj.transformExtent);
+    ol.proj.transformExtent,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.layer.Heatmap',
-    ol.layer.Heatmap);
+    ol.layer.Heatmap,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.layer.Heatmap.prototype,
@@ -102590,7 +114953,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.layer.Image',
-    ol.layer.Image);
+    ol.layer.Image,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.layer.Image.prototype,
@@ -102599,13 +114963,24 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.layer.Layer',
-    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,
+    'setSource',
+    ol.layer.Layer.prototype.setSource);
+
+goog.exportSymbol(
+    'ol.layer.Base',
+    ol.layer.Base,
+    OPENLAYERS);
+
 goog.exportProperty(
     ol.layer.Base.prototype,
     'getBrightness',
@@ -102698,7 +115073,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.layer.Group',
-    ol.layer.Group);
+    ol.layer.Group,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.layer.Group.prototype,
@@ -102712,7 +115088,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.layer.Tile',
-    ol.layer.Tile);
+    ol.layer.Tile,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.layer.Tile.prototype,
@@ -102741,7 +115118,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.layer.Vector',
-    ol.layer.Vector);
+    ol.layer.Vector,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.layer.Vector.prototype,
@@ -102765,11 +115143,23 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.interaction.DoubleClickZoom',
-    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);
+    ol.interaction.DragAndDrop,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.interaction.DragAndDrop.handleEvent',
+    ol.interaction.DragAndDrop.handleEvent,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.interaction.DragAndDropEvent.prototype,
@@ -102793,7 +115183,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.interaction.DragBox',
-    ol.interaction.DragBox);
+    ol.interaction.DragBox,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.interaction.DragBox.prototype,
@@ -102802,19 +115193,23 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.interaction.DragPan',
-    ol.interaction.DragPan);
+    ol.interaction.DragPan,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.DragRotateAndZoom',
-    ol.interaction.DragRotateAndZoom);
+    ol.interaction.DragRotateAndZoom,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.DragRotate',
-    ol.interaction.DragRotate);
+    ol.interaction.DragRotate,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.DragZoom',
-    ol.interaction.DragZoom);
+    ol.interaction.DragZoom,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.DrawEvent.prototype,
@@ -102823,45 +115218,114 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.interaction.Draw',
-    ol.interaction.Draw);
+    ol.interaction.Draw,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.interaction.Draw.handleEvent',
+    ol.interaction.Draw.handleEvent,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.interaction.Draw.prototype,
+    'finishDrawing',
+    ol.interaction.Draw.prototype.finishDrawing);
+
+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,
+    'setActive',
+    ol.interaction.Interaction.prototype.setActive);
 
 goog.exportSymbol(
     'ol.interaction.defaults',
-    ol.interaction.defaults);
+    ol.interaction.defaults,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.KeyboardPan',
-    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);
+    ol.interaction.KeyboardZoom,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.interaction.KeyboardZoom.handleEvent',
+    ol.interaction.KeyboardZoom.handleEvent,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.Modify',
-    ol.interaction.Modify);
+    ol.interaction.Modify,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.interaction.Modify.handleEvent',
+    ol.interaction.Modify.handleEvent,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.MouseWheelZoom',
-    ol.interaction.MouseWheelZoom);
+    ol.interaction.MouseWheelZoom,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.interaction.MouseWheelZoom.handleEvent',
+    ol.interaction.MouseWheelZoom.handleEvent,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.PinchRotate',
-    ol.interaction.PinchRotate);
+    ol.interaction.PinchRotate,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.interaction.PinchZoom',
-    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);
+    ol.interaction.Select,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.interaction.Select.prototype,
     'getFeatures',
     ol.interaction.Select.prototype.getFeatures);
 
+goog.exportSymbol(
+    'ol.interaction.Select.handleEvent',
+    ol.interaction.Select.handleEvent,
+    OPENLAYERS);
+
 goog.exportProperty(
     ol.interaction.Select.prototype,
     'setMap',
@@ -102869,7 +115333,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.Circle',
-    ol.geom.Circle);
+    ol.geom.Circle,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.Circle.prototype,
@@ -102911,9 +115376,15 @@ goog.exportProperty(
     '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);
+    ol.geom.Geometry,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.Geometry.prototype,
@@ -102940,6 +115411,16 @@ goog.exportProperty(
     'applyTransform',
     ol.geom.Geometry.prototype.applyTransform);
 
+goog.exportProperty(
+    ol.geom.Geometry.prototype,
+    'intersectsExtent',
+    ol.geom.Geometry.prototype.intersectsExtent);
+
+goog.exportProperty(
+    ol.geom.Geometry.prototype,
+    'translate',
+    ol.geom.Geometry.prototype.translate);
+
 goog.exportProperty(
     ol.geom.Geometry.prototype,
     'transform',
@@ -102947,7 +115428,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.GeometryCollection',
-    ol.geom.GeometryCollection);
+    ol.geom.GeometryCollection,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.GeometryCollection.prototype,
@@ -102969,14 +115451,30 @@ goog.exportProperty(
     '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);
+    ol.geom.LinearRing,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.LinearRing.prototype,
@@ -103005,7 +115503,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.LineString',
-    ol.geom.LineString);
+    ol.geom.LineString,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.LineString.prototype,
@@ -103037,6 +115536,11 @@ goog.exportProperty(
     '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',
@@ -103044,7 +115548,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.MultiLineString',
-    ol.geom.MultiLineString);
+    ol.geom.MultiLineString,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.MultiLineString.prototype,
@@ -103081,6 +115586,11 @@ goog.exportProperty(
     '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',
@@ -103088,7 +115598,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.MultiPoint',
-    ol.geom.MultiPoint);
+    ol.geom.MultiPoint,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.MultiPoint.prototype,
@@ -103120,6 +115631,11 @@ goog.exportProperty(
     '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',
@@ -103127,7 +115643,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.MultiPolygon',
-    ol.geom.MultiPolygon);
+    ol.geom.MultiPolygon,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.MultiPolygon.prototype,
@@ -103169,6 +115686,11 @@ goog.exportProperty(
     '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',
@@ -103176,7 +115698,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.Point',
-    ol.geom.Point);
+    ol.geom.Point,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.Point.prototype,
@@ -103193,6 +115716,11 @@ goog.exportProperty(
     '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',
@@ -103200,7 +115728,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.Polygon',
-    ol.geom.Polygon);
+    ol.geom.Polygon,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.Polygon.prototype,
@@ -103247,6 +115776,11 @@ goog.exportProperty(
     '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',
@@ -103254,11 +115788,18 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.geom.Polygon.circular',
-    ol.geom.Polygon.circular);
+    ol.geom.Polygon.circular,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.geom.Polygon.fromExtent',
+    ol.geom.Polygon.fromExtent,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.geom.SimpleGeometry',
-    ol.geom.SimpleGeometry);
+    ol.geom.SimpleGeometry,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.geom.SimpleGeometry.prototype,
@@ -103280,13 +115821,25 @@ goog.exportProperty(
     '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.Feature',
-    ol.format.Feature);
+    ol.format.Feature,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.format.GeoJSON',
-    ol.format.GeoJSON);
+    ol.format.GeoJSON,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.GeoJSON.prototype,
@@ -103313,6 +115866,11 @@ goog.exportProperty(
     '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',
@@ -103320,26 +115878,23 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.format.GeoJSON.prototype,
-    'writeGeometry',
-    ol.format.GeoJSON.prototype.writeGeometry);
-
-goog.exportSymbol(
-    'ol.format.GML',
-    ol.format.GML);
+    'writeFeaturesObject',
+    ol.format.GeoJSON.prototype.writeFeaturesObject);
 
 goog.exportProperty(
-    ol.format.GML.prototype,
-    'readFeatures',
-    ol.format.GML.prototype.readFeatures);
+    ol.format.GeoJSON.prototype,
+    'writeGeometry',
+    ol.format.GeoJSON.prototype.writeGeometry);
 
 goog.exportProperty(
-    ol.format.GML.prototype,
-    'writeFeatures',
-    ol.format.GML.prototype.writeFeatures);
+    ol.format.GeoJSON.prototype,
+    'writeGeometryObject',
+    ol.format.GeoJSON.prototype.writeGeometryObject);
 
 goog.exportSymbol(
     'ol.format.GPX',
-    ol.format.GPX);
+    ol.format.GPX,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.GPX.prototype,
@@ -103361,9 +115916,15 @@ goog.exportProperty(
     '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);
+    ol.format.IGC,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.IGC.prototype,
@@ -103382,7 +115943,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.format.KML',
-    ol.format.KML);
+    ol.format.KML,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.KML.prototype,
@@ -103409,9 +115971,15 @@ goog.exportProperty(
     'writeFeatures',
     ol.format.KML.prototype.writeFeatures);
 
+goog.exportProperty(
+    ol.format.KML.prototype,
+    'writeFeaturesNode',
+    ol.format.KML.prototype.writeFeaturesNode);
+
 goog.exportSymbol(
     'ol.format.OSMXML',
-    ol.format.OSMXML);
+    ol.format.OSMXML,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.OSMXML.prototype,
@@ -103425,23 +115993,28 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.format.Polyline',
-    ol.format.Polyline);
+    ol.format.Polyline,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.format.Polyline.encodeDeltas',
-    ol.format.Polyline.encodeDeltas);
+    ol.format.Polyline.encodeDeltas,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.format.Polyline.decodeDeltas',
-    ol.format.Polyline.decodeDeltas);
+    ol.format.Polyline.decodeDeltas,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.format.Polyline.encodeFloats',
-    ol.format.Polyline.encodeFloats);
+    ol.format.Polyline.encodeFloats,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.format.Polyline.decodeFloats',
-    ol.format.Polyline.decodeFloats);
+    ol.format.Polyline.decodeFloats,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.Polyline.prototype,
@@ -103470,7 +116043,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.format.TopoJSON',
-    ol.format.TopoJSON);
+    ol.format.TopoJSON,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.TopoJSON.prototype,
@@ -103484,7 +116058,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.format.WFS',
-    ol.format.WFS);
+    ol.format.WFS,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.WFS.prototype,
@@ -103518,7 +116093,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.format.WKT',
-    ol.format.WKT);
+    ol.format.WKT,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.format.WKT.prototype,
@@ -103552,64 +116128,138 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.format.WMSCapabilities',
-    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.GML2',
+    ol.format.GML2,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.format.GML3',
+    ol.format.GML3,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.format.GML3.prototype,
+    'writeGeometryNode',
+    ol.format.GML3.prototype.writeGeometryNode);
+
+goog.exportProperty(
+    ol.format.GML3.prototype,
+    'writeFeatures',
+    ol.format.GML3.prototype.writeFeatures);
+
+goog.exportProperty(
+    ol.format.GML3.prototype,
+    'writeFeaturesNode',
+    ol.format.GML3.prototype.writeFeaturesNode);
+
+goog.exportSymbol(
+    'ol.format.GML',
+    ol.format.GML,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.format.GML.prototype,
+    'writeFeatures',
+    ol.format.GML.prototype.writeFeatures);
+
+goog.exportProperty(
+    ol.format.GML.prototype,
+    'writeFeaturesNode',
+    ol.format.GML.prototype.writeFeaturesNode);
+
+goog.exportSymbol(
+    'ol.format.GMLBase',
+    ol.format.GMLBase,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.format.GMLBase.prototype,
+    'readFeatures',
+    ol.format.GMLBase.prototype.readFeatures);
+
 goog.exportSymbol(
     'ol.events.condition.altKeyOnly',
-    ol.events.condition.altKeyOnly);
+    ol.events.condition.altKeyOnly,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.altShiftKeysOnly',
-    ol.events.condition.altShiftKeysOnly);
+    ol.events.condition.altShiftKeysOnly,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.always',
-    ol.events.condition.always);
+    ol.events.condition.always,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.click',
-    ol.events.condition.click);
+    ol.events.condition.click,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.mouseMove',
-    ol.events.condition.mouseMove);
+    ol.events.condition.mouseMove,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.never',
-    ol.events.condition.never);
+    ol.events.condition.never,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.singleClick',
-    ol.events.condition.singleClick);
+    ol.events.condition.singleClick,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.noModifierKeys',
-    ol.events.condition.noModifierKeys);
+    ol.events.condition.noModifierKeys,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.platformModifierKeyOnly',
-    ol.events.condition.platformModifierKeyOnly);
+    ol.events.condition.platformModifierKeyOnly,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.shiftKeyOnly',
-    ol.events.condition.shiftKeyOnly);
+    ol.events.condition.shiftKeyOnly,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.targetNotEditable',
-    ol.events.condition.targetNotEditable);
+    ol.events.condition.targetNotEditable,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.events.condition.mouseOnly',
-    ol.events.condition.mouseOnly);
+    ol.events.condition.mouseOnly,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.dom.Input',
-    ol.dom.Input);
+    ol.dom.Input,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.dom.Input.prototype,
@@ -103633,7 +116283,13 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.control.Attribution',
-    ol.control.Attribution);
+    ol.control.Attribution,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.control.Attribution.render',
+    ol.control.Attribution.render,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.control.Attribution.prototype,
@@ -103657,7 +116313,8 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.control.Control',
-    ol.control.Control);
+    ol.control.Control,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.control.Control.prototype,
@@ -103671,15 +116328,23 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.control.defaults',
-    ol.control.defaults);
+    ol.control.defaults,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.control.FullScreen',
-    ol.control.FullScreen);
+    ol.control.FullScreen,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.control.MousePosition',
-    ol.control.MousePosition);
+    ol.control.MousePosition,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.control.MousePosition.render',
+    ol.control.MousePosition.render,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.control.MousePosition.prototype,
@@ -103706,19 +116371,66 @@ goog.exportProperty(
     'setProjection',
     ol.control.MousePosition.prototype.setProjection);
 
+goog.exportSymbol(
+    'ol.control.OverviewMap',
+    ol.control.OverviewMap,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.control.OverviewMap.prototype,
+    'setMap',
+    ol.control.OverviewMap.prototype.setMap);
+
+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.exportSymbol(
     'ol.control.Rotate',
-    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);
+    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',
@@ -103726,28 +116438,38 @@ goog.exportProperty(
 
 goog.exportSymbol(
     'ol.control.Zoom',
-    ol.control.Zoom);
+    ol.control.Zoom,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.control.ZoomSlider',
-    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);
+    ol.control.ZoomToExtent,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.color.asArray',
-    ol.color.asArray);
+    ol.color.asArray,
+    OPENLAYERS);
 
 goog.exportSymbol(
     'ol.color.asString',
-    ol.color.asString);
+    ol.color.asString,
+    OPENLAYERS);
 
 goog.exportProperty(
     ol.Object.prototype,
-    'dispatchChangeEvent',
-    ol.Object.prototype.dispatchChangeEvent);
+    'changed',
+    ol.Object.prototype.changed);
 
 goog.exportProperty(
     ol.Object.prototype,
@@ -103816,8 +116538,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.Collection.prototype,
-    'dispatchChangeEvent',
-    ol.Collection.prototype.dispatchChangeEvent);
+    'changed',
+    ol.Collection.prototype.changed);
 
 goog.exportProperty(
     ol.Collection.prototype,
@@ -103886,8 +116608,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.DeviceOrientation.prototype,
-    'dispatchChangeEvent',
-    ol.DeviceOrientation.prototype.dispatchChangeEvent);
+    'changed',
+    ol.DeviceOrientation.prototype.changed);
 
 goog.exportProperty(
     ol.DeviceOrientation.prototype,
@@ -103956,8 +116678,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.Feature.prototype,
-    'dispatchChangeEvent',
-    ol.Feature.prototype.dispatchChangeEvent);
+    'changed',
+    ol.Feature.prototype.changed);
 
 goog.exportProperty(
     ol.Feature.prototype,
@@ -104026,8 +116748,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.Geolocation.prototype,
-    'dispatchChangeEvent',
-    ol.Geolocation.prototype.dispatchChangeEvent);
+    'changed',
+    ol.Geolocation.prototype.changed);
 
 goog.exportProperty(
     ol.Geolocation.prototype,
@@ -104101,8 +116823,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.Map.prototype,
-    'dispatchChangeEvent',
-    ol.Map.prototype.dispatchChangeEvent);
+    'changed',
+    ol.Map.prototype.changed);
 
 goog.exportProperty(
     ol.Map.prototype,
@@ -104216,8 +116938,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.Overlay.prototype,
-    'dispatchChangeEvent',
-    ol.Overlay.prototype.dispatchChangeEvent);
+    'changed',
+    ol.Overlay.prototype.changed);
 
 goog.exportProperty(
     ol.Overlay.prototype,
@@ -104286,8 +117008,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.View.prototype,
-    'dispatchChangeEvent',
-    ol.View.prototype.dispatchChangeEvent);
+    'changed',
+    ol.View.prototype.changed);
 
 goog.exportProperty(
     ol.View.prototype,
@@ -104339,6 +117061,16 @@ goog.exportProperty(
     'getResolutions',
     ol.tilegrid.WMTS.prototype.getResolutions);
 
+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',
@@ -104369,6 +117101,16 @@ goog.exportProperty(
     'getResolutions',
     ol.tilegrid.XYZ.prototype.getResolutions);
 
+goog.exportProperty(
+    ol.tilegrid.XYZ.prototype,
+    'getTileCoordForCoordAndResolution',
+    ol.tilegrid.XYZ.prototype.getTileCoordForCoordAndResolution);
+
+goog.exportProperty(
+    ol.tilegrid.XYZ.prototype,
+    'getTileCoordForCoordAndZ',
+    ol.tilegrid.XYZ.prototype.getTileCoordForCoordAndZ);
+
 goog.exportProperty(
     ol.tilegrid.XYZ.prototype,
     'getTileSize',
@@ -104399,11 +117141,31 @@ goog.exportProperty(
     'getResolutions',
     ol.tilegrid.Zoomify.prototype.getResolutions);
 
+goog.exportProperty(
+    ol.tilegrid.Zoomify.prototype,
+    'getTileCoordForCoordAndResolution',
+    ol.tilegrid.Zoomify.prototype.getTileCoordForCoordAndResolution);
+
+goog.exportProperty(
+    ol.tilegrid.Zoomify.prototype,
+    'getTileCoordForCoordAndZ',
+    ol.tilegrid.Zoomify.prototype.getTileCoordForCoordAndZ);
+
 goog.exportProperty(
     ol.tilegrid.Zoomify.prototype,
     'getTileSize',
     ol.tilegrid.Zoomify.prototype.getTileSize);
 
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getOpacity',
+    ol.style.Circle.prototype.getOpacity);
+
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getRotateWithView',
+    ol.style.Circle.prototype.getRotateWithView);
+
 goog.exportProperty(
     ol.style.Circle.prototype,
     'getRotation',
@@ -104414,6 +117176,31 @@ goog.exportProperty(
     'getScale',
     ol.style.Circle.prototype.getScale);
 
+goog.exportProperty(
+    ol.style.Circle.prototype,
+    'getSnapToPixel',
+    ol.style.Circle.prototype.getSnapToPixel);
+
+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',
@@ -104424,10 +117211,60 @@ goog.exportProperty(
     'getScale',
     ol.style.Icon.prototype.getScale);
 
+goog.exportProperty(
+    ol.style.Icon.prototype,
+    'getSnapToPixel',
+    ol.style.Icon.prototype.getSnapToPixel);
+
+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,
+    'setRotation',
+    ol.style.RegularShape.prototype.setRotation);
+
+goog.exportProperty(
+    ol.style.RegularShape.prototype,
+    'setScale',
+    ol.style.RegularShape.prototype.setScale);
+
 goog.exportProperty(
     ol.source.Source.prototype,
-    'dispatchChangeEvent',
-    ol.source.Source.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.Source.prototype.changed);
 
 goog.exportProperty(
     ol.source.Source.prototype,
@@ -104476,8 +117313,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.Tile.prototype,
-    'dispatchChangeEvent',
-    ol.source.Tile.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.Tile.prototype.changed);
 
 goog.exportProperty(
     ol.source.Tile.prototype,
@@ -104531,8 +117368,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.TileImage.prototype,
-    'dispatchChangeEvent',
-    ol.source.TileImage.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.TileImage.prototype.changed);
 
 goog.exportProperty(
     ol.source.TileImage.prototype,
@@ -104606,8 +117443,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.BingMaps.prototype,
-    'dispatchChangeEvent',
-    ol.source.BingMaps.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.BingMaps.prototype.changed);
 
 goog.exportProperty(
     ol.source.BingMaps.prototype,
@@ -104656,8 +117493,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.Vector.prototype,
-    'dispatchChangeEvent',
-    ol.source.Vector.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.Vector.prototype.changed);
 
 goog.exportProperty(
     ol.source.Vector.prototype,
@@ -104709,6 +117546,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.Cluster.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.Cluster.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.Cluster.prototype,
     'getFeatures',
@@ -104761,8 +117603,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.Cluster.prototype,
-    'dispatchChangeEvent',
-    ol.source.Cluster.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.Cluster.prototype.changed);
 
 goog.exportProperty(
     ol.source.Cluster.prototype,
@@ -104814,6 +117656,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.FormatVector.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.FormatVector.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.FormatVector.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.FormatVector.prototype,
     'getFeatures',
@@ -104866,8 +117713,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.FormatVector.prototype,
-    'dispatchChangeEvent',
-    ol.source.FormatVector.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.FormatVector.prototype.changed);
 
 goog.exportProperty(
     ol.source.FormatVector.prototype,
@@ -104924,6 +117771,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.StaticVector.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.StaticVector.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.StaticVector.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.StaticVector.prototype,
     'getFeatures',
@@ -104976,8 +117828,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.StaticVector.prototype,
-    'dispatchChangeEvent',
-    ol.source.StaticVector.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.StaticVector.prototype.changed);
 
 goog.exportProperty(
     ol.source.StaticVector.prototype,
@@ -105034,6 +117886,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.GeoJSON.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.GeoJSON.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.GeoJSON.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.GeoJSON.prototype,
     'getFeatures',
@@ -105086,8 +117943,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.GeoJSON.prototype,
-    'dispatchChangeEvent',
-    ol.source.GeoJSON.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.GeoJSON.prototype.changed);
 
 goog.exportProperty(
     ol.source.GeoJSON.prototype,
@@ -105144,6 +118001,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.GPX.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.GPX.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.GPX.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.GPX.prototype,
     'getFeatures',
@@ -105196,8 +118058,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.GPX.prototype,
-    'dispatchChangeEvent',
-    ol.source.GPX.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.GPX.prototype.changed);
 
 goog.exportProperty(
     ol.source.GPX.prototype,
@@ -105254,6 +118116,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.IGC.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.IGC.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.IGC.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.IGC.prototype,
     'getFeatures',
@@ -105306,8 +118173,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.IGC.prototype,
-    'dispatchChangeEvent',
-    ol.source.IGC.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.IGC.prototype.changed);
 
 goog.exportProperty(
     ol.source.IGC.prototype,
@@ -105356,8 +118223,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.Image.prototype,
-    'dispatchChangeEvent',
-    ol.source.Image.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.Image.prototype.changed);
 
 goog.exportProperty(
     ol.source.Image.prototype,
@@ -105406,8 +118273,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.ImageCanvas.prototype,
-    'dispatchChangeEvent',
-    ol.source.ImageCanvas.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.ImageCanvas.prototype.changed);
 
 goog.exportProperty(
     ol.source.ImageCanvas.prototype,
@@ -105456,8 +118323,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.ImageMapGuide.prototype,
-    'dispatchChangeEvent',
-    ol.source.ImageMapGuide.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.ImageMapGuide.prototype.changed);
 
 goog.exportProperty(
     ol.source.ImageMapGuide.prototype,
@@ -105506,8 +118373,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.ImageStatic.prototype,
-    'dispatchChangeEvent',
-    ol.source.ImageStatic.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.ImageStatic.prototype.changed);
 
 goog.exportProperty(
     ol.source.ImageStatic.prototype,
@@ -105556,8 +118423,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.ImageVector.prototype,
-    'dispatchChangeEvent',
-    ol.source.ImageVector.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.ImageVector.prototype.changed);
 
 goog.exportProperty(
     ol.source.ImageVector.prototype,
@@ -105606,8 +118473,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.ImageWMS.prototype,
-    'dispatchChangeEvent',
-    ol.source.ImageWMS.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.ImageWMS.prototype.changed);
 
 goog.exportProperty(
     ol.source.ImageWMS.prototype,
@@ -105664,6 +118531,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.KML.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.KML.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.KML.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.KML.prototype,
     'getFeatures',
@@ -105716,8 +118588,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.KML.prototype,
-    'dispatchChangeEvent',
-    ol.source.KML.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.KML.prototype.changed);
 
 goog.exportProperty(
     ol.source.KML.prototype,
@@ -105786,8 +118658,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.XYZ.prototype,
-    'dispatchChangeEvent',
-    ol.source.XYZ.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.XYZ.prototype.changed);
 
 goog.exportProperty(
     ol.source.XYZ.prototype,
@@ -105866,8 +118738,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.MapQuest.prototype,
-    'dispatchChangeEvent',
-    ol.source.MapQuest.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.MapQuest.prototype.changed);
 
 goog.exportProperty(
     ol.source.MapQuest.prototype,
@@ -105946,8 +118818,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.OSM.prototype,
-    'dispatchChangeEvent',
-    ol.source.OSM.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.OSM.prototype.changed);
 
 goog.exportProperty(
     ol.source.OSM.prototype,
@@ -106004,6 +118876,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.OSMXML.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.OSMXML.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.OSMXML.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.OSMXML.prototype,
     'getFeatures',
@@ -106056,8 +118933,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.OSMXML.prototype,
-    'dispatchChangeEvent',
-    ol.source.OSMXML.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.OSMXML.prototype.changed);
 
 goog.exportProperty(
     ol.source.OSMXML.prototype,
@@ -106104,6 +118981,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.ServerVector.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.ServerVector.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.ServerVector.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.ServerVector.prototype,
     'getFeatures',
@@ -106156,8 +119038,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.ServerVector.prototype,
-    'dispatchChangeEvent',
-    ol.source.ServerVector.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.ServerVector.prototype.changed);
 
 goog.exportProperty(
     ol.source.ServerVector.prototype,
@@ -106236,8 +119118,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.Stamen.prototype,
-    'dispatchChangeEvent',
-    ol.source.Stamen.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.Stamen.prototype.changed);
 
 goog.exportProperty(
     ol.source.Stamen.prototype,
@@ -106291,8 +119173,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.TileDebug.prototype,
-    'dispatchChangeEvent',
-    ol.source.TileDebug.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.TileDebug.prototype.changed);
 
 goog.exportProperty(
     ol.source.TileDebug.prototype,
@@ -106366,8 +119248,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.TileJSON.prototype,
-    'dispatchChangeEvent',
-    ol.source.TileJSON.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.TileJSON.prototype.changed);
 
 goog.exportProperty(
     ol.source.TileJSON.prototype,
@@ -106394,11 +119276,71 @@ goog.exportProperty(
     'unByKey',
     ol.source.TileJSON.prototype.unByKey);
 
+goog.exportProperty(
+    ol.source.TileUTFGrid.prototype,
+    'getTileGrid',
+    ol.source.TileUTFGrid.prototype.getTileGrid);
+
+goog.exportProperty(
+    ol.source.TileUTFGrid.prototype,
+    'getAttributions',
+    ol.source.TileUTFGrid.prototype.getAttributions);
+
+goog.exportProperty(
+    ol.source.TileUTFGrid.prototype,
+    'getLogo',
+    ol.source.TileUTFGrid.prototype.getLogo);
+
+goog.exportProperty(
+    ol.source.TileUTFGrid.prototype,
+    'getProjection',
+    ol.source.TileUTFGrid.prototype.getProjection);
+
+goog.exportProperty(
+    ol.source.TileUTFGrid.prototype,
+    'getState',
+    ol.source.TileUTFGrid.prototype.getState);
+
+goog.exportProperty(
+    ol.source.TileUTFGrid.prototype,
+    'changed',
+    ol.source.TileUTFGrid.prototype.changed);
+
+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.TileVector.prototype,
     'readFeatures',
     ol.source.TileVector.prototype.readFeatures);
 
+goog.exportProperty(
+    ol.source.TileVector.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.TileVector.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.TileVector.prototype,
     'getFeaturesAtCoordinate',
@@ -106431,8 +119373,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.TileVector.prototype,
-    'dispatchChangeEvent',
-    ol.source.TileVector.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.TileVector.prototype.changed);
 
 goog.exportProperty(
     ol.source.TileVector.prototype,
@@ -106506,8 +119448,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.TileWMS.prototype,
-    'dispatchChangeEvent',
-    ol.source.TileWMS.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.TileWMS.prototype.changed);
 
 goog.exportProperty(
     ol.source.TileWMS.prototype,
@@ -106564,6 +119506,11 @@ goog.exportProperty(
     'forEachFeatureInExtent',
     ol.source.TopoJSON.prototype.forEachFeatureInExtent);
 
+goog.exportProperty(
+    ol.source.TopoJSON.prototype,
+    'forEachFeatureIntersectingExtent',
+    ol.source.TopoJSON.prototype.forEachFeatureIntersectingExtent);
+
 goog.exportProperty(
     ol.source.TopoJSON.prototype,
     'getFeatures',
@@ -106616,8 +119563,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.TopoJSON.prototype,
-    'dispatchChangeEvent',
-    ol.source.TopoJSON.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.TopoJSON.prototype.changed);
 
 goog.exportProperty(
     ol.source.TopoJSON.prototype,
@@ -106691,8 +119638,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.WMTS.prototype,
-    'dispatchChangeEvent',
-    ol.source.WMTS.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.WMTS.prototype.changed);
 
 goog.exportProperty(
     ol.source.WMTS.prototype,
@@ -106766,8 +119713,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.source.Zoomify.prototype,
-    'dispatchChangeEvent',
-    ol.source.Zoomify.prototype.dispatchChangeEvent);
+    'changed',
+    ol.source.Zoomify.prototype.changed);
 
 goog.exportProperty(
     ol.source.Zoomify.prototype,
@@ -106836,8 +119783,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.layer.Base.prototype,
-    'dispatchChangeEvent',
-    ol.layer.Base.prototype.dispatchChangeEvent);
+    'changed',
+    ol.layer.Base.prototype.changed);
 
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -106996,8 +119943,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.layer.Layer.prototype,
-    'dispatchChangeEvent',
-    ol.layer.Layer.prototype.dispatchChangeEvent);
+    'changed',
+    ol.layer.Layer.prototype.changed);
 
 goog.exportProperty(
     ol.layer.Layer.prototype,
@@ -107024,6 +119971,11 @@ goog.exportProperty(
     'unByKey',
     ol.layer.Layer.prototype.unByKey);
 
+goog.exportProperty(
+    ol.layer.Vector.prototype,
+    'setSource',
+    ol.layer.Vector.prototype.setSource);
+
 goog.exportProperty(
     ol.layer.Vector.prototype,
     'getBrightness',
@@ -107156,8 +120108,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.layer.Vector.prototype,
-    'dispatchChangeEvent',
-    ol.layer.Vector.prototype.dispatchChangeEvent);
+    'changed',
+    ol.layer.Vector.prototype.changed);
 
 goog.exportProperty(
     ol.layer.Vector.prototype,
@@ -107204,6 +120156,11 @@ goog.exportProperty(
     'setStyle',
     ol.layer.Heatmap.prototype.setStyle);
 
+goog.exportProperty(
+    ol.layer.Heatmap.prototype,
+    'setSource',
+    ol.layer.Heatmap.prototype.setSource);
+
 goog.exportProperty(
     ol.layer.Heatmap.prototype,
     'getBrightness',
@@ -107336,8 +120293,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.layer.Heatmap.prototype,
-    'dispatchChangeEvent',
-    ol.layer.Heatmap.prototype.dispatchChangeEvent);
+    'changed',
+    ol.layer.Heatmap.prototype.changed);
 
 goog.exportProperty(
     ol.layer.Heatmap.prototype,
@@ -107364,6 +120321,11 @@ goog.exportProperty(
     'unByKey',
     ol.layer.Heatmap.prototype.unByKey);
 
+goog.exportProperty(
+    ol.layer.Image.prototype,
+    'setSource',
+    ol.layer.Image.prototype.setSource);
+
 goog.exportProperty(
     ol.layer.Image.prototype,
     'getBrightness',
@@ -107496,8 +120458,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.layer.Image.prototype,
-    'dispatchChangeEvent',
-    ol.layer.Image.prototype.dispatchChangeEvent);
+    'changed',
+    ol.layer.Image.prototype.changed);
 
 goog.exportProperty(
     ol.layer.Image.prototype,
@@ -107656,8 +120618,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.layer.Group.prototype,
-    'dispatchChangeEvent',
-    ol.layer.Group.prototype.dispatchChangeEvent);
+    'changed',
+    ol.layer.Group.prototype.changed);
 
 goog.exportProperty(
     ol.layer.Group.prototype,
@@ -107684,6 +120646,11 @@ goog.exportProperty(
     'unByKey',
     ol.layer.Group.prototype.unByKey);
 
+goog.exportProperty(
+    ol.layer.Tile.prototype,
+    'setSource',
+    ol.layer.Tile.prototype.setSource);
+
 goog.exportProperty(
     ol.layer.Tile.prototype,
     'getBrightness',
@@ -107816,8 +120783,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.layer.Tile.prototype,
-    'dispatchChangeEvent',
-    ol.layer.Tile.prototype.dispatchChangeEvent);
+    'changed',
+    ol.layer.Tile.prototype.changed);
 
 goog.exportProperty(
     ol.layer.Tile.prototype,
@@ -107846,8 +120813,48 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.Interaction.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.Interaction.prototype.dispatchChangeEvent);
+    'bindTo',
+    ol.interaction.Interaction.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.Interaction.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.Interaction.prototype,
+    'unbindAll',
+    ol.interaction.Interaction.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.Interaction.prototype,
+    'changed',
+    ol.interaction.Interaction.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.Interaction.prototype,
@@ -107876,8 +120883,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.DoubleClickZoom.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.DoubleClickZoom.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.DoubleClickZoom.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.DoubleClickZoom.prototype,
+    'setActive',
+    ol.interaction.DoubleClickZoom.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.DoubleClickZoom.prototype,
+    'bindTo',
+    ol.interaction.DoubleClickZoom.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.DoubleClickZoom.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.DoubleClickZoom.prototype,
+    'unbindAll',
+    ol.interaction.DoubleClickZoom.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.DoubleClickZoom.prototype,
+    'changed',
+    ol.interaction.DoubleClickZoom.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.DoubleClickZoom.prototype,
@@ -107906,8 +120963,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.DragAndDrop.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.DragAndDrop.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.DragAndDrop.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.DragAndDrop.prototype,
+    'setActive',
+    ol.interaction.DragAndDrop.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.DragAndDrop.prototype,
+    'bindTo',
+    ol.interaction.DragAndDrop.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.DragAndDrop.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.DragAndDrop.prototype,
+    'unbindAll',
+    ol.interaction.DragAndDrop.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.DragAndDrop.prototype,
+    'changed',
+    ol.interaction.DragAndDrop.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.DragAndDrop.prototype,
@@ -107936,8 +121043,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.Pointer.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.Pointer.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.Pointer.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.Pointer.prototype,
+    'setActive',
+    ol.interaction.Pointer.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.Pointer.prototype,
+    'bindTo',
+    ol.interaction.Pointer.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.Pointer.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.Pointer.prototype,
+    'unbindAll',
+    ol.interaction.Pointer.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.Pointer.prototype,
+    'changed',
+    ol.interaction.Pointer.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.Pointer.prototype,
@@ -107966,8 +121123,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.DragBox.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.DragBox.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.DragBox.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.DragBox.prototype,
+    'setActive',
+    ol.interaction.DragBox.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.DragBox.prototype,
+    'bindTo',
+    ol.interaction.DragBox.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.DragBox.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.DragBox.prototype,
+    'unbindAll',
+    ol.interaction.DragBox.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.DragBox.prototype,
+    'changed',
+    ol.interaction.DragBox.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.DragBox.prototype,
@@ -107996,8 +121203,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.DragPan.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.DragPan.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.DragPan.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.DragPan.prototype,
+    'setActive',
+    ol.interaction.DragPan.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.DragPan.prototype,
+    'bindTo',
+    ol.interaction.DragPan.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.DragPan.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.DragPan.prototype,
+    'unbindAll',
+    ol.interaction.DragPan.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.DragPan.prototype,
+    'changed',
+    ol.interaction.DragPan.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.DragPan.prototype,
@@ -108026,8 +121283,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.DragRotateAndZoom.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.DragRotateAndZoom.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.DragRotateAndZoom.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.DragRotateAndZoom.prototype,
+    'setActive',
+    ol.interaction.DragRotateAndZoom.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.DragRotateAndZoom.prototype,
+    'bindTo',
+    ol.interaction.DragRotateAndZoom.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.DragRotateAndZoom.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.DragRotateAndZoom.prototype,
+    'unbindAll',
+    ol.interaction.DragRotateAndZoom.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.DragRotateAndZoom.prototype,
+    'changed',
+    ol.interaction.DragRotateAndZoom.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.DragRotateAndZoom.prototype,
@@ -108056,8 +121363,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.DragRotate.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.DragRotate.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.DragRotate.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.DragRotate.prototype,
+    'setActive',
+    ol.interaction.DragRotate.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.DragRotate.prototype,
+    'bindTo',
+    ol.interaction.DragRotate.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.DragRotate.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.DragRotate.prototype,
+    'unbindAll',
+    ol.interaction.DragRotate.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.DragRotate.prototype,
+    'changed',
+    ol.interaction.DragRotate.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.DragRotate.prototype,
@@ -108091,8 +121448,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.DragZoom.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.DragZoom.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.DragZoom.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.DragZoom.prototype,
+    'setActive',
+    ol.interaction.DragZoom.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.DragZoom.prototype,
+    'bindTo',
+    ol.interaction.DragZoom.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.DragZoom.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.DragZoom.prototype,
+    'unbindAll',
+    ol.interaction.DragZoom.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.DragZoom.prototype,
+    'changed',
+    ol.interaction.DragZoom.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.DragZoom.prototype,
@@ -108121,8 +121528,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.Draw.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.Draw.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.Draw.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.Draw.prototype,
+    'setActive',
+    ol.interaction.Draw.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.Draw.prototype,
+    'bindTo',
+    ol.interaction.Draw.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.Draw.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.Draw.prototype,
+    'unbindAll',
+    ol.interaction.Draw.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.Draw.prototype,
+    'changed',
+    ol.interaction.Draw.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.Draw.prototype,
@@ -108151,8 +121608,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.KeyboardPan.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.KeyboardPan.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.KeyboardPan.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.KeyboardPan.prototype,
+    'setActive',
+    ol.interaction.KeyboardPan.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.KeyboardPan.prototype,
+    'bindTo',
+    ol.interaction.KeyboardPan.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.KeyboardPan.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.KeyboardPan.prototype,
+    'unbindAll',
+    ol.interaction.KeyboardPan.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.KeyboardPan.prototype,
+    'changed',
+    ol.interaction.KeyboardPan.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.KeyboardPan.prototype,
@@ -108181,8 +121688,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.KeyboardZoom.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.KeyboardZoom.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.KeyboardZoom.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.KeyboardZoom.prototype,
+    'setActive',
+    ol.interaction.KeyboardZoom.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.KeyboardZoom.prototype,
+    'bindTo',
+    ol.interaction.KeyboardZoom.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.KeyboardZoom.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.KeyboardZoom.prototype,
+    'unbindAll',
+    ol.interaction.KeyboardZoom.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.KeyboardZoom.prototype,
+    'changed',
+    ol.interaction.KeyboardZoom.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.KeyboardZoom.prototype,
@@ -108211,8 +121768,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.Modify.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.Modify.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.Modify.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.Modify.prototype,
+    'setActive',
+    ol.interaction.Modify.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.Modify.prototype,
+    'bindTo',
+    ol.interaction.Modify.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.Modify.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.Modify.prototype,
+    'unbindAll',
+    ol.interaction.Modify.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.Modify.prototype,
+    'changed',
+    ol.interaction.Modify.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.Modify.prototype,
@@ -108241,8 +121848,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.MouseWheelZoom.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.MouseWheelZoom.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.MouseWheelZoom.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.MouseWheelZoom.prototype,
+    'setActive',
+    ol.interaction.MouseWheelZoom.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.MouseWheelZoom.prototype,
+    'bindTo',
+    ol.interaction.MouseWheelZoom.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.MouseWheelZoom.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.MouseWheelZoom.prototype,
+    'unbindAll',
+    ol.interaction.MouseWheelZoom.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.MouseWheelZoom.prototype,
+    'changed',
+    ol.interaction.MouseWheelZoom.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.MouseWheelZoom.prototype,
@@ -108271,8 +121928,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.PinchRotate.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.PinchRotate.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.PinchRotate.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.PinchRotate.prototype,
+    'setActive',
+    ol.interaction.PinchRotate.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.PinchRotate.prototype,
+    'bindTo',
+    ol.interaction.PinchRotate.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.PinchRotate.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.PinchRotate.prototype,
+    'unbindAll',
+    ol.interaction.PinchRotate.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.PinchRotate.prototype,
+    'changed',
+    ol.interaction.PinchRotate.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.PinchRotate.prototype,
@@ -108301,8 +122008,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.PinchZoom.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.PinchZoom.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.PinchZoom.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.PinchZoom.prototype,
+    'setActive',
+    ol.interaction.PinchZoom.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.PinchZoom.prototype,
+    'bindTo',
+    ol.interaction.PinchZoom.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.PinchZoom.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.PinchZoom.prototype,
+    'unbindAll',
+    ol.interaction.PinchZoom.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.PinchZoom.prototype,
+    'changed',
+    ol.interaction.PinchZoom.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.PinchZoom.prototype,
@@ -108331,8 +122088,58 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.Select.prototype,
-    'dispatchChangeEvent',
-    ol.interaction.Select.prototype.dispatchChangeEvent);
+    'getActive',
+    ol.interaction.Select.prototype.getActive);
+
+goog.exportProperty(
+    ol.interaction.Select.prototype,
+    'setActive',
+    ol.interaction.Select.prototype.setActive);
+
+goog.exportProperty(
+    ol.interaction.Select.prototype,
+    'bindTo',
+    ol.interaction.Select.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.interaction.Select.prototype.unbind);
+
+goog.exportProperty(
+    ol.interaction.Select.prototype,
+    'unbindAll',
+    ol.interaction.Select.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.interaction.Select.prototype,
+    'changed',
+    ol.interaction.Select.prototype.changed);
 
 goog.exportProperty(
     ol.interaction.Select.prototype,
@@ -108361,8 +122168,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.Geometry.prototype,
-    'dispatchChangeEvent',
-    ol.geom.Geometry.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.Geometry.prototype.changed);
 
 goog.exportProperty(
     ol.geom.Geometry.prototype,
@@ -108404,6 +122211,11 @@ goog.exportProperty(
     'getType',
     ol.geom.SimpleGeometry.prototype.getType);
 
+goog.exportProperty(
+    ol.geom.SimpleGeometry.prototype,
+    'intersectsExtent',
+    ol.geom.SimpleGeometry.prototype.intersectsExtent);
+
 goog.exportProperty(
     ol.geom.SimpleGeometry.prototype,
     'transform',
@@ -108411,8 +122223,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.SimpleGeometry.prototype,
-    'dispatchChangeEvent',
-    ol.geom.SimpleGeometry.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.SimpleGeometry.prototype.changed);
 
 goog.exportProperty(
     ol.geom.SimpleGeometry.prototype,
@@ -108454,6 +122266,16 @@ goog.exportProperty(
     'getLayout',
     ol.geom.Circle.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.Circle.prototype,
+    'applyTransform',
+    ol.geom.Circle.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.Circle.prototype,
+    'translate',
+    ol.geom.Circle.prototype.translate);
+
 goog.exportProperty(
     ol.geom.Circle.prototype,
     'getClosestPoint',
@@ -108461,13 +122283,13 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.Circle.prototype,
-    'transform',
-    ol.geom.Circle.prototype.transform);
+    'intersectsExtent',
+    ol.geom.Circle.prototype.intersectsExtent);
 
 goog.exportProperty(
     ol.geom.Circle.prototype,
-    'dispatchChangeEvent',
-    ol.geom.Circle.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.Circle.prototype.changed);
 
 goog.exportProperty(
     ol.geom.Circle.prototype,
@@ -108506,8 +122328,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.GeometryCollection.prototype,
-    'dispatchChangeEvent',
-    ol.geom.GeometryCollection.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.GeometryCollection.prototype.changed);
 
 goog.exportProperty(
     ol.geom.GeometryCollection.prototype,
@@ -108554,11 +122376,26 @@ goog.exportProperty(
     'getLayout',
     ol.geom.LinearRing.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.LinearRing.prototype,
+    'applyTransform',
+    ol.geom.LinearRing.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.LinearRing.prototype,
+    'translate',
+    ol.geom.LinearRing.prototype.translate);
+
 goog.exportProperty(
     ol.geom.LinearRing.prototype,
     'getClosestPoint',
     ol.geom.LinearRing.prototype.getClosestPoint);
 
+goog.exportProperty(
+    ol.geom.LinearRing.prototype,
+    'intersectsExtent',
+    ol.geom.LinearRing.prototype.intersectsExtent);
+
 goog.exportProperty(
     ol.geom.LinearRing.prototype,
     'transform',
@@ -108566,8 +122403,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.LinearRing.prototype,
-    'dispatchChangeEvent',
-    ol.geom.LinearRing.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.LinearRing.prototype.changed);
 
 goog.exportProperty(
     ol.geom.LinearRing.prototype,
@@ -108614,6 +122451,16 @@ goog.exportProperty(
     'getLayout',
     ol.geom.LineString.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.LineString.prototype,
+    'applyTransform',
+    ol.geom.LineString.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.LineString.prototype,
+    'translate',
+    ol.geom.LineString.prototype.translate);
+
 goog.exportProperty(
     ol.geom.LineString.prototype,
     'getClosestPoint',
@@ -108626,8 +122473,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.LineString.prototype,
-    'dispatchChangeEvent',
-    ol.geom.LineString.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.LineString.prototype.changed);
 
 goog.exportProperty(
     ol.geom.LineString.prototype,
@@ -108674,6 +122521,16 @@ goog.exportProperty(
     'getLayout',
     ol.geom.MultiLineString.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.MultiLineString.prototype,
+    'applyTransform',
+    ol.geom.MultiLineString.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.MultiLineString.prototype,
+    'translate',
+    ol.geom.MultiLineString.prototype.translate);
+
 goog.exportProperty(
     ol.geom.MultiLineString.prototype,
     'getClosestPoint',
@@ -108686,8 +122543,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.MultiLineString.prototype,
-    'dispatchChangeEvent',
-    ol.geom.MultiLineString.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.MultiLineString.prototype.changed);
 
 goog.exportProperty(
     ol.geom.MultiLineString.prototype,
@@ -108734,6 +122591,16 @@ goog.exportProperty(
     'getLayout',
     ol.geom.MultiPoint.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.MultiPoint.prototype,
+    'applyTransform',
+    ol.geom.MultiPoint.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.MultiPoint.prototype,
+    'translate',
+    ol.geom.MultiPoint.prototype.translate);
+
 goog.exportProperty(
     ol.geom.MultiPoint.prototype,
     'getClosestPoint',
@@ -108746,8 +122613,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.MultiPoint.prototype,
-    'dispatchChangeEvent',
-    ol.geom.MultiPoint.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.MultiPoint.prototype.changed);
 
 goog.exportProperty(
     ol.geom.MultiPoint.prototype,
@@ -108794,6 +122661,16 @@ goog.exportProperty(
     'getLayout',
     ol.geom.MultiPolygon.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.MultiPolygon.prototype,
+    'applyTransform',
+    ol.geom.MultiPolygon.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.MultiPolygon.prototype,
+    'translate',
+    ol.geom.MultiPolygon.prototype.translate);
+
 goog.exportProperty(
     ol.geom.MultiPolygon.prototype,
     'getClosestPoint',
@@ -108806,8 +122683,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.MultiPolygon.prototype,
-    'dispatchChangeEvent',
-    ol.geom.MultiPolygon.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.MultiPolygon.prototype.changed);
 
 goog.exportProperty(
     ol.geom.MultiPolygon.prototype,
@@ -108849,6 +122726,16 @@ goog.exportProperty(
     'getLayout',
     ol.geom.Point.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.Point.prototype,
+    'applyTransform',
+    ol.geom.Point.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.Point.prototype,
+    'translate',
+    ol.geom.Point.prototype.translate);
+
 goog.exportProperty(
     ol.geom.Point.prototype,
     'getClosestPoint',
@@ -108861,8 +122748,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.Point.prototype,
-    'dispatchChangeEvent',
-    ol.geom.Point.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.Point.prototype.changed);
 
 goog.exportProperty(
     ol.geom.Point.prototype,
@@ -108909,6 +122796,16 @@ goog.exportProperty(
     'getLayout',
     ol.geom.Polygon.prototype.getLayout);
 
+goog.exportProperty(
+    ol.geom.Polygon.prototype,
+    'applyTransform',
+    ol.geom.Polygon.prototype.applyTransform);
+
+goog.exportProperty(
+    ol.geom.Polygon.prototype,
+    'translate',
+    ol.geom.Polygon.prototype.translate);
+
 goog.exportProperty(
     ol.geom.Polygon.prototype,
     'getClosestPoint',
@@ -108921,8 +122818,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.geom.Polygon.prototype,
-    'dispatchChangeEvent',
-    ol.geom.Polygon.prototype.dispatchChangeEvent);
+    'changed',
+    ol.geom.Polygon.prototype.changed);
 
 goog.exportProperty(
     ol.geom.Polygon.prototype,
@@ -108949,6 +122846,21 @@ goog.exportProperty(
     'unByKey',
     ol.geom.Polygon.prototype.unByKey);
 
+goog.exportProperty(
+    ol.format.GML2.prototype,
+    'readFeatures',
+    ol.format.GML2.prototype.readFeatures);
+
+goog.exportProperty(
+    ol.format.GML3.prototype,
+    'readFeatures',
+    ol.format.GML3.prototype.readFeatures);
+
+goog.exportProperty(
+    ol.format.GML.prototype,
+    'readFeatures',
+    ol.format.GML.prototype.readFeatures);
+
 goog.exportProperty(
     ol.dom.Input.prototype,
     'bindTo',
@@ -108991,8 +122903,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.dom.Input.prototype,
-    'dispatchChangeEvent',
-    ol.dom.Input.prototype.dispatchChangeEvent);
+    'changed',
+    ol.dom.Input.prototype.changed);
 
 goog.exportProperty(
     ol.dom.Input.prototype,
@@ -109061,8 +122973,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.Control.prototype,
-    'dispatchChangeEvent',
-    ol.control.Control.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.Control.prototype.changed);
 
 goog.exportProperty(
     ol.control.Control.prototype,
@@ -109141,8 +123053,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.Attribution.prototype,
-    'dispatchChangeEvent',
-    ol.control.Attribution.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.Attribution.prototype.changed);
 
 goog.exportProperty(
     ol.control.Attribution.prototype,
@@ -109221,8 +123133,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.FullScreen.prototype,
-    'dispatchChangeEvent',
-    ol.control.FullScreen.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.FullScreen.prototype.changed);
 
 goog.exportProperty(
     ol.control.FullScreen.prototype,
@@ -109296,8 +123208,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.MousePosition.prototype,
-    'dispatchChangeEvent',
-    ol.control.MousePosition.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.MousePosition.prototype.changed);
 
 goog.exportProperty(
     ol.control.MousePosition.prototype,
@@ -109324,6 +123236,81 @@ goog.exportProperty(
     'unByKey',
     ol.control.MousePosition.prototype.unByKey);
 
+goog.exportProperty(
+    ol.control.OverviewMap.prototype,
+    'getMap',
+    ol.control.OverviewMap.prototype.getMap);
+
+goog.exportProperty(
+    ol.control.OverviewMap.prototype,
+    'bindTo',
+    ol.control.OverviewMap.prototype.bindTo);
+
+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,
+    'unbind',
+    ol.control.OverviewMap.prototype.unbind);
+
+goog.exportProperty(
+    ol.control.OverviewMap.prototype,
+    'unbindAll',
+    ol.control.OverviewMap.prototype.unbindAll);
+
+goog.exportProperty(
+    ol.control.OverviewMap.prototype,
+    'changed',
+    ol.control.OverviewMap.prototype.changed);
+
+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',
@@ -109376,8 +123363,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.Rotate.prototype,
-    'dispatchChangeEvent',
-    ol.control.Rotate.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.Rotate.prototype.changed);
 
 goog.exportProperty(
     ol.control.Rotate.prototype,
@@ -109456,8 +123443,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.ScaleLine.prototype,
-    'dispatchChangeEvent',
-    ol.control.ScaleLine.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.ScaleLine.prototype.changed);
 
 goog.exportProperty(
     ol.control.ScaleLine.prototype,
@@ -109536,8 +123523,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.Zoom.prototype,
-    'dispatchChangeEvent',
-    ol.control.Zoom.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.Zoom.prototype.changed);
 
 goog.exportProperty(
     ol.control.Zoom.prototype,
@@ -109611,8 +123598,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.ZoomSlider.prototype,
-    'dispatchChangeEvent',
-    ol.control.ZoomSlider.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.ZoomSlider.prototype.changed);
 
 goog.exportProperty(
     ol.control.ZoomSlider.prototype,
@@ -109691,8 +123678,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.control.ZoomToExtent.prototype,
-    'dispatchChangeEvent',
-    ol.control.ZoomToExtent.prototype.dispatchChangeEvent);
+    'changed',
+    ol.control.ZoomToExtent.prototype.changed);
 
 goog.exportProperty(
     ol.control.ZoomToExtent.prototype,
@@ -109718,3 +123705,7 @@ goog.exportProperty(
     ol.control.ZoomToExtent.prototype,
     'unByKey',
     ol.control.ZoomToExtent.prototype.unByKey);
+OPENLAYERS.ol = ol;
+
+  return OPENLAYERS.ol;
+}));
diff --git a/VIPSWeb/static/js/3rdparty/ol.js b/VIPSWeb/static/js/3rdparty/ol.js
index a14247c8e5163fb35b1da8ee8856b67e4b141845..298612c249275da74639b84e2f2caccbb66135eb 100644
--- a/VIPSWeb/static/js/3rdparty/ol.js
+++ b/VIPSWeb/static/js/3rdparty/ol.js
@@ -1,751 +1,883 @@
 // OpenLayers 3. See http://openlayers.org/
 // License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
-// Version: v3.0.0-30-g2876902
+// Version: v3.1.1
 
-(function(){var k,aa=aa||{},ba=this;function l(a){return void 0!==a}function ca(){}function da(a){a.gb=function(){return a.ee?a.ee:a.ee=new a}}
-function fa(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
-else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function ga(a){return null===a}function ha(a){return"array"==fa(a)}function ia(a){var b=fa(a);return"array"==b||"object"==b&&"number"==typeof a.length}function ja(a){return"string"==typeof a}function ka(a){return"number"==typeof a}function la(a){return"function"==fa(a)}function ma(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}function na(a){return a[oa]||(a[oa]=++pa)}
-var oa="closure_uid_"+(1E9*Math.random()>>>0),pa=0;function qa(a,b,c){return a.call.apply(a.bind,arguments)}function ra(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}
-function sa(a,b,c){sa=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?qa:ra;return sa.apply(null,arguments)}function ta(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}}var ua=Date.now||function(){return+new Date};
-function s(a,b){var c=a.split("."),d=ba;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)!c.length&&l(b)?d[e]=b:d[e]?d=d[e]:d=d[e]={}}function t(a,b){function c(){}c.prototype=b.prototype;a.K=b.prototype;a.prototype=new c;a.$i=function(a,c,f){return b.prototype[c].apply(a,Array.prototype.slice.call(arguments,2))}};function va(a){if(Error.captureStackTrace)Error.captureStackTrace(this,va);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))}t(va,Error);va.prototype.name="CustomError";var wa;function xa(a,b){for(var c=a.split("%s"),d="",e=Array.prototype.slice.call(arguments,1);e.length&&1<c.length;)d+=c.shift()+e.shift();return d+c.join("%s")}function ya(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}
-function Ca(a){if(!Da.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(Ea,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(Fa,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(Ga,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(Ha,"&quot;"));-1!=a.indexOf("'")&&(a=a.replace(Ja,"&#39;"));-1!=a.indexOf("\x00")&&(a=a.replace(Ka,"&#0;"));return a}var Ea=/&/g,Fa=/</g,Ga=/>/g,Ha=/"/g,Ja=/'/g,Ka=/\x00/g,Da=/[\x00&<>"']/;function La(a,b){return-1!=a.indexOf(b)}
-function Ma(a){a=l(void 0)?a.toFixed(void 0):String(a);var b=a.indexOf(".");-1==b&&(b=a.length);b=Math.max(0,2-b);return Array(b+1).join("0")+a}
-function Na(a,b){for(var c=0,d=ya(String(a)).split("."),e=ya(String(b)).split("."),f=Math.max(d.length,e.length),g=0;0==c&&g<f;g++){var h=d[g]||"",m=e[g]||"",n=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var r=n.exec(h)||["","",""],q=p.exec(m)||["","",""];if(0==r[0].length&&0==q[0].length)break;c=Oa(0==r[1].length?0:parseInt(r[1],10),0==q[1].length?0:parseInt(q[1],10))||Oa(0==r[2].length,0==q[2].length)||Oa(r[2],q[2])}while(0==c)}return c}function Oa(a,b){return a<b?-1:a>b?1:0}
-function Pa(){return"transform".replace(/\-([a-z])/g,function(a,b){return b.toUpperCase()})}function Qa(a){var b=ja(void 0)?"undefined".replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08"):"\\s";return a.replace(new RegExp("(^"+(b?"|["+b+"]+":"")+")([a-z])","g"),function(a,b,e){return b+e.toUpperCase()})};var Ra=Array.prototype,Sa=Ra.indexOf?function(a,b,c){return Ra.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(ja(a))return ja(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},Ta=Ra.forEach?function(a,b,c){Ra.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=ja(a)?a.split(""):a,f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},Ua=Ra.filter?function(a,b,c){return Ra.filter.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=[],
-f=0,g=ja(a)?a.split(""):a,h=0;h<d;h++)if(h in g){var m=g[h];b.call(c,m,h,a)&&(e[f++]=m)}return e},Va=Ra.map?function(a,b,c){return Ra.map.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=Array(d),f=ja(a)?a.split(""):a,g=0;g<d;g++)g in f&&(e[g]=b.call(c,f[g],g,a));return e},Wa=Ra.some?function(a,b,c){return Ra.some.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=ja(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return!0;return!1};
-function Xa(a){var b;a:{b=Ya;for(var c=a.length,d=ja(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return 0>b?null:ja(a)?a.charAt(b):a[b]}function Za(a,b){return 0<=Sa(a,b)}function $a(a){if(!ha(a))for(var b=a.length-1;0<=b;b--)delete a[b];a.length=0}function ab(a,b){var c=Sa(a,b),d;(d=0<=c)&&Ra.splice.call(a,c,1);return d}function bb(a){return Ra.concat.apply(Ra,arguments)}
-function cb(a){var b=a.length;if(0<b){for(var c=Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]}function db(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c],e;if(ha(d)||(e=ia(d))&&Object.prototype.hasOwnProperty.call(d,"callee"))a.push.apply(a,d);else if(e)for(var f=a.length,g=d.length,h=0;h<g;h++)a[f+h]=d[h];else a.push(d)}}function eb(a,b,c,d){Ra.splice.apply(a,fb(arguments,1))}function fb(a,b,c){return 2>=arguments.length?Ra.slice.call(a,b):Ra.slice.call(a,b,c)}
-function gb(a,b){a.sort(b||hb)}function ib(a,b){if(!ia(a)||!ia(b)||a.length!=b.length)return!1;for(var c=a.length,d=jb,e=0;e<c;e++)if(!d(a[e],b[e]))return!1;return!0}function hb(a,b){return a>b?1:a<b?-1:0}function jb(a,b){return a===b};var lb;a:{var mb=ba.navigator;if(mb){var nb=mb.userAgent;if(nb){lb=nb;break a}}lb=""};var ob,pb,qb;function rb(){return ba.navigator||null}var sb=La(lb,"Opera")||La(lb,"OPR"),ub=La(lb,"Trident")||La(lb,"MSIE"),vb=La(lb,"Gecko")&&!La(lb.toLowerCase(),"webkit")&&!(La(lb,"Trident")||La(lb,"MSIE")),wb=La(lb.toLowerCase(),"webkit"),xb,yb=rb();xb=yb&&yb.platform||"";ob=La(xb,"Mac");pb=La(xb,"Win");qb=La(xb,"Linux");var zb=!!rb()&&La(rb().appVersion||"","X11");function Bb(){var a=ba.document;return a?a.documentMode:void 0}
-var Cb=function(){var a="",b;if(sb&&ba.opera)return a=ba.opera.version,la(a)?a():a;vb?b=/rv\:([^\);]+)(\)|;)/:ub?b=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:wb&&(b=/WebKit\/(\S+)/);b&&(a=(a=b.exec(lb))?a[1]:"");return ub&&(b=Bb(),b>parseFloat(a))?String(b):a}(),Db={};function Eb(a){return Db[a]||(Db[a]=0<=Na(Cb,a))}var Fb=ba.document,Gb=Fb&&ub?Bb()||("CSS1Compat"==Fb.compatMode?parseInt(Cb,10):5):void 0;var Hb="https:"===ba.location.protocol,Ib=ub&&!Eb("9.0")&&""!==Cb;function Jb(a,b,c){return Math.min(Math.max(a,b),c)}function Kb(a,b){var c=a%b;return 0>c*b?c+b:c}function Lb(a,b,c){return a+c*(b-a)}function Mb(a){return a*Math.PI/180};function Nb(a){return function(b){if(l(b))return[Jb(b[0],a[0],a[2]),Jb(b[1],a[1],a[3])]}}function Ob(a){return a};function Pb(a,b){var c,d;c=0;for(d=b.length;c<d;++c)a.push(b[c])}function Qb(a,b,c){var d=a.length;if(a[0]<=b)return 0;if(!(b<=a[d-1]))if(0<c)for(c=1;c<d;++c){if(a[c]<b)return c-1}else if(0>c)for(c=1;c<d;++c){if(a[c]<=b)return c}else for(c=1;c<d;++c){if(a[c]==b)return c;if(a[c]<b)return a[c-1]-b<b-a[c]?c-1:c}return d-1};function Rb(a){return function(b,c,d){if(l(b))return b=Qb(a,b,d),b=Jb(b+c,0,a.length-1),a[b]}}function Sb(a,b,c){return function(d,e,f){if(l(d))return f=0<f?0:0>f?1:.5,d=Math.floor(Math.log(b/d)/Math.log(a)+f),e=Math.max(d+e,0),l(c)&&(e=Math.min(e,c)),b/Math.pow(a,e)}};function Tb(a){if(l(a))return 0}function Ub(a,b){if(l(a))return a+b}function Vb(a){var b=2*Math.PI/a;return function(a,d){if(l(a))return a=Math.floor((a+d)/b+.5)*b}}function Wb(){var a=Mb(5);return function(b,c){if(l(b))return Math.abs(b+c)<=a?0:b+c}};function Xb(a,b,c){this.center=a;this.resolution=b;this.rotation=c};var Yb=!ub||ub&&9<=Gb,Zb=!ub||ub&&9<=Gb,$b=ub&&!Eb("9");!wb||Eb("528");vb&&Eb("1.9b")||ub&&Eb("8")||sb&&Eb("9.5")||wb&&Eb("528");vb&&!Eb("8")||ub&&Eb("9");function ac(){0!=bc&&(cc[na(this)]=this)}var bc=0,cc={};ac.prototype.qb=!1;ac.prototype.Nb=function(){if(!this.qb&&(this.qb=!0,this.I(),0!=bc)){var a=na(this);delete cc[a]}};function dc(a,b){var c=ta(ec,b);a.bb||(a.bb=[]);a.bb.push(l(void 0)?sa(c,void 0):c)}ac.prototype.I=function(){if(this.bb)for(;this.bb.length;)this.bb.shift()()};function ec(a){a&&"function"==typeof a.Nb&&a.Nb()};function fc(a,b){this.type=a;this.b=this.target=b;this.g=!1;this.Fe=!0}fc.prototype.Nb=function(){};fc.prototype.d=function(){this.g=!0};fc.prototype.preventDefault=function(){this.Fe=!1};function gc(a){a.d()};var hc=ub?"focusout":"DOMFocusOut";function ic(a){ic[" "](a);return a}ic[" "]=ca;function jc(a,b){fc.call(this,a?a.type:"");this.relatedTarget=this.b=this.target=null;this.k=this.f=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.p=this.e=this.c=this.l=!1;this.state=null;this.i=!1;this.a=null;a&&kc(this,a,b)}t(jc,fc);var lc=[1,4,2];
-function kc(a,b,c){var d=a.type=b.type;a.target=b.target||b.srcElement;a.b=c;if(c=b.relatedTarget){if(vb){var e;a:{try{ic(c.nodeName);e=!0;break a}catch(f){}e=!1}e||(c=null)}}else"mouseover"==d?c=b.fromElement:"mouseout"==d&&(c=b.toElement);a.relatedTarget=c;a.offsetX=wb||void 0!==b.offsetX?b.offsetX:b.layerX;a.offsetY=wb||void 0!==b.offsetY?b.offsetY:b.layerY;a.clientX=void 0!==b.clientX?b.clientX:b.pageX;a.clientY=void 0!==b.clientY?b.clientY:b.pageY;a.screenX=b.screenX||0;a.screenY=b.screenY||
-0;a.button=b.button;a.f=b.keyCode||0;a.k=b.charCode||("keypress"==d?b.keyCode:0);a.l=b.ctrlKey;a.c=b.altKey;a.e=b.shiftKey;a.p=b.metaKey;a.i=ob?b.metaKey:b.ctrlKey;a.state=b.state;a.a=b;b.defaultPrevented&&a.preventDefault()}function mc(a){return(Yb?0==a.a.button:"click"==a.type?!0:!!(a.a.button&lc[0]))&&!(wb&&ob&&a.l)}jc.prototype.d=function(){jc.K.d.call(this);this.a.stopPropagation?this.a.stopPropagation():this.a.cancelBubble=!0};
-jc.prototype.preventDefault=function(){jc.K.preventDefault.call(this);var a=this.a;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,$b)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};jc.prototype.o=function(){return this.a};var nc="closure_listenable_"+(1E6*Math.random()|0);function oc(a){return!(!a||!a[nc])}var pc=0;function qc(a,b,c,d,e){this.wb=a;this.a=null;this.src=b;this.type=c;this.bc=!!d;this.Wc=e;this.key=++pc;this.Zb=this.Ec=!1}function rc(a){a.Zb=!0;a.wb=null;a.a=null;a.src=null;a.Wc=null};function sc(a,b,c){for(var d in a)b.call(c,a[d],d,a)}function tc(a,b){for(var c in a)if(b.call(void 0,a[c],c,a))return!0;return!1}function uc(a){var b=0,c;for(c in a)b++;return b}function vc(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b}function xc(a){var b=[],c=0,d;for(d in a)b[c++]=d;return b}function yc(a,b){return b in a}function zc(a){var b=Ac,c;for(c in b)if(a.call(void 0,b[c],c,b))return c}function Bc(a){for(var b in a)return!1;return!0}function Cc(a){for(var b in a)delete a[b]}
-function Dc(a,b){b in a&&delete a[b]}function v(a,b,c){return b in a?a[b]:c}function Ec(a,b){var c=[];return b in a?a[b]:a[b]=c}function Fc(a){var b={},c;for(c in a)b[c]=a[c];return b}var Gc="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");function Hc(a,b){for(var c,d,e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(var f=0;f<Gc.length;f++)c=Gc[f],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};function Ic(a){this.src=a;this.a={};this.c=0}Ic.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.a[f];a||(a=this.a[f]=[],this.c++);var g=Jc(a,b,d,e);-1<g?(b=a[g],c||(b.Ec=!1)):(b=new qc(b,this.src,f,!!d,e),b.Ec=c,a.push(b));return b};Ic.prototype.remove=function(a,b,c,d){a=a.toString();if(!(a in this.a))return!1;var e=this.a[a];b=Jc(e,b,c,d);return-1<b?(rc(e[b]),Ra.splice.call(e,b,1),0==e.length&&(delete this.a[a],this.c--),!0):!1};
-function Kc(a,b){var c=b.type;if(!(c in a.a))return!1;var d=ab(a.a[c],b);d&&(rc(b),0==a.a[c].length&&(delete a.a[c],a.c--));return d}function Lc(a,b,c,d,e){a=a.a[b.toString()];b=-1;a&&(b=Jc(a,c,d,e));return-1<b?a[b]:null}function Mc(a,b,c){var d=l(b),e=d?b.toString():"",f=l(c);return tc(a.a,function(a){for(var b=0;b<a.length;++b)if(!(d&&a[b].type!=e||f&&a[b].bc!=c))return!0;return!1})}
-function Jc(a,b,c,d){for(var e=0;e<a.length;++e){var f=a[e];if(!f.Zb&&f.wb==b&&f.bc==!!c&&f.Wc==d)return e}return-1};var Nc="closure_lm_"+(1E6*Math.random()|0),Oc={},Pc=0;function y(a,b,c,d,e){if(ha(b)){for(var f=0;f<b.length;f++)y(a,b[f],c,d,e);return null}c=Qc(c);return oc(a)?a.ya(b,c,d,e):Rc(a,b,c,!1,d,e)}function Rc(a,b,c,d,e,f){if(!b)throw Error("Invalid event type");var g=!!e,h=Sc(a);h||(a[Nc]=h=new Ic(a));c=h.add(b,c,d,e,f);if(c.a)return c;d=Tc();c.a=d;d.src=a;d.wb=c;a.addEventListener?a.addEventListener(b.toString(),d,g):a.attachEvent(Uc(b.toString()),d);Pc++;return c}
-function Tc(){var a=Vc,b=Zb?function(c){return a.call(b.src,b.wb,c)}:function(c){c=a.call(b.src,b.wb,c);if(!c)return c};return b}function Wc(a,b,c,d,e){if(ha(b)){for(var f=0;f<b.length;f++)Wc(a,b[f],c,d,e);return null}c=Qc(c);return oc(a)?a.Qa.add(String(b),c,!0,d,e):Rc(a,b,c,!0,d,e)}function Xc(a,b,c,d,e){if(ha(b))for(var f=0;f<b.length;f++)Xc(a,b[f],c,d,e);else c=Qc(c),oc(a)?a.Od(b,c,d,e):a&&(a=Sc(a))&&(b=Lc(a,b,c,!!d,e))&&Yc(b)}
-function Yc(a){if(ka(a)||!a||a.Zb)return!1;var b=a.src;if(oc(b))return Kc(b.Qa,a);var c=a.type,d=a.a;b.removeEventListener?b.removeEventListener(c,d,a.bc):b.detachEvent&&b.detachEvent(Uc(c),d);Pc--;(c=Sc(b))?(Kc(c,a),0==c.c&&(c.src=null,b[Nc]=null)):rc(a);return!0}function Uc(a){return a in Oc?Oc[a]:Oc[a]="on"+a}function Zc(a,b,c,d){var e=1;if(a=Sc(a))if(b=a.a[b.toString()])for(b=b.concat(),a=0;a<b.length;a++){var f=b[a];f&&f.bc==c&&!f.Zb&&(e&=!1!==$c(f,d))}return Boolean(e)}
-function $c(a,b){var c=a.wb,d=a.Wc||a.src;a.Ec&&Yc(a);return c.call(d,b)}
-function Vc(a,b){if(a.Zb)return!0;if(!Zb){var c;if(!(c=b))a:{c=["window","event"];for(var d=ba,e;e=c.shift();)if(null!=d[e])d=d[e];else{c=null;break a}c=d}e=c;c=new jc(e,this);d=!0;if(!(0>e.keyCode||void 0!=e.returnValue)){a:{var f=!1;if(0==e.keyCode)try{e.keyCode=-1;break a}catch(g){f=!0}if(f||void 0==e.returnValue)e.returnValue=!0}e=[];for(f=c.b;f;f=f.parentNode)e.push(f);for(var f=a.type,h=e.length-1;!c.g&&0<=h;h--)c.b=e[h],d&=Zc(e[h],f,!0,c);for(h=0;!c.g&&h<e.length;h++)c.b=e[h],d&=Zc(e[h],f,
-!1,c)}return d}return $c(a,new jc(b,this))}function Sc(a){a=a[Nc];return a instanceof Ic?a:null}var ad="__closure_events_fn_"+(1E9*Math.random()>>>0);function Qc(a){if(la(a))return a;a[ad]||(a[ad]=function(b){return a.handleEvent(b)});return a[ad]};function bd(a){return function(){return a}}var cd=bd(!1),dd=bd(!0);function ed(a){return a}function fd(a){var b;b=b||0;return function(){return a.apply(this,Array.prototype.slice.call(arguments,0,b))}}function gd(a){var b=arguments,c=b.length;return function(){for(var a=0;a<c;a++)if(!b[a].apply(this,arguments))return!1;return!0}};function hd(){ac.call(this);this.Qa=new Ic(this);this.Se=this;this.pd=null}t(hd,ac);hd.prototype[nc]=!0;k=hd.prototype;k.addEventListener=function(a,b,c,d){y(this,a,b,c,d)};k.removeEventListener=function(a,b,c,d){Xc(this,a,b,c,d)};
-k.dispatchEvent=function(a){var b,c=this.pd;if(c)for(b=[];c;c=c.pd)b.push(c);var c=this.Se,d=a.type||a;if(ja(a))a=new fc(a,c);else if(a instanceof fc)a.target=a.target||c;else{var e=a;a=new fc(d,c);Hc(a,e)}var e=!0,f;if(b)for(var g=b.length-1;!a.g&&0<=g;g--)f=a.b=b[g],e=id(f,d,!0,a)&&e;a.g||(f=a.b=c,e=id(f,d,!0,a)&&e,a.g||(e=id(f,d,!1,a)&&e));if(b)for(g=0;!a.g&&g<b.length;g++)f=a.b=b[g],e=id(f,d,!1,a)&&e;return e};
-k.I=function(){hd.K.I.call(this);if(this.Qa){var a=this.Qa,b=0,c;for(c in a.a){for(var d=a.a[c],e=0;e<d.length;e++)++b,rc(d[e]);delete a.a[c];a.c--}}this.pd=null};k.ya=function(a,b,c,d){return this.Qa.add(String(a),b,!1,c,d)};k.Od=function(a,b,c,d){return this.Qa.remove(String(a),b,c,d)};
-function id(a,b,c,d){b=a.Qa.a[String(b)];if(!b)return!0;b=b.concat();for(var e=!0,f=0;f<b.length;++f){var g=b[f];if(g&&!g.Zb&&g.bc==c){var h=g.wb,m=g.Wc||g.src;g.Ec&&Kc(a.Qa,g);e=!1!==h.call(m,d)&&e}}return e&&!1!=d.Fe}function jd(a,b,c){return Mc(a.Qa,l(b)?String(b):void 0,c)};function ld(){hd.call(this);this.c=0}t(ld,hd);k=ld.prototype;k.n=function(){++this.c;this.dispatchEvent("change")};k.A=function(){return this.c};k.t=function(a,b,c){return y(this,a,b,!1,c)};k.B=function(a,b,c){return Wc(this,a,b,!1,c)};k.v=function(a,b,c){Xc(this,a,b,!1,c)};k.C=function(a){Yc(a)};function md(a,b){fc.call(this,a);this.key=b}t(md,fc);function nd(a,b,c,d){this.source=a;this.target=b;this.e=c;this.c=d;this.b=this.a=ed}nd.prototype.d=function(a,b){this.a=a;this.b=b;od(this.source,this.e)};function pd(a){ld.call(this);na(this);this.p={};this.ga={};this.Ib={};this.Jb={};l(a)&&this.L(a)}t(pd,ld);var qd={},rd={},sd={};function td(a){return qd.hasOwnProperty(a)?qd[a]:qd[a]="change:"+a.toLowerCase()}k=pd.prototype;
-k.Z=function(a,b,c){c=c||a;this.Y(a);var d=td(c);this.Jb[a]=y(b,d,function(){od(this,a)},void 0,this);this.Ib[a]=y(b,"beforepropertychange",ud(a,c),void 0,this);b=new nd(this,b,a,c);this.ga[a]=b;od(this,a);return b};function ud(a,b){return function(c){c.key===b&&this.dispatchEvent(new md("beforepropertychange",a))}}
-k.get=function(a){var b,c=this.ga;if(c.hasOwnProperty(a)){a=c[a];b=a.target;var c=a.c,d=rd.hasOwnProperty(c)?rd[c]:rd[c]="get"+(c.substr(0,1).toUpperCase()+c.substr(1)),d=v(b,d);b=l(d)?d.call(b):b.get(c);b=a.b(b)}else this.p.hasOwnProperty(a)&&(b=this.p[a]);return b};k.N=function(){var a=this.ga,b;if(Bc(this.p)){if(Bc(a))return[];b=a}else if(Bc(a))b=this.p;else{b={};for(var c in this.p)b[c]=!0;for(c in a)b[c]=!0}return xc(b)};
-k.R=function(){var a={},b;for(b in this.p)a[b]=this.p[b];for(b in this.ga)a[b]=this.get(b);return a};function od(a,b){var c=td(b);a.dispatchEvent(c);a.dispatchEvent(new md("propertychange",b))}k.set=function(a,b){this.dispatchEvent(new md("beforepropertychange",a));var c=this.ga;if(c.hasOwnProperty(a)){var d=c[a],c=d.target,e=d.c;b=d.a(b);d=sd.hasOwnProperty(e)?sd[e]:sd[e]="set"+(e.substr(0,1).toUpperCase()+e.substr(1));d=v(c,d);l(d)?d.call(c,b):c.set(e,b)}else this.p[a]=b,od(this,a)};
-k.L=function(a){for(var b in a)this.set(b,a[b])};k.Y=function(a){var b=this.Jb,c=b[a];c&&(delete b[a],Yc(c),b=this.get(a),delete this.ga[a],this.p[a]=b);if(b=this.Ib[a])Yc(b),delete this.Ib[a]};k.$=function(){for(var a in this.Jb)this.Y(a)};function vd(a,b){a[0]+=b[0];a[1]+=b[1];return a}function wd(a,b){var c=a[0],d=a[1],e=b[0],f=b[1],g=e[0],e=e[1],h=f[0],f=f[1],m=h-g,n=f-e,c=0===m&&0===n?0:(m*(c-g)+n*(d-e))/(m*m+n*n||0);0>=c||(1<=c?(g=h,e=f):(g+=c*m,e+=c*n));return[g,e]}function xd(a,b){var c=Kb(a+180,360)-180,d=Math.abs(Math.round(3600*c));return Math.floor(d/3600)+"\u00b0 "+Math.floor(d/60%60)+"\u2032 "+Math.floor(d%60)+"\u2033 "+b.charAt(0>c?1:0)}
-function zd(a,b,c){return l(a)?b.replace("{x}",a[0].toFixed(c)).replace("{y}",a[1].toFixed(c)):""}function Ad(a,b){for(var c=!0,d=a.length-1;0<=d;--d)if(a[d]!=b[d]){c=!1;break}return c}function Bd(a,b){var c=Math.cos(b),d=Math.sin(b),e=a[1]*c+a[0]*d;a[0]=a[0]*c-a[1]*d;a[1]=e;return a}function Cd(a,b){var c=a[0]-b[0],d=a[1]-b[1];return c*c+d*d}function Dd(a,b){return zd(a,"{x}, {y}",b)};function Ed(a){this.length=a.length||a;for(var b=0;b<this.length;b++)this[b]=a[b]||0}Ed.prototype.a=4;Ed.prototype.set=function(a,b){b=b||0;for(var c=0;c<a.length&&b+c<this.length;c++)this[b+c]=a[c]};Ed.prototype.toString=Array.prototype.join;"undefined"==typeof Float32Array&&(Ed.BYTES_PER_ELEMENT=4,Ed.prototype.BYTES_PER_ELEMENT=Ed.prototype.a,Ed.prototype.set=Ed.prototype.set,Ed.prototype.toString=Ed.prototype.toString,s("Float32Array",Ed));function Fd(a){this.length=a.length||a;for(var b=0;b<this.length;b++)this[b]=a[b]||0}Fd.prototype.a=8;Fd.prototype.set=function(a,b){b=b||0;for(var c=0;c<a.length&&b+c<this.length;c++)this[b+c]=a[c]};Fd.prototype.toString=Array.prototype.join;if("undefined"==typeof Float64Array){try{Fd.BYTES_PER_ELEMENT=8}catch(Gd){}Fd.prototype.BYTES_PER_ELEMENT=Fd.prototype.a;Fd.prototype.set=Fd.prototype.set;Fd.prototype.toString=Fd.prototype.toString;s("Float64Array",Fd)};function Hd(a,b,c,d,e){a[0]=b;a[1]=c;a[2]=d;a[3]=e};function Id(){var a=Array(16);Jd(a,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return a}function Kd(){var a=Array(16);Jd(a,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return a}function Jd(a,b,c,d,e,f,g,h,m,n,p,r,q,u,x,B,E){a[0]=b;a[1]=c;a[2]=d;a[3]=e;a[4]=f;a[5]=g;a[6]=h;a[7]=m;a[8]=n;a[9]=p;a[10]=r;a[11]=q;a[12]=u;a[13]=x;a[14]=B;a[15]=E}
-function Ld(a,b){a[0]=b[0];a[1]=b[1];a[2]=b[2];a[3]=b[3];a[4]=b[4];a[5]=b[5];a[6]=b[6];a[7]=b[7];a[8]=b[8];a[9]=b[9];a[10]=b[10];a[11]=b[11];a[12]=b[12];a[13]=b[13];a[14]=b[14];a[15]=b[15]}function Md(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1}
-function Nd(a,b,c){var d=a[0],e=a[1],f=a[2],g=a[3],h=a[4],m=a[5],n=a[6],p=a[7],r=a[8],q=a[9],u=a[10],x=a[11],B=a[12],E=a[13],F=a[14];a=a[15];var w=b[0],U=b[1],Q=b[2],ea=b[3],Y=b[4],za=b[5],kb=b[6],Aa=b[7],Ab=b[8],tb=b[9],Ba=b[10],Ia=b[11],yd=b[12],kd=b[13],wc=b[14];b=b[15];c[0]=d*w+h*U+r*Q+B*ea;c[1]=e*w+m*U+q*Q+E*ea;c[2]=f*w+n*U+u*Q+F*ea;c[3]=g*w+p*U+x*Q+a*ea;c[4]=d*Y+h*za+r*kb+B*Aa;c[5]=e*Y+m*za+q*kb+E*Aa;c[6]=f*Y+n*za+u*kb+F*Aa;c[7]=g*Y+p*za+x*kb+a*Aa;c[8]=d*Ab+h*tb+r*Ba+B*Ia;c[9]=e*Ab+m*tb+q*Ba+
-E*Ia;c[10]=f*Ab+n*tb+u*Ba+F*Ia;c[11]=g*Ab+p*tb+x*Ba+a*Ia;c[12]=d*yd+h*kd+r*wc+B*b;c[13]=e*yd+m*kd+q*wc+E*b;c[14]=f*yd+n*kd+u*wc+F*b;c[15]=g*yd+p*kd+x*wc+a*b}function Od(a,b,c){var d=a[1]*b+a[5]*c+0*a[9]+a[13],e=a[2]*b+a[6]*c+0*a[10]+a[14],f=a[3]*b+a[7]*c+0*a[11]+a[15];a[12]=a[0]*b+a[4]*c+0*a[8]+a[12];a[13]=d;a[14]=e;a[15]=f}function Pd(a,b,c){Jd(a,a[0]*b,a[1]*b,a[2]*b,a[3]*b,a[4]*c,a[5]*c,a[6]*c,a[7]*c,1*a[8],1*a[9],1*a[10],1*a[11],a[12],a[13],a[14],a[15])}
-function Qd(a,b){var c=a[0],d=a[1],e=a[2],f=a[3],g=a[4],h=a[5],m=a[6],n=a[7],p=Math.cos(b),r=Math.sin(b);a[0]=c*p+g*r;a[1]=d*p+h*r;a[2]=e*p+m*r;a[3]=f*p+n*r;a[4]=c*-r+g*p;a[5]=d*-r+h*p;a[6]=e*-r+m*p;a[7]=f*-r+n*p}new Float64Array(3);new Float64Array(3);new Float64Array(4);new Float64Array(4);new Float64Array(4);new Float64Array(16);function Rd(a){for(var b=Sd(),c=0,d=a.length;c<d;++c){var e=b,f=a[c];f[0]<e[0]&&(e[0]=f[0]);f[0]>e[2]&&(e[2]=f[0]);f[1]<e[1]&&(e[1]=f[1]);f[1]>e[3]&&(e[3]=f[1])}return b}function Td(a,b,c){var d=Math.min.apply(null,a),e=Math.min.apply(null,b);a=Math.max.apply(null,a);b=Math.max.apply(null,b);return Ud(d,e,a,b,c)}function Vd(a,b,c){return l(c)?(c[0]=a[0]-b,c[1]=a[1]-b,c[2]=a[2]+b,c[3]=a[3]+b,c):[a[0]-b,a[1]-b,a[2]+b,a[3]+b]}
-function Wd(a,b){return l(b)?(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b):a.slice()}function Xd(a,b,c){b=b<a[0]?a[0]-b:a[2]<b?b-a[2]:0;a=c<a[1]?a[1]-c:a[3]<c?c-a[3]:0;return b*b+a*a}function Yd(a,b){return a[0]<=b[0]&&b[2]<=a[2]&&a[1]<=b[1]&&b[3]<=a[3]}function Sd(){return[Infinity,Infinity,-Infinity,-Infinity]}function Ud(a,b,c,d,e){return l(e)?(e[0]=a,e[1]=b,e[2]=c,e[3]=d,e):[a,b,c,d]}function Zd(a){return Ud(Infinity,Infinity,-Infinity,-Infinity,a)}
-function $d(a,b){var c=a[0],d=a[1];return Ud(c,d,c,d,b)}function ae(a,b){return a[0]==b[0]&&a[2]==b[2]&&a[1]==b[1]&&a[3]==b[3]}function be(a,b){b[0]<a[0]&&(a[0]=b[0]);b[2]>a[2]&&(a[2]=b[2]);b[1]<a[1]&&(a[1]=b[1]);b[3]>a[3]&&(a[3]=b[3]);return a}function ce(a,b,c,d,e){for(;c<d;c+=e){var f=a,g=b[c],h=b[c+1];f[0]=Math.min(f[0],g);f[1]=Math.min(f[1],h);f[2]=Math.max(f[2],g);f[3]=Math.max(f[3],h)}return a}function de(a){var b=0;ee(a)||(b=fe(a)*ge(a));return b}function he(a){return[a[0],a[1]]}
-function ie(a){return[a[2],a[1]]}function je(a){return[(a[0]+a[2])/2,(a[1]+a[3])/2]}function ke(a,b){var c;"bottom-left"===b?c=he(a):"bottom-right"===b?c=ie(a):"top-left"===b?c=le(a):"top-right"===b&&(c=me(a));return c}function ne(a,b,c,d){var e=b*d[0]/2;d=b*d[1]/2;b=Math.cos(c);c=Math.sin(c);e=[-e,-e,e,e];d=[-d,d,-d,d];var f,g,h;for(f=0;4>f;++f)g=e[f],h=d[f],e[f]=a[0]+g*b-h*c,d[f]=a[1]+g*c+h*b;return Td(e,d,void 0)}function ge(a){return a[3]-a[1]}
-function oe(a,b){var c=l(void 0)?void 0:Sd();pe(a,b)&&(c[0]=a[0]>b[0]?a[0]:b[0],c[1]=a[1]>b[1]?a[1]:b[1],c[2]=a[2]<b[2]?a[2]:b[2],c[3]=a[3]<b[3]?a[3]:b[3]);return c}function le(a){return[a[0],a[3]]}function me(a){return[a[2],a[3]]}function fe(a){return a[2]-a[0]}function pe(a,b){return a[0]<=b[2]&&a[2]>=b[0]&&a[1]<=b[3]&&a[3]>=b[1]}function ee(a){return a[2]<a[0]||a[3]<a[1]}function qe(a,b){return l(b)?(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b):a}
-function re(a,b){var c=(a[2]-a[0])/2*(b-1),d=(a[3]-a[1])/2*(b-1);a[0]-=c;a[2]+=c;a[1]-=d;a[3]+=d}function te(a,b,c){a=[a[0],a[1],a[0],a[3],a[2],a[1],a[2],a[3]];b(a,a,2);return Td([a[0],a[2],a[4],a[6]],[a[1],a[3],a[5],a[7]],c)};/*
+(function (root, factory) {
+  if (typeof define === "function" && define.amd) {
+    define([], factory);
+  } else if (typeof exports === "object") {
+    module.exports = factory();
+  } else {
+    root.ol = factory();
+  }
+}(this, function () {
+  var OPENLAYERS = {};
+  var l,aa=aa||{},ba=this;function m(b){return void 0!==b}function t(b,c,d){b=b.split(".");d=d||ba;b[0]in d||!d.execScript||d.execScript("var "+b[0]);for(var e;b.length&&(e=b.shift());)!b.length&&m(c)?d[e]=c:d[e]?d=d[e]:d=d[e]={}}function ca(){}function da(b){b.Ja=function(){return b.lf?b.lf:b.lf=new b}}
+function ea(b){var c=typeof b;if("object"==c)if(b){if(b instanceof Array)return"array";if(b instanceof Object)return c;var d=Object.prototype.toString.call(b);if("[object Window]"==d)return"object";if("[object Array]"==d||"number"==typeof b.length&&"undefined"!=typeof b.splice&&"undefined"!=typeof b.propertyIsEnumerable&&!b.propertyIsEnumerable("splice"))return"array";if("[object Function]"==d||"undefined"!=typeof b.call&&"undefined"!=typeof b.propertyIsEnumerable&&!b.propertyIsEnumerable("call"))return"function"}else return"null";
+else if("function"==c&&"undefined"==typeof b.call)return"object";return c}function fa(b){return null===b}function ga(b){return"array"==ea(b)}function ha(b){var c=ea(b);return"array"==c||"object"==c&&"number"==typeof b.length}function ia(b){return"string"==typeof b}function ja(b){return"number"==typeof b}function ka(b){return"function"==ea(b)}function la(b){var c=typeof b;return"object"==c&&null!=b||"function"==c}function ma(b){return b[na]||(b[na]=++oa)}
+var na="closure_uid_"+(1E9*Math.random()>>>0),oa=0;function pa(b,c,d){return b.call.apply(b.bind,arguments)}function ra(b,c,d){if(!b)throw Error();if(2<arguments.length){var e=Array.prototype.slice.call(arguments,2);return function(){var d=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(d,e);return b.apply(c,d)}}return function(){return b.apply(c,arguments)}}
+function sa(b,c,d){sa=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?pa:ra;return sa.apply(null,arguments)}function ta(b,c){var d=Array.prototype.slice.call(arguments,1);return function(){var c=d.slice();c.push.apply(c,arguments);return b.apply(this,c)}}var ua=Date.now||function(){return+new Date};
+function u(b,c){function d(){}d.prototype=c.prototype;b.S=c.prototype;b.prototype=new d;b.prototype.constructor=b;b.Vl=function(b,d,g){for(var h=Array(arguments.length-2),k=2;k<arguments.length;k++)h[k-2]=arguments[k];return c.prototype[d].apply(b,h)}};var va,wa;function xa(b){if(Error.captureStackTrace)Error.captureStackTrace(this,xa);else{var c=Error().stack;c&&(this.stack=c)}b&&(this.message=String(b))}u(xa,Error);xa.prototype.name="CustomError";var ya;function za(b,c){for(var d=b.split("%s"),e="",f=Array.prototype.slice.call(arguments,1);f.length&&1<d.length;)e+=d.shift()+f.shift();return e+d.join("%s")}var Aa=String.prototype.trim?function(b){return b.trim()}:function(b){return b.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};
+function Ba(b){if(!Ca.test(b))return b;-1!=b.indexOf("&")&&(b=b.replace(Da,"&amp;"));-1!=b.indexOf("<")&&(b=b.replace(Ea,"&lt;"));-1!=b.indexOf(">")&&(b=b.replace(Fa,"&gt;"));-1!=b.indexOf('"')&&(b=b.replace(Ga,"&quot;"));-1!=b.indexOf("'")&&(b=b.replace(Ha,"&#39;"));-1!=b.indexOf("\x00")&&(b=b.replace(Ia,"&#0;"));return b}var Da=/&/g,Ea=/</g,Fa=/>/g,Ga=/"/g,Ha=/'/g,Ia=/\x00/g,Ca=/[\x00&<>"']/;
+function Ja(b){return String(b).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")}function Ma(b){b=m(void 0)?b.toFixed(void 0):String(b);var c=b.indexOf(".");-1==c&&(c=b.length);c=Math.max(0,2-c);return Array(c+1).join("0")+b}
+function Na(b,c){for(var d=0,e=Aa(String(b)).split("."),f=Aa(String(c)).split("."),g=Math.max(e.length,f.length),h=0;0==d&&h<g;h++){var k=e[h]||"",n=f[h]||"",p=RegExp("(\\d*)(\\D*)","g"),q=RegExp("(\\d*)(\\D*)","g");do{var r=p.exec(k)||["","",""],s=q.exec(n)||["","",""];if(0==r[0].length&&0==s[0].length)break;d=Oa(0==r[1].length?0:parseInt(r[1],10),0==s[1].length?0:parseInt(s[1],10))||Oa(0==r[2].length,0==s[2].length)||Oa(r[2],s[2])}while(0==d)}return d}function Oa(b,c){return b<c?-1:b>c?1:0}
+function Qa(){return"transform".replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()})}function Ra(b){var c=ia(void 0)?Ja(void 0):"\\s";return b.replace(new RegExp("(^"+(c?"|["+c+"]+":"")+")([a-z])","g"),function(b,c,f){return c+f.toUpperCase()})};var Sa=Array.prototype;function Ta(b,c,d){Sa.forEach.call(b,c,d)}function Ua(b,c){return Sa.filter.call(b,c,void 0)}function Va(b,c,d){return Sa.map.call(b,c,d)}function Wa(b,c){return Sa.some.call(b,c,void 0)}function Xa(b){var c;a:{c=Ya;for(var d=b.length,e=ia(b)?b.split(""):b,f=0;f<d;f++)if(f in e&&c.call(void 0,e[f],f,b)){c=f;break a}c=-1}return 0>c?null:ia(b)?b.charAt(c):b[c]}function Za(b,c){return 0<=Sa.indexOf.call(b,c,void 0)}
+function $a(b){if(!ga(b))for(var c=b.length-1;0<=c;c--)delete b[c];b.length=0}function ab(b,c){var d=Sa.indexOf.call(b,c,void 0),e;(e=0<=d)&&Sa.splice.call(b,d,1);return e}function bb(b){return Sa.concat.apply(Sa,arguments)}function cb(b){var c=b.length;if(0<c){for(var d=Array(c),e=0;e<c;e++)d[e]=b[e];return d}return[]}function db(b,c){for(var d=1;d<arguments.length;d++){var e=arguments[d];if(ha(e)){var f=b.length||0,g=e.length||0;b.length=f+g;for(var h=0;h<g;h++)b[f+h]=e[h]}else b.push(e)}}
+function eb(b,c,d,e){Sa.splice.apply(b,fb(arguments,1))}function fb(b,c,d){return 2>=arguments.length?Sa.slice.call(b,c):Sa.slice.call(b,c,d)}function gb(b,c){b.sort(c||hb)}function ib(b,c){if(!ha(b)||!ha(c)||b.length!=c.length)return!1;for(var d=b.length,e=jb,f=0;f<d;f++)if(!e(b[f],c[f]))return!1;return!0}function hb(b,c){return b>c?1:b<c?-1:0}function jb(b,c){return b===c};var kb;a:{var lb=ba.navigator;if(lb){var mb=lb.userAgent;if(mb){kb=mb;break a}}kb=""}function nb(b){return-1!=kb.indexOf(b)};function ob(b,c,d){for(var e in b)c.call(d,b[e],e,b)}function pb(b,c){for(var d in b)if(c.call(void 0,b[d],d,b))return!0;return!1}function qb(b){var c=0,d;for(d in b)c++;return c}function rb(b){var c=[],d=0,e;for(e in b)c[d++]=b[e];return c}function sb(b){var c=[],d=0,e;for(e in b)c[d++]=e;return c}function tb(b,c){return c in b}function ub(b){var c=wb,d;for(d in c)if(b.call(void 0,c[d],d,c))return d}function xb(b){for(var c in b)return!1;return!0}function yb(b){for(var c in b)delete b[c]}
+function zb(b,c){c in b&&delete b[c]}function Ab(b,c,d){if(c in b)throw Error('The object already contains the key "'+c+'"');b[c]=d}function x(b,c,d){return c in b?b[c]:d}function Bb(b,c){var d=[];return c in b?b[c]:b[c]=d}function Cb(b){var c={},d;for(d in b)c[d]=b[d];return c}var Db="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");
+function Eb(b,c){for(var d,e,f=1;f<arguments.length;f++){e=arguments[f];for(d in e)b[d]=e[d];for(var g=0;g<Db.length;g++)d=Db[g],Object.prototype.hasOwnProperty.call(e,d)&&(b[d]=e[d])}}function Fb(b){var c=arguments.length;if(1==c&&ga(arguments[0]))return Fb.apply(null,arguments[0]);for(var d={},e=0;e<c;e++)d[arguments[e]]=!0;return d};var Gb=nb("Opera")||nb("OPR"),Hb=nb("Trident")||nb("MSIE"),Ib=nb("Gecko")&&-1==kb.toLowerCase().indexOf("webkit")&&!(nb("Trident")||nb("MSIE")),Jb=-1!=kb.toLowerCase().indexOf("webkit"),Kb=nb("Macintosh"),Lb=nb("Windows"),Mb=nb("Linux")||nb("CrOS"),Nb,Ob=ba.navigator||null;Nb=!!Ob&&-1!=(Ob.appVersion||"").indexOf("X11");function Pb(){var b=ba.document;return b?b.documentMode:void 0}
+var Qb=function(){var b="",c;if(Gb&&ba.opera)return b=ba.opera.version,ka(b)?b():b;Ib?c=/rv\:([^\);]+)(\)|;)/:Hb?c=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:Jb&&(c=/WebKit\/(\S+)/);c&&(b=(b=c.exec(kb))?b[1]:"");return Hb&&(c=Pb(),c>parseFloat(b))?String(c):b}(),Rb={};function Tb(b){return Rb[b]||(Rb[b]=0<=Na(Qb,b))}var Ub=ba.document,Vb=Ub&&Hb?Pb()||("CSS1Compat"==Ub.compatMode?parseInt(Qb,10):5):void 0;var Wb="https:"===ba.location.protocol,Xb=Hb&&!Tb("9.0")&&""!==Qb;function Yb(b,c,d){return Math.min(Math.max(b,c),d)}function Zb(b,c){var d=b%c;return 0>d*c?d+c:d}function $b(b,c,d){return b+d*(c-b)}function bc(b){return b*Math.PI/180};function cc(b){return function(c){if(m(c))return[Yb(c[0],b[0],b[2]),Yb(c[1],b[1],b[3])]}}function dc(b){return b};function ec(b,c,d){var e=b.length;if(b[0]<=c)return 0;if(!(c<=b[e-1]))if(0<d)for(d=1;d<e;++d){if(b[d]<c)return d-1}else if(0>d)for(d=1;d<e;++d){if(b[d]<=c)return d}else for(d=1;d<e;++d){if(b[d]==c)return d;if(b[d]<c)return b[d-1]-c<c-b[d]?d-1:d}return e-1};function fc(b){return function(c,d,e){if(m(c))return c=ec(b,c,e),c=Yb(c+d,0,b.length-1),b[c]}}function gc(b,c,d){return function(e,f,g){if(m(e))return g=0<g?0:0>g?1:.5,e=Math.floor(Math.log(c/e)/Math.log(b)+g),f=Math.max(e+f,0),m(d)&&(f=Math.min(f,d)),c/Math.pow(b,f)}};function hc(b){if(m(b))return 0}function ic(b,c){if(m(b))return b+c}function jc(b){var c=2*Math.PI/b;return function(b,e){if(m(b))return b=Math.floor((b+e)/c+.5)*c}}function kc(){var b=bc(5);return function(c,d){if(m(c))return Math.abs(c+d)<=b?0:c+d}};function lc(b,c,d){this.center=b;this.resolution=c;this.rotation=d};var mc=!Hb||Hb&&9<=Vb,nc=!Hb||Hb&&9<=Vb,oc=Hb&&!Tb("9");!Jb||Tb("528");Ib&&Tb("1.9b")||Hb&&Tb("8")||Gb&&Tb("9.5")||Jb&&Tb("528");Ib&&!Tb("8")||Hb&&Tb("9");function pc(){0!=qc&&(rc[ma(this)]=this);this.Sa=this.Sa;this.la=this.la}var qc=0,rc={};pc.prototype.Sa=!1;pc.prototype.hc=function(){if(!this.Sa&&(this.Sa=!0,this.M(),0!=qc)){var b=ma(this);delete rc[b]}};function sc(b,c){var d=ta(tc,c);b.la||(b.la=[]);b.la.push(m(void 0)?sa(d,void 0):d)}pc.prototype.M=function(){if(this.la)for(;this.la.length;)this.la.shift()()};function tc(b){b&&"function"==typeof b.hc&&b.hc()};function uc(b,c){this.type=b;this.b=this.target=c;this.f=!1;this.Zf=!0}uc.prototype.hc=function(){};uc.prototype.lb=function(){this.f=!0};uc.prototype.preventDefault=function(){this.Zf=!1};function vc(b){b.lb()}function wc(b){b.preventDefault()};var xc=Hb?"focusout":"DOMFocusOut";function yc(b){yc[" "](b);return b}yc[" "]=ca;function zc(b,c){uc.call(this,b?b.type:"");this.relatedTarget=this.b=this.target=null;this.i=this.e=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.n=this.d=this.c=this.o=!1;this.state=null;this.g=!1;this.a=null;b&&Ac(this,b,c)}u(zc,uc);var Bc=[1,4,2];
+function Ac(b,c,d){b.a=c;var e=b.type=c.type;b.target=c.target||c.srcElement;b.b=d;if(d=c.relatedTarget){if(Ib){var f;a:{try{yc(d.nodeName);f=!0;break a}catch(g){}f=!1}f||(d=null)}}else"mouseover"==e?d=c.fromElement:"mouseout"==e&&(d=c.toElement);b.relatedTarget=d;Object.defineProperties?Object.defineProperties(b,{offsetX:{configurable:!0,enumerable:!0,get:b.bf,set:b.hl},offsetY:{configurable:!0,enumerable:!0,get:b.cf,set:b.il}}):(b.offsetX=b.bf(),b.offsetY=b.cf());b.clientX=void 0!==c.clientX?c.clientX:
+c.pageX;b.clientY=void 0!==c.clientY?c.clientY:c.pageY;b.screenX=c.screenX||0;b.screenY=c.screenY||0;b.button=c.button;b.e=c.keyCode||0;b.i=c.charCode||("keypress"==e?c.keyCode:0);b.o=c.ctrlKey;b.c=c.altKey;b.d=c.shiftKey;b.n=c.metaKey;b.g=Kb?c.metaKey:c.ctrlKey;b.state=c.state;c.defaultPrevented&&b.preventDefault()}function Cc(b){return(mc?0==b.a.button:"click"==b.type?!0:!!(b.a.button&Bc[0]))&&!(Jb&&Kb&&b.o)}l=zc.prototype;
+l.lb=function(){zc.S.lb.call(this);this.a.stopPropagation?this.a.stopPropagation():this.a.cancelBubble=!0};l.preventDefault=function(){zc.S.preventDefault.call(this);var b=this.a;if(b.preventDefault)b.preventDefault();else if(b.returnValue=!1,oc)try{if(b.ctrlKey||112<=b.keyCode&&123>=b.keyCode)b.keyCode=-1}catch(c){}};l.fh=function(){return this.a};l.bf=function(){return Jb||void 0!==this.a.offsetX?this.a.offsetX:this.a.layerX};
+l.hl=function(b){Object.defineProperties(this,{offsetX:{writable:!0,enumerable:!0,configurable:!0,value:b}})};l.cf=function(){return Jb||void 0!==this.a.offsetY?this.a.offsetY:this.a.layerY};l.il=function(b){Object.defineProperties(this,{offsetY:{writable:!0,enumerable:!0,configurable:!0,value:b}})};var Dc="closure_listenable_"+(1E6*Math.random()|0);function Ec(b){return!(!b||!b[Dc])}var Fc=0;function Gc(b,c,d,e,f){this.Xb=b;this.a=null;this.src=c;this.type=d;this.vc=!!e;this.qd=f;this.key=++Fc;this.uc=this.Xc=!1}function Hc(b){b.uc=!0;b.Xb=null;b.a=null;b.src=null;b.qd=null};function Ic(b){this.src=b;this.a={};this.c=0}Ic.prototype.add=function(b,c,d,e,f){var g=b.toString();b=this.a[g];b||(b=this.a[g]=[],this.c++);var h=Jc(b,c,e,f);-1<h?(c=b[h],d||(c.Xc=!1)):(c=new Gc(c,this.src,g,!!e,f),c.Xc=d,b.push(c));return c};Ic.prototype.remove=function(b,c,d,e){b=b.toString();if(!(b in this.a))return!1;var f=this.a[b];c=Jc(f,c,d,e);return-1<c?(Hc(f[c]),Sa.splice.call(f,c,1),0==f.length&&(delete this.a[b],this.c--),!0):!1};
+function Kc(b,c){var d=c.type;if(!(d in b.a))return!1;var e=ab(b.a[d],c);e&&(Hc(c),0==b.a[d].length&&(delete b.a[d],b.c--));return e}function Lc(b,c,d,e,f){b=b.a[c.toString()];c=-1;b&&(c=Jc(b,d,e,f));return-1<c?b[c]:null}function Mc(b,c,d){var e=m(c),f=e?c.toString():"",g=m(d);return pb(b.a,function(b){for(var c=0;c<b.length;++c)if(!(e&&b[c].type!=f||g&&b[c].vc!=d))return!0;return!1})}
+function Jc(b,c,d,e){for(var f=0;f<b.length;++f){var g=b[f];if(!g.uc&&g.Xb==c&&g.vc==!!d&&g.qd==e)return f}return-1};var Nc="closure_lm_"+(1E6*Math.random()|0),Oc={},Pc=0;function z(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)z(b,c[g],d,e,f);return null}d=Qc(d);return Ec(b)?b.La(c,d,e,f):Rc(b,c,d,!1,e,f)}function Rc(b,c,d,e,f,g){if(!c)throw Error("Invalid event type");var h=!!f,k=Sc(b);k||(b[Nc]=k=new Ic(b));d=k.add(c,d,e,f,g);if(d.a)return d;e=Tc();d.a=e;e.src=b;e.Xb=d;b.addEventListener?b.addEventListener(c.toString(),e,h):b.attachEvent(Uc(c.toString()),e);Pc++;return d}
+function Tc(){var b=Vc,c=nc?function(d){return b.call(c.src,c.Xb,d)}:function(d){d=b.call(c.src,c.Xb,d);if(!d)return d};return c}function Wc(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)Wc(b,c[g],d,e,f);return null}d=Qc(d);return Ec(b)?b.gb.add(String(c),d,!0,e,f):Rc(b,c,d,!0,e,f)}function Xc(b,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)Xc(b,c[g],d,e,f);else d=Qc(d),Ec(b)?b.Fe(c,d,e,f):b&&(b=Sc(b))&&(c=Lc(b,c,d,!!e,f))&&Yc(c)}
+function Yc(b){if(ja(b)||!b||b.uc)return!1;var c=b.src;if(Ec(c))return Kc(c.gb,b);var d=b.type,e=b.a;c.removeEventListener?c.removeEventListener(d,e,b.vc):c.detachEvent&&c.detachEvent(Uc(d),e);Pc--;(d=Sc(c))?(Kc(d,b),0==d.c&&(d.src=null,c[Nc]=null)):Hc(b);return!0}function Uc(b){return b in Oc?Oc[b]:Oc[b]="on"+b}function Zc(b,c,d,e){var f=!0;if(b=Sc(b))if(c=b.a[c.toString()])for(c=c.concat(),b=0;b<c.length;b++){var g=c[b];g&&g.vc==d&&!g.uc&&(g=$c(g,e),f=f&&!1!==g)}return f}
+function $c(b,c){var d=b.Xb,e=b.qd||b.src;b.Xc&&Yc(b);return d.call(e,c)}
+function Vc(b,c){if(b.uc)return!0;if(!nc){var d;if(!(d=c))a:{d=["window","event"];for(var e=ba,f;f=d.shift();)if(null!=e[f])e=e[f];else{d=null;break a}d=e}f=d;d=new zc(f,this);e=!0;if(!(0>f.keyCode||void 0!=f.returnValue)){a:{var g=!1;if(0==f.keyCode)try{f.keyCode=-1;break a}catch(h){g=!0}if(g||void 0==f.returnValue)f.returnValue=!0}f=[];for(g=d.b;g;g=g.parentNode)f.push(g);for(var g=b.type,k=f.length-1;!d.f&&0<=k;k--){d.b=f[k];var n=Zc(f[k],g,!0,d),e=e&&n}for(k=0;!d.f&&k<f.length;k++)d.b=f[k],n=
+Zc(f[k],g,!1,d),e=e&&n}return e}return $c(b,new zc(c,this))}function Sc(b){b=b[Nc];return b instanceof Ic?b:null}var ad="__closure_events_fn_"+(1E9*Math.random()>>>0);function Qc(b){if(ka(b))return b;b[ad]||(b[ad]=function(c){return b.handleEvent(c)});return b[ad]};function bd(b){return function(){return b}}var cd=bd(!1),dd=bd(!0);function ed(b){return b}function gd(b){var c;c=c||0;return function(){return b.apply(this,Array.prototype.slice.call(arguments,0,c))}}function hd(b){var c=arguments,d=c.length;return function(){for(var b,f=0;f<d;f++)b=c[f].apply(this,arguments);return b}}function id(b){var c=arguments,d=c.length;return function(){for(var b=0;b<d;b++)if(!c[b].apply(this,arguments))return!1;return!0}};function jd(){pc.call(this);this.gb=new Ic(this);this.Ig=this;this.Vd=null}u(jd,pc);jd.prototype[Dc]=!0;l=jd.prototype;l.addEventListener=function(b,c,d,e){z(this,b,c,d,e)};l.removeEventListener=function(b,c,d,e){Xc(this,b,c,d,e)};
+l.dispatchEvent=function(b){var c,d=this.Vd;if(d)for(c=[];d;d=d.Vd)c.push(d);var d=this.Ig,e=b.type||b;if(ia(b))b=new uc(b,d);else if(b instanceof uc)b.target=b.target||d;else{var f=b;b=new uc(e,d);Eb(b,f)}var f=!0,g;if(c)for(var h=c.length-1;!b.f&&0<=h;h--)g=b.b=c[h],f=kd(g,e,!0,b)&&f;b.f||(g=b.b=d,f=kd(g,e,!0,b)&&f,b.f||(f=kd(g,e,!1,b)&&f));if(c)for(h=0;!b.f&&h<c.length;h++)g=b.b=c[h],f=kd(g,e,!1,b)&&f;return f};
+l.M=function(){jd.S.M.call(this);if(this.gb){var b=this.gb,c=0,d;for(d in b.a){for(var e=b.a[d],f=0;f<e.length;f++)++c,Hc(e[f]);delete b.a[d];b.c--}}this.Vd=null};l.La=function(b,c,d,e){return this.gb.add(String(b),c,!1,d,e)};l.Fe=function(b,c,d,e){return this.gb.remove(String(b),c,d,e)};
+function kd(b,c,d,e){c=b.gb.a[String(c)];if(!c)return!0;c=c.concat();for(var f=!0,g=0;g<c.length;++g){var h=c[g];if(h&&!h.uc&&h.vc==d){var k=h.Xb,n=h.qd||h.src;h.Xc&&Kc(b.gb,h);f=!1!==k.call(n,e)&&f}}return f&&0!=e.Zf}function ld(b,c,d){return Mc(b.gb,m(c)?String(c):void 0,d)};function md(){jd.call(this);this.c=0}u(md,jd);function nd(b){Yc(b)}l=md.prototype;l.l=function(){++this.c;this.dispatchEvent("change")};l.A=function(){return this.c};l.u=function(b,c,d){return z(this,b,c,!1,d)};l.B=function(b,c,d){return Wc(this,b,c,!1,d)};l.v=function(b,c,d){Xc(this,b,c,!1,d)};l.C=nd;function od(b,c,d){uc.call(this,b);this.key=c;this.oldValue=d}u(od,uc);function pd(b,c,d,e){this.source=b;this.target=c;this.b=d;this.c=e;this.d=this.a=ed}pd.prototype.e=function(b,c){var d=rd(this.source,this.b);this.a=b;this.d=c;sd(this.source,this.b,d)};function td(b){md.call(this);ma(this);this.o={};this.Ra={};this.ec={};m(b)&&this.G(b)}u(td,md);var ud={},vd={},wd={};function xd(b){return ud.hasOwnProperty(b)?ud[b]:ud[b]="change:"+b}
+function rd(b,c){var d=vd.hasOwnProperty(c)?vd[c]:vd[c]="get"+(String(c.charAt(0)).toUpperCase()+String(c.substr(1)).toLowerCase()),d=x(b,d);return m(d)?d.call(b):b.get(c)}l=td.prototype;l.O=function(b,c,d){d=d||b;this.P(b);var e=xd(d);this.ec[b]=z(c,e,function(c){sd(this,b,c.oldValue)},void 0,this);c=new pd(this,c,b,d);this.Ra[b]=c;sd(this,b,this.o[b]);return c};l.get=function(b){var c,d=this.Ra;d.hasOwnProperty(b)?(b=d[b],c=rd(b.target,b.c),c=b.d(c)):this.o.hasOwnProperty(b)&&(c=this.o[b]);return c};
+l.I=function(){var b=this.Ra,c;if(xb(this.o)){if(xb(b))return[];c=b}else if(xb(b))c=this.o;else{c={};for(var d in this.o)c[d]=!0;for(d in b)c[d]=!0}return sb(c)};l.L=function(){var b={},c;for(c in this.o)b[c]=this.o[c];for(c in this.Ra)b[c]=this.get(c);return b};function sd(b,c,d){var e;e=xd(c);b.dispatchEvent(new od(e,c,d));b.dispatchEvent(new od("propertychange",c,d))}
+l.set=function(b,c){var d=this.Ra;if(d.hasOwnProperty(b)){var e=d[b];c=e.a(c);var d=e.target,e=e.c,f=c,g=wd.hasOwnProperty(e)?wd[e]:wd[e]="set"+(String(e.charAt(0)).toUpperCase()+String(e.substr(1)).toLowerCase()),g=x(d,g);m(g)?g.call(d,f):d.set(e,f)}else d=this.o[b],this.o[b]=c,sd(this,b,d)};l.G=function(b){for(var c in b)this.set(c,b[c])};l.P=function(b){var c=this.ec,d=c[b];d&&(delete c[b],Yc(d),c=this.get(b),delete this.Ra[b],this.o[b]=c)};l.R=function(){for(var b in this.ec)this.P(b)};function yd(b,c){b[0]+=c[0];b[1]+=c[1];return b}function zd(b,c){var d=b[0],e=b[1],f=c[0],g=c[1],h=f[0],f=f[1],k=g[0],g=g[1],n=k-h,p=g-f,d=0===n&&0===p?0:(n*(d-h)+p*(e-f))/(n*n+p*p||0);0>=d||(1<=d?(h=k,f=g):(h+=d*n,f+=d*p));return[h,f]}function Ad(b,c){var d=Zb(b+180,360)-180,e=Math.abs(Math.round(3600*d));return Math.floor(e/3600)+"\u00b0 "+Math.floor(e/60%60)+"\u2032 "+Math.floor(e%60)+"\u2033 "+c.charAt(0>d?1:0)}
+function Bd(b,c,d){return m(b)?c.replace("{x}",b[0].toFixed(d)).replace("{y}",b[1].toFixed(d)):""}function Cd(b,c){for(var d=!0,e=b.length-1;0<=e;--e)if(b[e]!=c[e]){d=!1;break}return d}function Dd(b,c){var d=Math.cos(c),e=Math.sin(c),f=b[1]*d+b[0]*e;b[0]=b[0]*d-b[1]*e;b[1]=f;return b}function Ed(b,c){var d=b[0]-c[0],e=b[1]-c[1];return d*d+e*e}function Fd(b,c){return Bd(b,"{x}, {y}",c)};function Gd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}Gd.prototype.a=4;Gd.prototype.set=function(b,c){c=c||0;for(var d=0;d<b.length&&c+d<this.length;d++)this[c+d]=b[d]};Gd.prototype.toString=Array.prototype.join;"undefined"==typeof Float32Array&&(Gd.BYTES_PER_ELEMENT=4,Gd.prototype.BYTES_PER_ELEMENT=Gd.prototype.a,Gd.prototype.set=Gd.prototype.set,Gd.prototype.toString=Gd.prototype.toString,t("Float32Array",Gd,void 0));function Hd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}Hd.prototype.a=8;Hd.prototype.set=function(b,c){c=c||0;for(var d=0;d<b.length&&c+d<this.length;d++)this[c+d]=b[d]};Hd.prototype.toString=Array.prototype.join;if("undefined"==typeof Float64Array){try{Hd.BYTES_PER_ELEMENT=8}catch(Id){}Hd.prototype.BYTES_PER_ELEMENT=Hd.prototype.a;Hd.prototype.set=Hd.prototype.set;Hd.prototype.toString=Hd.prototype.toString;t("Float64Array",Hd,void 0)};function Jd(b,c,d,e,f){b[0]=c;b[1]=d;b[2]=e;b[3]=f};function Kd(){var b=Array(16);Ld(b,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return b}function Md(){var b=Array(16);Ld(b,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return b}function Ld(b,c,d,e,f,g,h,k,n,p,q,r,s,v,y,C,F){b[0]=c;b[1]=d;b[2]=e;b[3]=f;b[4]=g;b[5]=h;b[6]=k;b[7]=n;b[8]=p;b[9]=q;b[10]=r;b[11]=s;b[12]=v;b[13]=y;b[14]=C;b[15]=F}
+function Nd(b,c){b[0]=c[0];b[1]=c[1];b[2]=c[2];b[3]=c[3];b[4]=c[4];b[5]=c[5];b[6]=c[6];b[7]=c[7];b[8]=c[8];b[9]=c[9];b[10]=c[10];b[11]=c[11];b[12]=c[12];b[13]=c[13];b[14]=c[14];b[15]=c[15]}function Od(b){b[0]=1;b[1]=0;b[2]=0;b[3]=0;b[4]=0;b[5]=1;b[6]=0;b[7]=0;b[8]=0;b[9]=0;b[10]=1;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1}
+function Pd(b,c,d){var e=b[0],f=b[1],g=b[2],h=b[3],k=b[4],n=b[5],p=b[6],q=b[7],r=b[8],s=b[9],v=b[10],y=b[11],C=b[12],F=b[13],G=b[14];b=b[15];var w=c[0],U=c[1],N=c[2],Y=c[3],T=c[4],qa=c[5],vb=c[6],Ka=c[7],ac=c[8],Sb=c[9],La=c[10],Pa=c[11],Ud=c[12],qd=c[13],fd=c[14];c=c[15];d[0]=e*w+k*U+r*N+C*Y;d[1]=f*w+n*U+s*N+F*Y;d[2]=g*w+p*U+v*N+G*Y;d[3]=h*w+q*U+y*N+b*Y;d[4]=e*T+k*qa+r*vb+C*Ka;d[5]=f*T+n*qa+s*vb+F*Ka;d[6]=g*T+p*qa+v*vb+G*Ka;d[7]=h*T+q*qa+y*vb+b*Ka;d[8]=e*ac+k*Sb+r*La+C*Pa;d[9]=f*ac+n*Sb+s*La+F*Pa;
+d[10]=g*ac+p*Sb+v*La+G*Pa;d[11]=h*ac+q*Sb+y*La+b*Pa;d[12]=e*Ud+k*qd+r*fd+C*c;d[13]=f*Ud+n*qd+s*fd+F*c;d[14]=g*Ud+p*qd+v*fd+G*c;d[15]=h*Ud+q*qd+y*fd+b*c}function Qd(b,c,d){var e=b[1]*c+b[5]*d+0*b[9]+b[13],f=b[2]*c+b[6]*d+0*b[10]+b[14],g=b[3]*c+b[7]*d+0*b[11]+b[15];b[12]=b[0]*c+b[4]*d+0*b[8]+b[12];b[13]=e;b[14]=f;b[15]=g}function Rd(b,c,d){Ld(b,b[0]*c,b[1]*c,b[2]*c,b[3]*c,b[4]*d,b[5]*d,b[6]*d,b[7]*d,1*b[8],1*b[9],1*b[10],1*b[11],b[12],b[13],b[14],b[15])}
+function Sd(b,c){var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],k=b[5],n=b[6],p=b[7],q=Math.cos(c),r=Math.sin(c);b[0]=d*q+h*r;b[1]=e*q+k*r;b[2]=f*q+n*r;b[3]=g*q+p*r;b[4]=d*-r+h*q;b[5]=e*-r+k*q;b[6]=f*-r+n*q;b[7]=g*-r+p*q}new Float64Array(3);new Float64Array(3);new Float64Array(4);new Float64Array(4);new Float64Array(4);new Float64Array(16);function Td(b){for(var c=Vd(),d=0,e=b.length;d<e;++d){var f=c,g=b[d];g[0]<f[0]&&(f[0]=g[0]);g[0]>f[2]&&(f[2]=g[0]);g[1]<f[1]&&(f[1]=g[1]);g[1]>f[3]&&(f[3]=g[1])}return c}function Wd(b,c,d){var e=Math.min.apply(null,b),f=Math.min.apply(null,c);b=Math.max.apply(null,b);c=Math.max.apply(null,c);return Xd(e,f,b,c,d)}function Yd(b,c,d){return m(d)?(d[0]=b[0]-c,d[1]=b[1]-c,d[2]=b[2]+c,d[3]=b[3]+c,d):[b[0]-c,b[1]-c,b[2]+c,b[3]+c]}
+function Zd(b,c,d){c=c<b[0]?b[0]-c:b[2]<c?c-b[2]:0;b=d<b[1]?b[1]-d:b[3]<d?d-b[3]:0;return c*c+b*b}function $d(b,c){return b[0]<=c[0]&&c[2]<=b[2]&&b[1]<=c[1]&&c[3]<=b[3]}function ae(b,c,d){return b[0]<=c&&c<=b[2]&&b[1]<=d&&d<=b[3]}function be(b,c){var d=b[1],e=b[2],f=b[3],g=c[0],h=c[1],k=0;g<b[0]?k=k|16:g>e&&(k=k|4);h<d?k|=8:h>f&&(k|=2);0===k&&(k=1);return k}function Vd(){return[Infinity,Infinity,-Infinity,-Infinity]}function Xd(b,c,d,e,f){return m(f)?(f[0]=b,f[1]=c,f[2]=d,f[3]=e,f):[b,c,d,e]}
+function ce(b,c){var d=b[0],e=b[1];return Xd(d,e,d,e,c)}function de(b,c){return b[0]==c[0]&&b[2]==c[2]&&b[1]==c[1]&&b[3]==c[3]}function ee(b,c){c[0]<b[0]&&(b[0]=c[0]);c[2]>b[2]&&(b[2]=c[2]);c[1]<b[1]&&(b[1]=c[1]);c[3]>b[3]&&(b[3]=c[3]);return b}function fe(b,c,d,e,f){for(;d<e;d+=f){var g=b,h=c[d],k=c[d+1];g[0]=Math.min(g[0],h);g[1]=Math.min(g[1],k);g[2]=Math.max(g[2],h);g[3]=Math.max(g[3],k)}return b}
+function ge(b,c){var d;return(d=c.call(void 0,he(b)))||(d=c.call(void 0,ie(b)))||(d=c.call(void 0,je(b)))?d:(d=c.call(void 0,ie(b)))?d:!1}function he(b){return[b[0],b[1]]}function ie(b){return[b[2],b[1]]}function ke(b){return[(b[0]+b[2])/2,(b[1]+b[3])/2]}function le(b,c){var d;"bottom-left"===c?d=he(b):"bottom-right"===c?d=ie(b):"top-left"===c?d=me(b):"top-right"===c&&(d=je(b));return d}
+function ne(b,c,d,e){var f=c*e[0]/2;e=c*e[1]/2;c=Math.cos(d);d=Math.sin(d);f=[-f,-f,f,f];e=[-e,e,-e,e];var g,h,k;for(g=0;4>g;++g)h=f[g],k=e[g],f[g]=b[0]+h*c-k*d,e[g]=b[1]+h*d+k*c;return Wd(f,e,void 0)}function oe(b){return b[3]-b[1]}function pe(b,c,d){d=m(d)?d:Vd();qe(b,c)&&(d[0]=b[0]>c[0]?b[0]:c[0],d[1]=b[1]>c[1]?b[1]:c[1],d[2]=b[2]<c[2]?b[2]:c[2],d[3]=b[3]<c[3]?b[3]:c[3]);return d}function me(b){return[b[0],b[3]]}function je(b){return[b[2],b[3]]}function re(b){return b[2]-b[0]}
+function qe(b,c){return b[0]<=c[2]&&b[2]>=c[0]&&b[1]<=c[3]&&b[3]>=c[1]}function se(b){return b[2]<b[0]||b[3]<b[1]}function te(b,c){return m(c)?(c[0]=b[0],c[1]=b[1],c[2]=b[2],c[3]=b[3],c):b}function ue(b,c){var d=(b[2]-b[0])/2*(c-1),e=(b[3]-b[1])/2*(c-1);b[0]-=d;b[2]+=d;b[1]-=e;b[3]+=e}function we(b,c,d){b=[b[0],b[1],b[0],b[3],b[2],b[1],b[2],b[3]];c(b,b,2);return Wd([b[0],b[2],b[4],b[6]],[b[1],b[3],b[5],b[7]],d)};/*
 
  Latitude/longitude spherical geodesy formulae taken from
  http://www.movable-type.co.uk/scripts/latlong.html
  Licenced under CC-BY-3.0.
 */
-function ue(a){this.radius=a}function ve(a,b){var c=Mb(a[1]),d=Mb(b[1]),e=(d-c)/2,f=Mb(b[0]-a[0])/2,c=Math.sin(e)*Math.sin(e)+Math.sin(f)*Math.sin(f)*Math.cos(c)*Math.cos(d);return 2*we.radius*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))}ue.prototype.offset=function(a,b,c){var d=Mb(a[1]);b/=this.radius;var e=Math.asin(Math.sin(d)*Math.cos(b)+Math.cos(d)*Math.sin(b)*Math.cos(c));return[180*(Mb(a[0])+Math.atan2(Math.sin(c)*Math.sin(b)*Math.cos(d),Math.cos(b)-Math.sin(d)*Math.sin(e)))/Math.PI,180*e/Math.PI]};var we=new ue(6370997);var xe={};xe.degrees=2*Math.PI*we.radius/360;xe.ft=.3048;xe.m=1;function ye(a){this.a=a.code;this.c=a.units;this.g=l(a.extent)?a.extent:null;this.d=l(a.worldExtent)?a.worldExtent:null;this.b=l(a.axisOrientation)?a.axisOrientation:"enu";this.f=l(a.global)?a.global:!1;this.e=null}k=ye.prototype;k.mf=function(){return this.a};k.s=function(){return this.g};k.ih=function(){return this.c};k.ae=function(){return xe[this.c]};k.Pf=function(){return this.d};function ze(a){return a.b}k.ug=function(){return this.f};
-k.jh=function(a){this.g=a};k.zi=function(a){this.d=a};k.yd=function(a,b){if("degrees"==this.c)return a;var c=Ae(this,Be("EPSG:4326")),d=[b[0]-a/2,b[1],b[0]+a/2,b[1],b[0],b[1]-a/2,b[0],b[1]+a/2],d=c(d,d,2),c=(ve(d.slice(0,2),d.slice(2,4))+ve(d.slice(4,6),d.slice(6,8)))/2;"ft"==this.c&&(c/=.3048);return c};var Ce={},De={};function Ee(a){Fe(a);Ta(a,function(b){Ta(a,function(a){b!==a&&Ge(b,a,He)})})}function Ie(){var a=Je,b=Ke,c=Le;Ta(Me,function(d){Ta(a,function(a){Ge(d,a,b);Ge(a,d,c)})})}
-function Ne(a){Ce[a.a]=a;Ge(a,a,He)}function Fe(a){var b=[];Ta(a,function(a){b.push(Ne(a))})}function Oe(a){return null!=a?ja(a)?Be(a):a:Be("EPSG:3857")}function Ge(a,b,c){a=a.a;b=b.a;a in De||(De[a]={});De[a][b]=c}function Pe(a,b,c,d){a=Be(a);b=Be(b);Ge(a,b,Qe(c));Ge(b,a,Qe(d))}function Qe(a){return function(b,c,d){var e=b.length;d=l(d)?d:2;c=l(c)?c:Array(e);var f,g;for(g=0;g<e;g+=d)for(f=a([b[g],b[g+1]]),c[g]=f[0],c[g+1]=f[1],f=d-1;2<=f;--f)c[g+f]=b[g+f];return c}}
-function Be(a){var b;if(a instanceof ye)b=a;else if(ja(a)){if(b=Ce[a],!l(b)&&"function"==typeof proj4){var c=proj4.defs(a);if(l(c)){b=c.units;!l(b)&&l(c.to_meter)&&(b=c.to_meter.toString(),xe[b]=c.to_meter);b=new ye({code:a,units:b,axisOrientation:c.axis});Ne(b);var d,e,f;for(d in Ce)e=proj4.defs(d),l(e)&&(f=Be(d),e===c?Ee([f,b]):(e=proj4(d,a),Pe(f,b,e.forward,e.inverse)))}else b=null}}else b=null;return b}function Re(a,b){return a===b?!0:a.c!=b.c?!1:Ae(a,b)===He}
-function Se(a,b){var c=Be(a),d=Be(b);return Ae(c,d)}function Ae(a,b){var c=a.a,d=b.a,e;c in De&&d in De[c]&&(e=De[c][d]);l(e)||(e=Te);return e}function Te(a,b){if(l(b)&&a!==b){for(var c=0,d=a.length;c<d;++c)b[c]=a[c];a=b}return a}function He(a,b){var c;if(l(b)){c=0;for(var d=a.length;c<d;++c)b[c]=a[c];c=b}else c=a.slice();return c}function Ue(a,b,c){b=Se(b,c);return te(a,b)};function z(a){pd.call(this);a=l(a)?a:{};this.l=[0,0];var b={};b.center=l(a.center)?a.center:null;this.o=Oe(a.projection);var c,d,e,f=l(a.minZoom)?a.minZoom:0;c=l(a.maxZoom)?a.maxZoom:28;var g=l(a.zoomFactor)?a.zoomFactor:2;if(l(a.resolutions))c=a.resolutions,d=c[0],e=c[c.length-1],c=Rb(c);else{d=Oe(a.projection);e=d.s();var h=(null===e?360*xe.degrees/xe[d.c]:Math.max(fe(e),ge(e)))/256/Math.pow(2,0),m=h/Math.pow(2,28);d=a.maxResolution;l(d)?f=0:d=h/Math.pow(g,f);e=a.minResolution;l(e)||(e=l(a.maxZoom)?
-l(a.maxResolution)?d/Math.pow(g,c):h/Math.pow(g,c):m);c=f+Math.floor(Math.log(d/e)/Math.log(g));e=d/Math.pow(g,c-f);c=Sb(g,d,c-f)}this.f=d;this.u=e;this.k=f;f=l(a.extent)?Nb(a.extent):Ob;(l(a.enableRotation)?a.enableRotation:1)?(d=a.constrainRotation,d=l(d)&&!0!==d?!1===d?Ub:ka(d)?Vb(d):Ub:Wb()):d=Tb;this.r=new Xb(f,c,d);l(a.resolution)?b.resolution=a.resolution:l(a.zoom)&&(b.resolution=this.constrainResolution(this.f,a.zoom-this.k));b.rotation=l(a.rotation)?a.rotation:0;this.L(b)}t(z,pd);
-z.prototype.g=function(a){return this.r.center(a)};z.prototype.constrainResolution=function(a,b,c){return this.r.resolution(a,b||0,c||0)};z.prototype.constrainRotation=function(a,b){return this.r.rotation(a,b||0)};z.prototype.a=function(){return this.get("center")};z.prototype.getCenter=z.prototype.a;z.prototype.D=function(a){var b=this.a(),c=this.b();return[b[0]-c*a[0]/2,b[1]-c*a[1]/2,b[0]+c*a[0]/2,b[1]+c*a[1]/2]};z.prototype.F=function(){return this.o};z.prototype.b=function(){return this.get("resolution")};
-z.prototype.getResolution=z.prototype.b;z.prototype.i=function(a,b){return Math.max(fe(a)/b[0],ge(a)/b[1])};function Ve(a){var b=a.f,c=Math.log(b/a.u)/Math.log(2);return function(a){return b/Math.pow(2,a*c)}}z.prototype.e=function(){return this.get("rotation")};z.prototype.getRotation=z.prototype.e;function We(a){var b=a.f,c=Math.log(b/a.u)/Math.log(2);return function(a){return Math.log(b/a)/Math.log(2)/c}}
-function Xe(a){var b=a.a(),c=a.o,d=a.b();a=a.e();return{center:b.slice(),projection:l(c)?c:null,resolution:d,rotation:l(a)?a:0}}k=z.prototype;k.Rf=function(){var a,b=this.b();if(l(b)){var c,d=0;do{c=this.constrainResolution(this.f,d);if(c==b){a=d;break}++d}while(c>this.u)}return l(a)?this.k+a:a};k.Yd=function(a,b){if(!ee(a)){this.Ka(je(a));var c=this.i(a,b),d=this.constrainResolution(c,0,0);d<c&&(d=this.constrainResolution(d,-1,0));this.d(d)}};
-k.lf=function(a,b,c){var d=l(c)?c:{};c=l(d.padding)?d.padding:[0,0,0,0];var e=l(d.constrainResolution)?d.constrainResolution:!0,f=l(d.nearest)?d.nearest:!1,g;l(d.minResolution)?g=d.minResolution:l(d.maxZoom)?g=this.constrainResolution(this.f,d.maxZoom-this.k,0):g=0;var h=a.j,m=this.e(),d=Math.cos(-m),m=Math.sin(-m),n=Infinity,p=Infinity,r=-Infinity,q=-Infinity;a=a.a;for(var u=0,x=h.length;u<x;u+=a)var B=h[u]*d-h[u+1]*m,E=h[u]*m+h[u+1]*d,n=Math.min(n,B),p=Math.min(p,E),r=Math.max(r,B),q=Math.max(q,
-E);b=this.i([n,p,r,q],[b[0]-c[1]-c[3],b[1]-c[0]-c[2]]);b=isNaN(b)?g:Math.max(b,g);e&&(g=this.constrainResolution(b,0,0),!f&&g<b&&(g=this.constrainResolution(g,-1,0)),b=g);this.d(b);m=-m;f=(n+r)/2+(c[1]-c[3])/2*b;c=(p+q)/2+(c[0]-c[2])/2*b;this.Ka([f*d-c*m,c*d+f*m])};k.ff=function(a,b,c){var d=this.e(),e=Math.cos(-d),d=Math.sin(-d),f=a[0]*e-a[1]*d;a=a[1]*e+a[0]*d;var g=this.b(),f=f+(b[0]/2-c[0])*g;a+=(c[1]-b[1]/2)*g;d=-d;this.Ka([f*e-a*d,a*e+f*d])};function Ye(a){return null!=a.a()&&l(a.b())}
-k.rotate=function(a,b){if(l(b)){var c,d=this.a();l(d)&&(c=[d[0]-b[0],d[1]-b[1]],Bd(c,a-this.e()),vd(c,b));this.Ka(c)}this.q(a)};k.Ka=function(a){this.set("center",a)};z.prototype.setCenter=z.prototype.Ka;function Ze(a,b){a.l[1]+=b}z.prototype.d=function(a){this.set("resolution",a)};z.prototype.setResolution=z.prototype.d;z.prototype.q=function(a){this.set("rotation",a)};z.prototype.setRotation=z.prototype.q;z.prototype.Q=function(a){a=this.constrainResolution(this.f,a-this.k,0);this.d(a)};function $e(a){return 1-Math.pow(1-a,3)};function af(a){return 3*a*a-2*a*a*a}function bf(a){return a}function cf(a){return.5>a?af(2*a):1-af(2*(a-.5))};function df(a){var b=a.source,c=l(a.start)?a.start:ua(),d=b[0],e=b[1],f=l(a.duration)?a.duration:1E3,g=l(a.easing)?a.easing:af;return function(a,b){if(b.time<c)return b.animate=!0,b.viewHints[0]+=1,!0;if(b.time<c+f){var n=1-g((b.time-c)/f),p=d-b.viewState.center[0],r=e-b.viewState.center[1];b.animate=!0;b.viewState.center[0]+=n*p;b.viewState.center[1]+=n*r;b.viewHints[0]+=1;return!0}return!1}}
-function ef(a){var b=l(a.rotation)?a.rotation:0,c=l(a.start)?a.start:ua(),d=l(a.duration)?a.duration:1E3,e=l(a.easing)?a.easing:af,f=l(a.anchor)?a.anchor:null;return function(a,h){if(h.time<c)return h.animate=!0,h.viewHints[0]+=1,!0;if(h.time<c+d){var m=1-e((h.time-c)/d),m=(b-h.viewState.rotation)*m;h.animate=!0;h.viewState.rotation+=m;if(null!==f){var n=h.viewState.center;n[0]-=f[0];n[1]-=f[1];Bd(n,m);vd(n,f)}h.viewHints[0]+=1;return!0}return!1}}
-function ff(a){var b=a.resolution,c=l(a.start)?a.start:ua(),d=l(a.duration)?a.duration:1E3,e=l(a.easing)?a.easing:af;return function(a,g){if(g.time<c)return g.animate=!0,g.viewHints[0]+=1,!0;if(g.time<c+d){var h=1-e((g.time-c)/d),m=b-g.viewState.resolution;g.animate=!0;g.viewState.resolution+=h*m;g.viewHints[0]+=1;return!0}return!1}};function gf(a,b,c,d){return l(d)?(d[0]=a,d[1]=b,d[2]=c,d):[a,b,c]}function hf(a,b,c){return a+"/"+b+"/"+c}function jf(a){var b=a[0],c=Array(b),d=1<<b-1,e,f;for(e=0;e<b;++e)f=48,a[1]&d&&(f+=1),a[2]&d&&(f+=2),c[e]=String.fromCharCode(f),d>>=1;return c.join("")}function kf(a){return hf(a[0],a[1],a[2])};function lf(a,b,c,d){this.a=a;this.d=b;this.b=c;this.c=d}function mf(a,b,c,d,e){return l(e)?(e.a=a,e.d=b,e.b=c,e.c=d,e):new lf(a,b,c,d)}lf.prototype.contains=function(a){return nf(this,a[1],a[2])};function of(a,b){return a.a<=b.a&&b.d<=a.d&&a.b<=b.b&&b.c<=a.c}function nf(a,b,c){return a.a<=b&&b<=a.d&&a.b<=c&&c<=a.c}function pf(a,b){return a.a==b.a&&a.b==b.b&&a.d==b.d&&a.c==b.c};function qf(a){this.c=a.html;this.a=l(a.tileRanges)?a.tileRanges:null}qf.prototype.b=function(){return this.c};var rf=!ub||ub&&9<=Gb;!vb&&!ub||ub&&ub&&9<=Gb||vb&&Eb("1.9.1");ub&&Eb("9");function sf(a,b){this.x=l(a)?a:0;this.y=l(b)?b:0}k=sf.prototype;k.clone=function(){return new sf(this.x,this.y)};k.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};k.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};k.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};k.scale=function(a,b){var c=ka(b)?b:a;this.x*=a;this.y*=c;return this};function tf(a,b){this.width=a;this.height=b}k=tf.prototype;k.clone=function(){return new tf(this.width,this.height)};k.ka=function(){return!(this.width*this.height)};k.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};k.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};k.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};
-k.scale=function(a,b){var c=ka(b)?b:a;this.width*=a;this.height*=c;return this};function vf(a){return a?new wf(xf(a)):wa||(wa=new wf)}function yf(a){var b=document;return ja(a)?b.getElementById(a):a}function zf(a,b){sc(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Af?a.setAttribute(Af[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})}
-var Af={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};function Bf(a){a=a.document.documentElement;return new tf(a.clientWidth,a.clientHeight)}
-function Cf(a,b,c){var d=arguments,e=document,f=d[0],g=d[1];if(!rf&&g&&(g.name||g.type)){f=["<",f];g.name&&f.push(' name="',Ca(g.name),'"');if(g.type){f.push(' type="',Ca(g.type),'"');var h={};Hc(h,g);delete h.type;g=h}f.push(">");f=f.join("")}f=e.createElement(f);g&&(ja(g)?f.className=g:ha(g)?f.className=g.join(" "):zf(f,g));2<d.length&&Df(e,f,d,2);return f}
-function Df(a,b,c,d){function e(c){c&&b.appendChild(ja(c)?a.createTextNode(c):c)}for(;d<c.length;d++){var f=c[d];!ia(f)||ma(f)&&0<f.nodeType?e(f):Ta(Ef(f)?cb(f):f,e)}}function Ff(a){return document.createElement(a)}function Gf(a,b){Df(xf(a),a,arguments,1)}function Hf(a){for(var b;b=a.firstChild;)a.removeChild(b)}function If(a,b){b.parentNode&&b.parentNode.insertBefore(a,b.nextSibling)}function Jf(a,b,c){a.insertBefore(b,a.childNodes[c]||null)}
-function Kf(a){a&&a.parentNode&&a.parentNode.removeChild(a)}function Lf(a){if(void 0!=a.firstElementChild)a=a.firstElementChild;else for(a=a.firstChild;a&&1!=a.nodeType;)a=a.nextSibling;return a}function Mf(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}function xf(a){return 9==a.nodeType?a:a.ownerDocument||a.document}
-function Ef(a){if(a&&"number"==typeof a.length){if(ma(a))return"function"==typeof a.item||"string"==typeof a.item;if(la(a))return"function"==typeof a.item}return!1}function wf(a){this.a=a||ba.document||document}function Nf(a){var b=a.a;a=wb?b.body||b.documentElement:b.documentElement;b=b.parentWindow||b.defaultView;return ub&&Eb("10")&&b.pageYOffset!=a.scrollTop?new sf(a.scrollLeft,a.scrollTop):new sf(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)}wf.prototype.appendChild=function(a,b){a.appendChild(b)};
-wf.prototype.contains=Mf;function Of(a,b,c,d){this.top=a;this.right=b;this.bottom=c;this.left=d}k=Of.prototype;k.clone=function(){return new Of(this.top,this.right,this.bottom,this.left)};k.contains=function(a){return this&&a?a instanceof Of?a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom:a.x>=this.left&&a.x<=this.right&&a.y>=this.top&&a.y<=this.bottom:!1};
-k.ceil=function(){this.top=Math.ceil(this.top);this.right=Math.ceil(this.right);this.bottom=Math.ceil(this.bottom);this.left=Math.ceil(this.left);return this};k.floor=function(){this.top=Math.floor(this.top);this.right=Math.floor(this.right);this.bottom=Math.floor(this.bottom);this.left=Math.floor(this.left);return this};k.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this};
-k.scale=function(a,b){var c=ka(b)?b:a;this.left*=a;this.right*=a;this.top*=c;this.bottom*=c;return this};function Pf(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}k=Pf.prototype;k.clone=function(){return new Pf(this.left,this.top,this.width,this.height)};k.contains=function(a){return a instanceof Pf?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height};
-function Qf(a,b){var c=b.x<a.left?a.left-b.x:Math.max(b.x-(a.left+a.width),0),d=b.y<a.top?a.top-b.y:Math.max(b.y-(a.top+a.height),0);return c*c+d*d}k.distance=function(a){return Math.sqrt(Qf(this,a))};k.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};
-k.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};k.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};k.scale=function(a,b){var c=ka(b)?b:a;this.left*=a;this.width*=a;this.top*=c;this.height*=c;return this};function Rf(a,b){var c=xf(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))?c[b]||c.getPropertyValue(b)||"":""}function Sf(a,b){return Rf(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style&&a.style[b]}function Tf(a,b,c){var d,e=vb&&(ob||zb)&&Eb("1.9");b instanceof sf?(d=b.x,b=b.y):(d=b,b=c);a.style.left=Uf(d,e);a.style.top=Uf(b,e)}
-function Vf(a){var b;try{b=a.getBoundingClientRect()}catch(c){return{left:0,top:0,right:0,bottom:0}}ub&&a.ownerDocument.body&&(a=a.ownerDocument,b.left-=a.documentElement.clientLeft+a.body.clientLeft,b.top-=a.documentElement.clientTop+a.body.clientTop);return b}
-function Wf(a){if(ub&&!(ub&&8<=Gb))return a.offsetParent;var b=xf(a),c=Sf(a,"position"),d="fixed"==c||"absolute"==c;for(a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=Sf(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null}
-function Xf(a){var b,c=xf(a),d=Sf(a,"position"),e=vb&&c.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==d&&(b=c.getBoxObjectFor(a))&&(0>b.screenX||0>b.screenY),f=new sf(0,0),g;b=c?xf(c):document;(g=!ub||ub&&9<=Gb)||(vf(b),g=!0);g=g?b.documentElement:b.body;if(a==g)return f;if(a.getBoundingClientRect)b=Vf(a),a=Nf(vf(c)),f.x=b.left+a.x,f.y=b.top+a.y;else if(c.getBoxObjectFor&&!e)b=c.getBoxObjectFor(a),a=c.getBoxObjectFor(g),f.x=b.screenX-a.screenX,f.y=b.screenY-a.screenY;else{b=a;do{f.x+=b.offsetLeft;
-f.y+=b.offsetTop;b!=a&&(f.x+=b.clientLeft||0,f.y+=b.clientTop||0);if(wb&&"fixed"==Sf(b,"position")){f.x+=c.body.scrollLeft;f.y+=c.body.scrollTop;break}b=b.offsetParent}while(b&&b!=a);if(sb||wb&&"absolute"==d)f.y-=c.body.offsetTop;for(b=a;(b=Wf(b))&&b!=c.body&&b!=g;)f.x-=b.scrollLeft,sb&&"TR"==b.tagName||(f.y-=b.scrollTop)}return f}function Yf(a,b){var c=Zf(a),d=Zf(b);return new sf(c.x-d.x,c.y-d.y)}
-function Zf(a){if(1==a.nodeType){var b;if(a.getBoundingClientRect)b=Vf(a),b=new sf(b.left,b.top);else{b=Nf(vf(a));var c=Xf(a);b=new sf(c.x-b.x,c.y-b.y)}if(vb&&!Eb(12)){b:{c=Pa();if(void 0===a.style[c]&&(c=(wb?"Webkit":vb?"Moz":ub?"ms":sb?"O":null)+Qa(c),void 0!==a.style[c])){c=(wb?"-webkit":vb?"-moz":ub?"-ms":sb?"-o":null)+"-transform";break b}c="transform"}a=(a=Sf(a,c)||Sf(a,"transform"))?(a=a.match($f))?new sf(parseFloat(a[1]),parseFloat(a[2])):new sf(0,0):new sf(0,0);a=new sf(b.x+a.x,b.y+a.y)}else a=
-b;return a}b=la(a.o);c=a;a.targetTouches?c=a.targetTouches[0]:b&&a.a.targetTouches&&(c=a.a.targetTouches[0]);return new sf(c.clientX,c.clientY)}function Uf(a,b){"number"==typeof a&&(a=(b?Math.round(a):a)+"px");return a}function ag(a){var b=bg;if("none"!=Sf(a,"display"))return b(a);var c=a.style,d=c.display,e=c.visibility,f=c.position;c.visibility="hidden";c.position="absolute";c.display="inline";a=b(a);c.display=d;c.position=f;c.visibility=e;return a}
-function bg(a){var b=a.offsetWidth,c=a.offsetHeight,d=wb&&!b&&!c;return l(b)&&!d||!a.getBoundingClientRect?new tf(b,c):(a=Vf(a),new tf(a.right-a.left,a.bottom-a.top))}function cg(a,b){var c=a.style;"opacity"in c?c.opacity=b:"MozOpacity"in c?c.MozOpacity=b:"filter"in c&&(c.filter=""===b?"":"alpha(opacity="+100*b+")")}function dg(a,b){a.style.display=b?"":"none"}
-function eg(a){var b=xf(a),c=ub&&a.currentStyle,d;if(d=c)vf(b),d=!0;if(d&&"auto"!=c.width&&"auto"!=c.height&&!c.boxSizing)return b=fg(a,c.width,"width","pixelWidth"),a=fg(a,c.height,"height","pixelHeight"),new tf(b,a);c=new tf(a.offsetWidth,a.offsetHeight);b=gg(a,"padding");a=hg(a);return new tf(c.width-a.left-b.left-b.right-a.right,c.height-a.top-b.top-b.bottom-a.bottom)}
-function fg(a,b,c,d){if(/^\d+px?$/.test(b))return parseInt(b,10);var e=a.style[c],f=a.runtimeStyle[c];a.runtimeStyle[c]=a.currentStyle[c];a.style[c]=b;b=a.style[d];a.style[c]=e;a.runtimeStyle[c]=f;return b}function ig(a,b){var c=a.currentStyle?a.currentStyle[b]:null;return c?fg(a,c,"left","pixelLeft"):0}
-function gg(a,b){if(ub){var c=ig(a,b+"Left"),d=ig(a,b+"Right"),e=ig(a,b+"Top"),f=ig(a,b+"Bottom");return new Of(e,d,f,c)}c=Rf(a,b+"Left");d=Rf(a,b+"Right");e=Rf(a,b+"Top");f=Rf(a,b+"Bottom");return new Of(parseFloat(e),parseFloat(d),parseFloat(f),parseFloat(c))}var jg={thin:2,medium:4,thick:6};function kg(a,b){if("none"==(a.currentStyle?a.currentStyle[b+"Style"]:null))return 0;var c=a.currentStyle?a.currentStyle[b+"Width"]:null;return c in jg?jg[c]:fg(a,c,"left","pixelLeft")}
-function hg(a){if(ub&&!(ub&&9<=Gb)){var b=kg(a,"borderLeft"),c=kg(a,"borderRight"),d=kg(a,"borderTop");a=kg(a,"borderBottom");return new Of(d,c,a,b)}b=Rf(a,"borderLeftWidth");c=Rf(a,"borderRightWidth");d=Rf(a,"borderTopWidth");a=Rf(a,"borderBottomWidth");return new Of(parseFloat(d),parseFloat(c),parseFloat(a),parseFloat(b))}var $f=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;function lg(a,b){var c=Ff("CANVAS");l(a)&&(c.width=a);l(b)&&(c.height=b);return c.getContext("2d")}
-var mg=function(){var a;return function(){if(!l(a))if(ba.getComputedStyle){var b=Ff("P"),c,d={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(b);for(var e in d)e in b.style&&(b.style[e]="translate(1px,1px)",c=ba.getComputedStyle(b).getPropertyValue(d[e]));Kf(b);a=c&&"none"!==c}else a=!1;return a}}(),ng=function(){var a;return function(){if(!l(a))if(ba.getComputedStyle){var b=Ff("P"),
-c,d={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(b);for(var e in d)e in b.style&&(b.style[e]="translate3d(1px,1px,1px)",c=ba.getComputedStyle(b).getPropertyValue(d[e]));Kf(b);a=c&&"none"!==c}else a=!1;return a}}();function og(a,b){var c=a.style;c.WebkitTransform=b;c.MozTransform=b;c.a=b;c.msTransform=b;c.transform=b;ub&&!Ib&&(a.style.transformOrigin="0 0")}
-function pg(a,b){var c;if(ng()){if(l(6)){var d=Array(16);for(c=0;16>c;++c)d[c]=b[c].toFixed(6);c=d.join(",")}else c=b.join(",");og(a,"matrix3d("+c+")")}else if(mg()){d=[b[0],b[1],b[4],b[5],b[12],b[13]];if(l(6)){var e=Array(6);for(c=0;6>c;++c)e[c]=d[c].toFixed(6);c=e.join(",")}else c=d.join(",");og(a,"matrix("+c+")")}else a.style.left=Math.round(b[12])+"px",a.style.top=Math.round(b[13])+"px"};var qg=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function rg(a,b){var c,d,e=qg.length;for(d=0;d<e;++d)try{if(c=a.getContext(qg[d],b),null!==c)return c}catch(f){}return null};var sg=ba.devicePixelRatio||1,tg="ArrayBuffer"in ba,ug=!1,vg=function(){if(!("HTMLCanvasElement"in ba))return!1;try{var a=lg();if(null===a)return!1;l(a.setLineDash)&&(ug=!0);return!0}catch(b){return!1}}(),wg="DeviceOrientationEvent"in ba,xg="geolocation"in ba.navigator,yg="ontouchstart"in ba,zg="PointerEvent"in ba,Ag=!!ba.navigator.msPointerEnabled,Bg=function(){if(!("WebGLRenderingContext"in ba))return!1;try{var a=Ff("CANVAS");return!ga(rg(a,{kf:!0}))}catch(b){return!1}}();function Cg(a,b,c){fc.call(this,a,c);this.element=b}t(Cg,fc);function A(a){pd.call(this);this.a=a||[];Dg(this)}t(A,pd);k=A.prototype;k.clear=function(){for(;0<this.xb();)this.pop()};k.je=function(a){var b,c;b=0;for(c=a.length;b<c;++b)this.push(a[b]);return this};k.forEach=function(a,b){Ta(this.a,a,b)};k.Kg=function(){return this.a};k.item=function(a){return this.a[a]};k.xb=function(){return this.get("length")};k.Xc=function(a,b){eb(this.a,a,0,b);Dg(this);this.dispatchEvent(new Cg("add",b,this))};
-k.pop=function(){return this.gd(this.xb()-1)};k.push=function(a){var b=this.a.length;this.Xc(b,a);return b};k.remove=function(a){var b=this.a,c,d;c=0;for(d=b.length;c<d;++c)if(b[c]===a)return this.gd(c)};k.gd=function(a){var b=this.a[a];Ra.splice.call(this.a,a,1);Dg(this);this.dispatchEvent(new Cg("remove",b,this));return b};
-k.vi=function(a,b){var c=this.xb();if(a<c)c=this.a[a],this.a[a]=b,this.dispatchEvent(new Cg("remove",c,this)),this.dispatchEvent(new Cg("add",b,this));else{for(;c<a;++c)this.Xc(c,void 0);this.Xc(a,b)}};function Dg(a){a.set("length",a.a.length)};var Eg=/^#(?:[0-9a-f]{3}){1,2}$/i,Fg=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i,Gg=/^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;function Hg(a){return ha(a)?a:Ig(a)}function Jg(a){if(!ja(a)){var b=a[0];b!=(b|0)&&(b=b+.5|0);var c=a[1];c!=(c|0)&&(c=c+.5|0);var d=a[2];d!=(d|0)&&(d=d+.5|0);a="rgba("+b+","+c+","+d+","+a[3]+")"}return a}
-var Ig=function(){var a={},b=0;return function(c,d){var e;if(a.hasOwnProperty(c))e=a[c];else{if(1024<=b){e=0;for(var f in a)0===(e++&3)&&(delete a[f],--b)}var g,h;Eg.exec(c)?(h=3==c.length-1?1:2,e=parseInt(c.substr(1+0*h,h),16),f=parseInt(c.substr(1+1*h,h),16),g=parseInt(c.substr(1+2*h,h),16),1==h&&(e=(e<<4)+e,f=(f<<4)+f,g=(g<<4)+g),e=[e,f,g,1]):(h=Gg.exec(c))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),h=Number(h[4]),e=[e,f,g,h],e=Kg(e,e)):(h=Fg.exec(c))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),
-e=[e,f,g,1],e=Kg(e,e)):e=void 0;a[c]=e;++b}l(d)&&(d[0]=e[0],d[1]=e[1],d[2]=e[2],d[3]=e[3],e=d);return e}}();function Kg(a,b){var c=l(b)?b:[];c[0]=Jb(a[0]+.5|0,0,255);c[1]=Jb(a[1]+.5|0,0,255);c[2]=Jb(a[2]+.5|0,0,255);c[3]=Jb(a[3],0,1);return c};function Lg(){this.g=Id();this.c=void 0;this.a=Id();this.d=void 0;this.b=Id();this.f=void 0;this.e=Id();this.l=void 0;this.i=Id()}
-function Mg(a,b,c,d,e){var f=!1;l(b)&&b!==a.c&&(f=a.a,Md(f),f[12]=b,f[13]=b,f[14]=b,f[15]=1,a.c=b,f=!0);if(l(c)&&c!==a.d){f=a.b;Md(f);f[0]=c;f[5]=c;f[10]=c;f[15]=1;var g=-.5*c+.5;f[12]=g;f[13]=g;f[14]=g;f[15]=1;a.d=c;f=!0}l(d)&&d!==a.f&&(f=Math.cos(d),g=Math.sin(d),Jd(a.e,.213+.787*f-.213*g,.213-.213*f+.143*g,.213-.213*f-.787*g,0,.715-.715*f-.715*g,.715+.285*f+.14*g,.715-.715*f+.715*g,0,.072-.072*f+.928*g,.072-.072*f-.283*g,.072+.928*f+.072*g,0,0,0,0,1),a.f=d,f=!0);l(e)&&e!==a.l&&(Jd(a.i,.213+.787*
-e,.213-.213*e,.213-.213*e,0,.715-.715*e,.715+.285*e,.715-.715*e,0,.072-.072*e,.072-.072*e,.072+.928*e,0,0,0,0,1),a.l=e,f=!0);f&&(f=a.g,Md(f),l(c)&&Nd(f,a.b,f),l(b)&&Nd(f,a.a,f),l(e)&&Nd(f,a.i,f),l(d)&&Nd(f,a.e,f));return a.g};function Ng(a){if(a.classList)return a.classList;a=a.className;return ja(a)&&a.match(/\S+/g)||[]}function Og(a,b){return a.classList?a.classList.contains(b):Za(Ng(a),b)}function Pg(a,b){a.classList?a.classList.add(b):Og(a,b)||(a.className+=0<a.className.length?" "+b:b)}function Qg(a,b){a.classList?a.classList.remove(b):Og(a,b)&&(a.className=Ua(Ng(a),function(a){return a!=b}).join(" "))};function Rg(a,b,c){fc.call(this,a);this.map=b;this.frameState=l(c)?c:null}t(Rg,fc);function Sg(a){pd.call(this);this.element=l(a.element)?a.element:null;this.D=l(a.target)?yf(a.target):null;this.a=null;this.i=[]}t(Sg,pd);Sg.prototype.I=function(){Kf(this.element);Sg.K.I.call(this)};Sg.prototype.d=function(){return this.a};Sg.prototype.yb=ca;
-Sg.prototype.setMap=function(a){null===this.a||Kf(this.element);0!=this.i.length&&(Ta(this.i,Yc),this.i.length=0);this.a=a;null!==this.a&&((null===this.D?a.r:this.D).appendChild(this.element),this.yb!==ca&&this.i.push(y(a,"postrender",this.yb,!1,this)),a.O())};function Tg(a,b){this.a=a;this.e=b};function Ug(a){Tg.call(this,a,{mousedown:this.wg,mousemove:this.xg,mouseup:this.Ag,mouseover:this.zg,mouseout:this.yg});this.c=a.b;this.b=[]}t(Ug,Tg);function Vg(a,b){for(var c=a.b,d=b.clientX,e=b.clientY,f=0,g=c.length,h;f<g&&(h=c[f]);f++){var m=Math.abs(e-h[1]);if(25>=Math.abs(d-h[0])&&25>=m)return!0}return!1}function Wg(a){var b=Xg(a,a.a),c=b.preventDefault;b.preventDefault=function(){a.preventDefault();c()};b.pointerId=1;b.isPrimary=!0;b.pointerType="mouse";return b}k=Ug.prototype;
-k.wg=function(a){if(!Vg(this,a)){(1).toString()in this.c&&this.cancel(a);var b=Wg(a);this.c[(1).toString()]=a;Yg(this.a,Zg,b,a)}};k.xg=function(a){if(!Vg(this,a)){var b=Wg(a);Yg(this.a,$g,b,a)}};k.Ag=function(a){if(!Vg(this,a)){var b=v(this.c,(1).toString());b&&b.button===a.button&&(b=Wg(a),Yg(this.a,ah,b,a),Dc(this.c,(1).toString()))}};k.zg=function(a){if(!Vg(this,a)){var b=Wg(a);bh(this.a,b,a)}};k.yg=function(a){if(!Vg(this,a)){var b=Wg(a);ch(this.a,b,a)}};
-k.cancel=function(a){var b=Wg(a);this.a.cancel(b,a);Dc(this.c,(1).toString())};function dh(a){Tg.call(this,a,{MSPointerDown:this.Fg,MSPointerMove:this.Gg,MSPointerUp:this.Jg,MSPointerOut:this.Hg,MSPointerOver:this.Ig,MSPointerCancel:this.Eg,MSGotPointerCapture:this.Cg,MSLostPointerCapture:this.Dg});this.c=a.b;this.b=["","unavailable","touch","pen","mouse"]}t(dh,Tg);function eh(a,b){var c=b;ka(b.a.pointerType)&&(c=Xg(b,b.a),c.pointerType=a.b[b.a.pointerType]);return c}k=dh.prototype;k.Fg=function(a){this.c[a.a.pointerId]=a;var b=eh(this,a);Yg(this.a,Zg,b,a)};
-k.Gg=function(a){var b=eh(this,a);Yg(this.a,$g,b,a)};k.Jg=function(a){var b=eh(this,a);Yg(this.a,ah,b,a);Dc(this.c,a.a.pointerId)};k.Hg=function(a){var b=eh(this,a);ch(this.a,b,a)};k.Ig=function(a){var b=eh(this,a);bh(this.a,b,a)};k.Eg=function(a){var b=eh(this,a);this.a.cancel(b,a);Dc(this.c,a.a.pointerId)};k.Dg=function(a){this.a.dispatchEvent(new fh("lostpointercapture",a,a.a))};k.Cg=function(a){this.a.dispatchEvent(new fh("gotpointercapture",a,a.a))};function hh(a){Tg.call(this,a,{pointerdown:this.bi,pointermove:this.ci,pointerup:this.fi,pointerout:this.di,pointerover:this.ei,pointercancel:this.ai,gotpointercapture:this.Sf,lostpointercapture:this.vg})}t(hh,Tg);k=hh.prototype;k.bi=function(a){ih(this.a,a)};k.ci=function(a){ih(this.a,a)};k.fi=function(a){ih(this.a,a)};k.di=function(a){ih(this.a,a)};k.ei=function(a){ih(this.a,a)};k.ai=function(a){ih(this.a,a)};k.vg=function(a){ih(this.a,a)};k.Sf=function(a){ih(this.a,a)};function fh(a,b,c){fc.call(this,a);this.a=b;a=l(c)?c:{};this.buttons=jh(a);this.pressure=kh(a,this.buttons);this.bubbles=v(a,"bubbles",!1);this.cancelable=v(a,"cancelable",!1);this.view=v(a,"view",null);this.detail=v(a,"detail",null);this.screenX=v(a,"screenX",0);this.screenY=v(a,"screenY",0);this.clientX=v(a,"clientX",0);this.clientY=v(a,"clientY",0);this.button=v(a,"button",0);this.relatedTarget=v(a,"relatedTarget",null);this.pointerId=v(a,"pointerId",0);this.width=v(a,"width",0);this.height=v(a,
-"height",0);this.pointerType=v(a,"pointerType","");this.isPrimary=v(a,"isPrimary",!1);b.preventDefault&&(this.preventDefault=function(){b.preventDefault()})}t(fh,fc);function jh(a){if(a.buttons||lh)a=a.buttons;else switch(a.which){case 1:a=1;break;case 2:a=4;break;case 3:a=2;break;default:a=0}return a}function kh(a,b){var c=0;a.pressure?c=a.pressure:c=b?.5:0;return c}var lh=!1;try{lh=1===(new MouseEvent("click",{buttons:1})).buttons}catch(mh){};function nh(a,b){Tg.call(this,a,{touchstart:this.Ei,touchmove:this.Di,touchend:this.Ci,touchcancel:this.Bi});this.c=a.b;this.g=b;this.b=void 0;this.f=0;this.d=void 0}t(nh,Tg);k=nh.prototype;k.Ee=function(){this.f=0;this.d=void 0};
-function oh(a,b,c){b=Xg(b,c);b.pointerId=c.identifier+2;b.bubbles=!0;b.cancelable=!0;b.detail=a.f;b.button=0;b.buttons=1;b.width=c.webkitRadiusX||c.radiusX||0;b.height=c.webkitRadiusY||c.radiusY||0;b.pressure=c.webkitForce||c.force||.5;b.isPrimary=a.b===c.identifier;b.pointerType="touch";b.clientX=c.clientX;b.clientY=c.clientY;b.screenX=c.screenX;b.screenY=c.screenY;return b}
-function ph(a,b,c){function d(){b.preventDefault()}var e=Array.prototype.slice.call(b.a.changedTouches),f=e.length,g,h;for(g=0;g<f;++g)h=oh(a,b,e[g]),h.preventDefault=d,c.call(a,b,h)}
-k.Ei=function(a){var b=a.a.touches,c=xc(this.c),d=c.length;if(d>=b.length){var e=[],f,g,h;for(f=0;f<d;++f){g=c[f];h=this.c[g];var m;if(!(m=1==g))a:{m=b.length;for(var n=void 0,p=0;p<m;p++)if(n=b[p],n.identifier===g-2){m=!0;break a}m=!1}m||e.push(h.Bb)}for(f=0;f<e.length;++f)this.ud(a,e[f])}b=uc(this.c);if(0===b||1===b&&(1).toString()in this.c)this.b=a.a.changedTouches[0].identifier,l(this.d)&&ba.clearTimeout(this.d);qh(this,a);this.f++;ph(this,a,this.Xh)};
-k.Xh=function(a,b){this.c[b.pointerId]={target:b.target,Bb:b,Ae:b.target};var c=this.a;b.bubbles=!0;Yg(c,rh,b,a);c=this.a;b.bubbles=!1;Yg(c,sh,b,a);Yg(this.a,Zg,b,a)};k.Di=function(a){a.preventDefault();ph(this,a,this.Bg)};k.Bg=function(a,b){var c=v(this.c,b.pointerId);if(c){var d=c.Bb,e=c.Ae;Yg(this.a,$g,b,a);d&&e!==b.target&&(d.relatedTarget=b.target,b.relatedTarget=e,d.target=e,b.target?(ch(this.a,d,a),bh(this.a,b,a)):(b.target=e,b.relatedTarget=null,this.ud(a,b)));c.Bb=b;c.Ae=b.target}};
-k.Ci=function(a){qh(this,a);ph(this,a,this.Fi)};k.Fi=function(a,b){Yg(this.a,ah,b,a);this.a.Bb(b,a);var c=this.a;b.bubbles=!1;Yg(c,th,b,a);Dc(this.c,b.pointerId);b.isPrimary&&(this.b=void 0,this.d=ba.setTimeout(sa(this.Ee,this),200))};k.Bi=function(a){ph(this,a,this.ud)};k.ud=function(a,b){this.a.cancel(b,a);this.a.Bb(b,a);var c=this.a;b.bubbles=!1;Yg(c,th,b,a);Dc(this.c,b.pointerId);b.isPrimary&&(this.b=void 0,this.d=ba.setTimeout(sa(this.Ee,this),200))};
-function qh(a,b){var c=a.g.b,d=b.a.changedTouches[0];if(a.b===d.identifier){var e=[d.clientX,d.clientY];c.push(e);ba.setTimeout(function(){ab(c,e)},2500)}};function uh(a){hd.call(this);this.e=a;this.b={};this.d={};this.c=[];zg?vh(this,new hh(this)):Ag?vh(this,new dh(this)):(a=new Ug(this),vh(this,a),yg&&vh(this,new nh(this,a)));a=this.c.length;for(var b,c=0;c<a;c++)b=this.c[c],wh(this,xc(b.e))}t(uh,hd);function vh(a,b){var c=xc(b.e);c&&(Ta(c,function(a){var c=b.e[a];c&&(this.d[a]=sa(c,b))},a),a.c.push(b))}uh.prototype.a=function(a){var b=this.d[a.type];b&&b(a)};function wh(a,b){Ta(b,function(a){y(this.e,a,this.a,!1,this)},a)}
-function xh(a,b){Ta(b,function(a){Xc(this.e,a,this.a,!1,this)},a)}function Xg(a,b){for(var c={},d,e=0,f=yh.length;e<f;e++)d=yh[e][0],c[d]=a[d]||b[d]||yh[e][1];return c}uh.prototype.Bb=function(a,b){a.bubbles=!0;Yg(this,zh,a,b)};uh.prototype.cancel=function(a,b){Yg(this,Ah,a,b)};function ch(a,b,c){a.Bb(b,c);b.target.contains(b.relatedTarget)||(b.bubbles=!1,Yg(a,th,b,c))}function bh(a,b,c){b.bubbles=!0;Yg(a,rh,b,c);b.target.contains(b.relatedTarget)||(b.bubbles=!1,Yg(a,sh,b,c))}
-function Yg(a,b,c,d){a.dispatchEvent(new fh(b,d,c))}function ih(a,b){a.dispatchEvent(new fh(b.type,b,b.a))}uh.prototype.I=function(){for(var a=this.c.length,b,c=0;c<a;c++)b=this.c[c],xh(this,xc(b.e));uh.K.I.call(this)};
-var $g="pointermove",Zg="pointerdown",ah="pointerup",rh="pointerover",zh="pointerout",sh="pointerenter",th="pointerleave",Ah="pointercancel",yh=[["bubbles",!1],["cancelable",!1],["view",null],["detail",null],["screenX",0],["screenY",0],["clientX",0],["clientY",0],["ctrlKey",!1],["altKey",!1],["shiftKey",!1],["metaKey",!1],["button",0],["relatedTarget",null],["buttons",0],["pointerId",0],["width",0],["height",0],["pressure",0],["tiltX",0],["tiltY",0],["pointerType",""],["hwTimestamp",0],["isPrimary",
-!1],["type",""],["target",null],["currentTarget",null],["which",0]];function Bh(a){a=l(a)?a:{};this.o=Ff("UL");this.l=Ff("LI");this.o.appendChild(this.l);dg(this.l,!1);this.b=l(a.collapsed)?a.collapsed:!0;this.f=l(a.collapsible)?a.collapsible:!0;this.f||(this.b=!1);var b=l(a.className)?a.className:"ol-attribution",c=Cf("SPAN",{role:"tooltip"},l(a.tipLabel)?a.tipLabel:"Attributions");this.q=l(a.collapseLabel)?a.collapseLabel:"\u00bb";this.u=l(a.label)?a.label:"i";this.r=Cf("SPAN",{},this.f&&!this.b?this.q:this.u);var d=Cf("BUTTON",{"class":"ol-has-tooltip",type:"button"},
-this.r);d.appendChild(c);c=new uh(d);dc(this,c);y(c,ah,this.Yg,!1,this);y(d,"click",this.Xg,!1,this);y(d,["mouseout",hc],function(){this.blur()},!1);b=Cf("DIV",{"class":b+" ol-unselectable ol-control"+(this.b&&this.f?" ol-collapsed":"")+(this.f?"":" ol-uncollapsible")},this.o,d);Sg.call(this,{element:b,target:a.target});this.k=!0;this.g={};this.e={};this.F={}}t(Bh,Sg);k=Bh.prototype;
-k.yb=function(a){a=a.frameState;if(null===a)this.k&&(dg(this.element,!1),this.k=!1);else{var b,c,d,e,f,g,h,m,n,p=a.layerStatesArray,r=Fc(a.attributions),q={};c=0;for(b=p.length;c<b;c++)if(d=p[c].layer.a,n=na(d).toString(),m=d.f,null!==m)for(d=0,e=m.length;d<e;d++)if(g=m[d],h=na(g).toString(),!(h in r)){f=a.usedTiles[n];var u;if(u=l(f))a:if(null===g.a)u=!0;else{var x=u=void 0,B=void 0,E=void 0;for(E in f)if(E in g.a)for(B=f[E],u=0,x=g.a[E].length;u<x;++u){var F=g.a[E][u];if(F.a<=B.d&&F.d>=B.a&&F.b<=
-B.c&&F.c>=B.b){u=!0;break a}}u=!1}u?(h in q&&delete q[h],r[h]=g):q[h]=g}b=[r,q];c=b[0];b=b[1];for(var w in this.g)w in c?(this.e[w]||(dg(this.g[w],!0),this.e[w]=!0),delete c[w]):w in b?(this.e[w]&&(dg(this.g[w],!1),delete this.e[w]),delete b[w]):(Kf(this.g[w]),delete this.g[w],delete this.e[w]);for(w in c)n=Ff("LI"),n.innerHTML=c[w].c,this.o.appendChild(n),this.g[w]=n,this.e[w]=!0;for(w in b)n=Ff("LI"),n.innerHTML=b[w].c,dg(n,!1),this.o.appendChild(n),this.g[w]=n;w=!Bc(this.e)||!Bc(a.logos);this.k!=
-w&&(dg(this.element,w),this.k=w);w&&Bc(this.e)?Pg(this.element,"ol-logo-only"):Qg(this.element,"ol-logo-only");var U;a=a.logos;w=this.F;for(U in w)U in a||(Kf(w[U]),delete w[U]);for(var Q in a)Q in w||(U=new Image,U.src=Q,c=a[Q],""===c?c=U:(c=Cf("A",{href:c,target:"_blank"}),c.appendChild(U)),this.l.appendChild(c),w[Q]=c);dg(this.l,!Bc(a))}};k.Xg=function(a){0!==a.screenX&&0!==a.screenY||Ch(this)};k.Yg=function(a){a.a.preventDefault();Ch(this)};
-function Ch(a){var b=a.element;Og(b,"ol-collapsed")?Qg(b,"ol-collapsed"):Pg(b,"ol-collapsed");var b=a.r,c=a.b?a.q:a.u;if("textContent"in b)b.textContent=c;else if(3==b.nodeType)b.data=c;else if(b.firstChild&&3==b.firstChild.nodeType){for(;b.lastChild!=b.firstChild;)b.removeChild(b.lastChild);b.firstChild.data=c}else Hf(b),b.appendChild(xf(b).createTextNode(String(c)));a.b=!a.b}k.of=function(){return this.f};
-k.xi=function(a){if(this.f!==a){this.f=a;var b=this.element;Og(b,"ol-uncollapsible")?Qg(b,"ol-uncollapsible"):Pg(b,"ol-uncollapsible");!a&&this.b&&Ch(this)}};k.wi=function(a){this.f&&this.b!==a&&Ch(this)};k.nf=function(){return this.b};function Dh(a){a=l(a)?a:{};var b=l(a.className)?a.className:"ol-rotate";this.b=Cf("SPAN",{"class":"ol-compass"},l(a.label)?a.label:"\u21e7");var c=Cf("SPAN",{role:"tooltip"},l(a.tipLabel)?a.tipLabel:"Reset rotation"),c=Cf("BUTTON",{"class":b+"-reset ol-has-tooltip",name:"ResetRotation",type:"button"},c,this.b),d=new uh(c);dc(this,d);y(d,ah,Dh.prototype.k,!1,this);y(c,"click",Dh.prototype.l,!1,this);y(c,["mouseout",hc],function(){this.blur()},!1);b=Cf("DIV",b+" ol-unselectable ol-control",c);Sg.call(this,
-{element:b,target:a.target});this.f=l(a.duration)?a.duration:250;this.e=l(a.autoHide)?a.autoHide:!0;this.g=void 0;b.style.opacity=this.e?0:1}t(Dh,Sg);Dh.prototype.l=function(a){0!==a.screenX&&0!==a.screenY||Eh(this)};Dh.prototype.k=function(a){a.a.preventDefault();Eh(this)};function Eh(a){for(var b=a.a,c=b.a(),d=c.e();d<-Math.PI;)d+=2*Math.PI;for(;d>Math.PI;)d-=2*Math.PI;l(d)&&(0<a.f&&b.Ea(ef({rotation:d,duration:a.f,easing:$e})),c.q(0))}
-Dh.prototype.yb=function(a){a=a.frameState;if(null!==a){a=a.viewState.rotation;if(a!=this.g){var b="rotate("+180*a/Math.PI+"deg)";this.e&&(this.element.style.opacity=0===a?0:1);this.b.style.msTransform=b;this.b.style.webkitTransform=b;this.b.style.transform=b}this.g=a}};function Fh(a){a=l(a)?a:{};var b=l(a.className)?a.className:"ol-zoom",c=l(a.delta)?a.delta:1,d=l(a.zoomInLabel)?a.zoomInLabel:"+",e=l(a.zoomOutLabel)?a.zoomOutLabel:"\u2212",f=l(a.zoomOutTipLabel)?a.zoomOutTipLabel:"Zoom out",g=Cf("SPAN",{role:"tooltip"},l(a.zoomInTipLabel)?a.zoomInTipLabel:"Zoom in"),d=Cf("BUTTON",{"class":b+"-in ol-has-tooltip",type:"button"},g,d),g=new uh(d);dc(this,g);y(g,ah,ta(Fh.prototype.f,c),!1,this);y(d,"click",ta(Fh.prototype.e,c),!1,this);y(d,["mouseout",hc],function(){this.blur()},
-!1);f=Cf("SPAN",{role:"tooltip"},f);e=Cf("BUTTON",{"class":b+"-out  ol-has-tooltip",type:"button"},f,e);f=new uh(e);dc(this,f);y(f,ah,ta(Fh.prototype.f,-c),!1,this);y(e,"click",ta(Fh.prototype.e,-c),!1,this);y(e,["mouseout",hc],function(){this.blur()},!1);b=Cf("DIV",b+" ol-unselectable ol-control",d,e);Sg.call(this,{element:b,target:a.target});this.b=l(a.duration)?a.duration:250}t(Fh,Sg);Fh.prototype.e=function(a,b){0!==b.screenX&&0!==b.screenY||Gh(this,a)};
-Fh.prototype.f=function(a,b){b.a.preventDefault();Gh(this,a)};function Gh(a,b){var c=a.a,d=c.a(),e=d.b();l(e)&&(0<a.b&&c.Ea(ff({resolution:e,duration:a.b,easing:$e})),c=d.constrainResolution(e,b),d.d(c))};function Hh(a){a=l(a)?a:{};var b=new A;(l(a.zoom)?a.zoom:1)&&b.push(new Fh(a.zoomOptions));(l(a.rotate)?a.rotate:1)&&b.push(new Dh(a.rotateOptions));(l(a.attribution)?a.attribution:1)&&b.push(new Bh(a.attributionOptions));return b};var Ih=wb?"webkitfullscreenchange":vb?"mozfullscreenchange":ub?"MSFullscreenChange":"fullscreenchange";function Jh(){var a=vf().a,b=a.body;return!!(b.webkitRequestFullscreen||b.mozRequestFullScreen&&a.mozFullScreenEnabled||b.msRequestFullscreen&&a.msFullscreenEnabled||b.requestFullscreen&&a.fullscreenEnabled)}
-function Kh(a){a.webkitRequestFullscreen?a.webkitRequestFullscreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.msRequestFullscreen?a.msRequestFullscreen():a.requestFullscreen&&a.requestFullscreen()}function Lh(){var a=vf().a;return!!(a.webkitIsFullScreen||a.mozFullScreen||a.msFullscreenElement||a.fullscreenElement)};function Mh(a){a=l(a)?a:{};this.b=l(a.className)?a.className:"ol-full-screen";var b=Cf("SPAN",{role:"tooltip"},l(a.tipLabel)?a.tipLabel:"Toggle full-screen"),c=Cf("BUTTON",{"class":this.b+"-"+Lh()+" ol-has-tooltip"});c.appendChild(b);b=new uh(c);dc(this,b);y(b,ah,this.l,!1,this);y(c,"click",this.g,!1,this);y(c,["mouseout",hc],function(){this.blur()},!1);y(ba.document,Ih,this.e,!1,this);b=this.b+" ol-unselectable ol-control"+(Jh()?"":"ol-unsupported");c=Cf("DIV",b,c);Sg.call(this,{element:c,target:a.target});
-this.f=l(a.keys)?a.keys:!1}t(Mh,Sg);Mh.prototype.g=function(a){0!==a.screenX&&0!==a.screenY||Nh(this)};Mh.prototype.l=function(a){a.a.preventDefault();Nh(this)};
-function Nh(a){if(Jh()){var b=a.a;null!==b&&(Lh()?(a=vf().a,a.webkitCancelFullScreen?a.webkitCancelFullScreen():a.mozCancelFullScreen?a.mozCancelFullScreen():a.msExitFullscreen?a.msExitFullscreen():a.exitFullscreen&&a.exitFullscreen()):(b=b.rc(),b=yf(b),a.f?b.mozRequestFullScreenWithKeys?b.mozRequestFullScreenWithKeys():b.webkitRequestFullscreen?b.webkitRequestFullscreen():Kh(b):Kh(b)))}}
-Mh.prototype.e=function(){var a=this.b+"-true",b=this.b+"-false",c=Lf(this.element),d=this.a;Lh()?Og(c,b)&&(Qg(c,b),Pg(c,a)):Og(c,a)&&(Qg(c,a),Pg(c,b));null===d||d.F()};function C(a){a=l(a)?a:{};var b=Cf("DIV",{"class":l(a.className)?a.className:"ol-mouse-position"});Sg.call(this,{element:b,target:a.target});y(this,td("projection"),this.F,!1,this);l(a.coordinateFormat)&&this.q(a.coordinateFormat);l(a.projection)&&this.o(Be(a.projection));this.Q=l(a.undefinedHTML)?a.undefinedHTML:"";this.l=b.innerHTML;this.f=this.e=this.b=null}t(C,Sg);
-C.prototype.yb=function(a){a=a.frameState;null===a?this.b=null:this.b!=a.viewState.projection&&(this.b=a.viewState.projection,this.e=null);Oh(this,this.f)};C.prototype.F=function(){this.e=null};C.prototype.g=function(){return this.get("coordinateFormat")};C.prototype.getCoordinateFormat=C.prototype.g;C.prototype.k=function(){return this.get("projection")};C.prototype.getProjection=C.prototype.k;C.prototype.r=function(a){a=Yf(a,this.a.b);this.f=[a.x,a.y];Oh(this,this.f)};
-C.prototype.u=function(){Oh(this,null);this.f=null};C.prototype.setMap=function(a){C.K.setMap.call(this,a);null!==a&&(a=a.b,this.i.push(y(a,"mousemove",this.r,!1,this),y(a,"mouseout",this.u,!1,this)))};C.prototype.q=function(a){this.set("coordinateFormat",a)};C.prototype.setCoordinateFormat=C.prototype.q;C.prototype.o=function(a){this.set("projection",a)};C.prototype.setProjection=C.prototype.o;
-function Oh(a,b){var c=a.Q;if(null!==b&&null!==a.b){if(null===a.e){var d=a.k();a.e=l(d)?Ae(a.b,d):Te}d=a.a.ta(b);null!==d&&(a.e(d,d),c=a.g(),c=l(c)?c(d):d.toString())}l(a.l)&&c==a.l||(a.element.innerHTML=c,a.l=c)};function Ph(a){a=l(a)?a:{};var b=l(a.className)?a.className:"ol-scale-line";this.g=Cf("DIV",{"class":b+"-inner"});this.f=Cf("DIV",{"class":b+" ol-unselectable"},this.g);this.q=null;this.l=l(a.minWidth)?a.minWidth:64;this.b=!1;this.u=void 0;this.r="";this.e=null;Sg.call(this,{element:this.f,target:a.target});y(this,td("units"),this.F,!1,this);this.o(a.units||"metric")}t(Ph,Sg);var Qh=[1,2,5];Ph.prototype.k=function(){return this.get("units")};Ph.prototype.getUnits=Ph.prototype.k;
-Ph.prototype.yb=function(a){a=a.frameState;null===a?this.q=null:this.q=a.viewState;Rh(this)};Ph.prototype.F=function(){Rh(this)};Ph.prototype.o=function(a){this.set("units",a)};Ph.prototype.setUnits=Ph.prototype.o;
-function Rh(a){var b=a.q;if(null===b)a.b&&(dg(a.f,!1),a.b=!1);else{var c=b.center,d=b.projection,b=d.yd(b.resolution,c),e=d.c,f=a.k();"degrees"!=e||"metric"!=f&&"imperial"!=f&&"us"!=f&&"nautical"!=f?"ft"!=e&&"m"!=e||"degrees"!=f?a.e=null:(null===a.e&&(a.e=Ae(d,Be("EPSG:4326"))),c=Math.cos(Mb(a.e(c)[1])),d=we.radius,"ft"==e&&(d/=.3048),b*=180/(Math.PI*c*d)):(a.e=null,c=Math.cos(Mb(c[1])),b*=Math.PI*c*we.radius/180);c=a.l*b;e="";"degrees"==f?c<1/60?(e="\u2033",b*=3600):1>c?(e="\u2032",b*=60):e="\u00b0":
-"imperial"==f?.9144>c?(e="in",b/=.0254):1609.344>c?(e="ft",b/=.3048):(e="mi",b/=1609.344):"nautical"==f?(b/=1852,e="nm"):"metric"==f?1>c?(e="mm",b*=1E3):1E3>c?e="m":(e="km",b/=1E3):"us"==f&&(.9144>c?(e="in",b*=39.37):1609.344>c?(e="ft",b/=.30480061):(e="mi",b/=1609.3472));for(var f=3*Math.floor(Math.log(a.l*b)/Math.log(10)),g,h;;){g=Qh[f%3]*Math.pow(10,Math.floor(f/3));h=Math.round(g/b);if(isNaN(h)){dg(a.f,!1);a.b=!1;return}if(h>=a.l)break;++f}g=g+e;a.r!=g&&(a.g.innerHTML=g,a.r=g);a.u!=h&&(a.g.style.width=
-h+"px",a.u=h);a.b||(dg(a.f,!0),a.b=!0)}};function Sh(a){ac.call(this);this.c=a;this.a={}}t(Sh,ac);var Th=[];Sh.prototype.ya=function(a,b,c,d){ha(b)||(b&&(Th[0]=b.toString()),b=Th);for(var e=0;e<b.length;e++){var f=y(a,b[e],c||this.handleEvent,d||!1,this.c||this);if(!f)break;this.a[f.key]=f}return this};
-Sh.prototype.Od=function(a,b,c,d,e){if(ha(b))for(var f=0;f<b.length;f++)this.Od(a,b[f],c,d,e);else c=c||this.handleEvent,e=e||this.c||this,c=Qc(c),d=!!d,b=oc(a)?Lc(a.Qa,String(b),c,d,e):a?(a=Sc(a))?Lc(a,b,c,d,e):null:null,b&&(Yc(b),delete this.a[b.key]);return this};function Uh(a){sc(a.a,Yc);a.a={}}Sh.prototype.I=function(){Sh.K.I.call(this);Uh(this)};Sh.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};function Vh(a,b,c){hd.call(this);this.target=a;this.handle=b||a;this.c=c||new Pf(NaN,NaN,NaN,NaN);this.b=xf(a);this.a=new Sh(this);dc(this,this.a);y(this.handle,["touchstart","mousedown"],this.Ke,!1,this)}t(Vh,hd);var Wh=ub||vb&&Eb("1.9.3");k=Vh.prototype;k.clientX=0;k.clientY=0;k.screenX=0;k.screenY=0;k.Le=0;k.Me=0;k.Sb=0;k.Tb=0;k.rb=!1;k.I=function(){Vh.K.I.call(this);Xc(this.handle,["touchstart","mousedown"],this.Ke,!1,this);Uh(this.a);Wh&&this.b.releaseCapture();this.handle=this.target=null};
-k.Ke=function(a){var b="mousedown"==a.type;if(this.rb||b&&!mc(a))this.dispatchEvent("earlycancel");else if(Xh(a),this.dispatchEvent(new Yh("start",this,a.clientX,a.clientY))){this.rb=!0;a.preventDefault();var b=this.b,c=b.documentElement,d=!Wh;this.a.ya(b,["touchmove","mousemove"],this.gg,d);this.a.ya(b,["touchend","mouseup"],this.Jc,d);Wh?(c.setCapture(!1),this.a.ya(c,"losecapture",this.Jc)):this.a.ya(b?b.parentWindow||b.defaultView:window,"blur",this.Jc);this.e&&this.a.ya(this.e,"scroll",this.Wh,
-d);this.clientX=this.Le=a.clientX;this.clientY=this.Me=a.clientY;this.screenX=a.screenX;this.screenY=a.screenY;this.Sb=this.target.offsetLeft;this.Tb=this.target.offsetTop;this.d=Nf(vf(this.b));ua()}};k.Jc=function(a){Uh(this.a);Wh&&this.b.releaseCapture();if(this.rb){Xh(a);this.rb=!1;var b=Zh(this,this.Sb),c=$h(this,this.Tb);this.dispatchEvent(new Yh("end",this,a.clientX,a.clientY,0,b,c))}else this.dispatchEvent("earlycancel")};
-function Xh(a){var b=a.type;"touchstart"==b||"touchmove"==b?kc(a,a.a.targetTouches[0],a.b):"touchend"!=b&&"touchcancel"!=b||kc(a,a.a.changedTouches[0],a.b)}
-k.gg=function(a){Xh(a);var b=1*(a.clientX-this.clientX),c=a.clientY-this.clientY;this.clientX=a.clientX;this.clientY=a.clientY;this.screenX=a.screenX;this.screenY=a.screenY;if(!this.rb){var d=this.Le-this.clientX,e=this.Me-this.clientY;if(0<d*d+e*e)if(this.dispatchEvent(new Yh("start",this,a.clientX,a.clientY)))this.rb=!0;else{this.qb||this.Jc(a);return}}c=ai(this,b,c);b=c.x;c=c.y;this.rb&&this.dispatchEvent(new Yh("beforedrag",this,a.clientX,a.clientY,0,b,c))&&(bi(this,a,b,c),a.preventDefault())};
-function ai(a,b,c){var d=Nf(vf(a.b));b+=d.x-a.d.x;c+=d.y-a.d.y;a.d=d;a.Sb+=b;a.Tb+=c;b=Zh(a,a.Sb);a=$h(a,a.Tb);return new sf(b,a)}k.Wh=function(a){var b=ai(this,0,0);a.clientX=this.clientX;a.clientY=this.clientY;bi(this,a,b.x,b.y)};function bi(a,b,c,d){a.target.style.left=c+"px";a.target.style.top=d+"px";a.dispatchEvent(new Yh("drag",a,b.clientX,b.clientY,0,c,d))}
-function Zh(a,b){var c=a.c,d=isNaN(c.left)?null:c.left,c=isNaN(c.width)?0:c.width;return Math.min(null!=d?d+c:Infinity,Math.max(null!=d?d:-Infinity,b))}function $h(a,b){var c=a.c,d=isNaN(c.top)?null:c.top,c=isNaN(c.height)?0:c.height;return Math.min(null!=d?d+c:Infinity,Math.max(null!=d?d:-Infinity,b))}function Yh(a,b,c,d,e,f,g){fc.call(this,a);this.clientX=c;this.clientY=d;this.left=l(f)?f:b.Sb;this.top=l(g)?g:b.Tb}t(Yh,fc);function ci(a){a=l(a)?a:{};this.b=void 0;this.f=di;this.g=!1;var b=l(a.className)?a.className:"ol-zoomslider";a=Cf("DIV",[b+"-thumb","ol-unselectable"]);b=Cf("DIV",[b,"ol-unselectable"],a);this.e=new Vh(a);dc(this,this.e);y(this.e,["drag","end"],this.k,void 0,this);y(b,"click",this.l,!1,this);y(a,"click",gc);Sg.call(this,{element:b})}t(ci,Sg);var di=0;ci.prototype.setMap=function(a){ci.K.setMap.call(this,a);null===a||a.O()};
-ci.prototype.yb=function(a){if(null!==a.frameState){if(!this.g){var b=this.element,c=Lf(b),b=eg(b),d;d=Xf(c);var e=ag(c);d=new Pf(d.x,d.y,e.width,e.height);var e=gg(c,"margin"),f=hg(c),c=b.width-e.left-e.right-f.left-f.right-d.width;d=b.height-e.top-e.bottom-f.top-f.bottom-d.height;b.width>b.height?(this.f=1,b=new Pf(0,0,c,0)):(this.f=di,b=new Pf(0,0,0,d));this.e.c=b||new Pf(NaN,NaN,NaN,NaN);this.g=!0}a=a.frameState.viewState.resolution;a!==this.b&&(this.b=a,a=-1*(We(this.a.a())(a)-1),b=this.e,c=
-Lf(this.element),1==this.f?Tf(c,b.c.left+b.c.width*a):Tf(c,b.c.left,b.c.top+b.c.height*a))}};ci.prototype.l=function(a){var b=this.a,c=b.a();a=ei(this,fi(this,a.offsetX,a.offsetY));b.Ea(ff({resolution:a,duration:200,easing:$e}));a=c.constrainResolution(a);c.d(a)};function fi(a,b,c){var d=a.e.c,e=0;return e=1===a.f?(b-d.left)/d.width:(c-d.top)/d.height}function ei(a,b){b=-1*(Jb(b,0,1)-1);return Ve(a.a.a())(b)}
-ci.prototype.k=function(a){var b=this.a,c=b.a();"drag"===a.type?(a=ei(this,fi(this,a.left,a.top)),a!==this.b&&(this.b=a,c.d(a))):(b.Ea(ff({resolution:this.b,duration:200,easing:$e})),a=c.constrainResolution(this.b),c.d(a))};function gi(a){a=l(a)?a:{};this.b=l(a.extent)?a.extent:null;var b=l(a.className)?a.className:"ol-zoom-extent",c=Cf("SPAN",{role:"tooltip"},l(a.tipLabel)?a.tipLabel:"Fit to extent"),d=Cf("BUTTON",{"class":"ol-has-tooltip"});d.appendChild(c);c=new uh(d);dc(this,c);y(c,ah,this.f,!1,this);y(d,"click",this.e,!1,this);y(d,["mouseout",hc],function(){this.blur()},!1);b=Cf("DIV",b+" ol-unselectable ol-control",d);Sg.call(this,{element:b,target:a.target})}t(gi,Sg);
-gi.prototype.e=function(a){0!==a.screenX&&0!==a.screenY||hi(this)};gi.prototype.f=function(a){a.a.preventDefault();hi(this)};function hi(a){var b=a.a,c=b.a();a=null===a.b?c.o.s():a.b;b=b.e();c.Yd(a,b)};function D(a){pd.call(this);a=l(a)?a:{};this.a=null;y(this,td("tracking"),this.l,!1,this);this.b(l(a.tracking)?a.tracking:!1)}t(D,pd);D.prototype.I=function(){this.b(!1);D.K.I.call(this)};
-D.prototype.k=function(a){a=a.a;if(null!=a.alpha){var b=Mb(a.alpha);this.set("alpha",b);"boolean"==typeof a.absolute&&a.absolute?this.set("heading",b):null!=a.webkitCompassHeading&&null!=a.webkitCompassAccuracy&&-1!=a.webkitCompassAccuracy&&this.set("heading",Mb(a.webkitCompassHeading))}null!=a.beta&&this.set("beta",Mb(a.beta));null!=a.gamma&&this.set("gamma",Mb(a.gamma));this.n()};D.prototype.e=function(){return this.get("alpha")};D.prototype.getAlpha=D.prototype.e;D.prototype.f=function(){return this.get("beta")};
-D.prototype.getBeta=D.prototype.f;D.prototype.g=function(){return this.get("gamma")};D.prototype.getGamma=D.prototype.g;D.prototype.i=function(){return this.get("heading")};D.prototype.getHeading=D.prototype.i;D.prototype.d=function(){return this.get("tracking")};D.prototype.getTracking=D.prototype.d;D.prototype.l=function(){if(wg){var a=this.d();a&&null===this.a?this.a=y(ba,"deviceorientation",this.k,!1,this):a||null===this.a||(Yc(this.a),this.a=null)}};
-D.prototype.b=function(a){this.set("tracking",a)};D.prototype.setTracking=D.prototype.b;function ii(a){pd.call(this);this.a=a;y(this.a,["change","input"],this.i,!1,this);y(this,td("value"),this.l,!1,this);y(this,td("checked"),this.g,!1,this)}t(ii,pd);ii.prototype.b=function(){return this.get("checked")};ii.prototype.getChecked=ii.prototype.b;ii.prototype.d=function(){return this.get("value")};ii.prototype.getValue=ii.prototype.d;ii.prototype.f=function(a){this.set("value",a)};ii.prototype.setValue=ii.prototype.f;ii.prototype.e=function(a){this.set("checked",a)};
-ii.prototype.setChecked=ii.prototype.e;ii.prototype.i=function(){var a=this.a;"checkbox"===a.type||"radio"===a.type?this.e(a.checked):this.f(a.value)};ii.prototype.g=function(){this.a.checked=this.b()};ii.prototype.l=function(){this.a.value=this.d()};function ji(a,b,c,d){Rg.call(this,a,b,d);this.a=c;this.originalEvent=c.a;this.pixel=b.xd(this.originalEvent);this.coordinate=b.ta(this.pixel)}t(ji,Rg);ji.prototype.preventDefault=function(){ji.K.preventDefault.call(this);this.a.preventDefault()};ji.prototype.d=function(){ji.K.d.call(this);this.a.d()};function ki(a,b,c,d){ji.call(this,a,b,c.a,d);this.c=c}t(ki,ji);
-function li(a){hd.call(this);this.c=a;this.e=0;this.i=!1;this.f=this.g=this.b=null;a=this.c.b;this.p=0;this.k={};this.d=new uh(a);this.a=null;this.g=y(this.d,Zg,this.ig,!1,this);this.l=y(this.d,$g,this.li,!1,this)}t(li,hd);function mi(a,b){var c;c=new ki(ni,a.c,b);a.dispatchEvent(c);0!==a.e?(ba.clearTimeout(a.e),a.e=0,c=new ki(oi,a.c,b),a.dispatchEvent(c)):a.e=ba.setTimeout(sa(function(){this.e=0;var a=new ki(pi,this.c,b);this.dispatchEvent(a)},a),250)}
-function qi(a,b){b.type==ri||b.type==si?delete a.k[b.pointerId]:b.type==ti&&(a.k[b.pointerId]=!0);a.p=uc(a.k)}k=li.prototype;k.ne=function(a){qi(this,a);var b=new ki(ri,this.c,a);this.dispatchEvent(b);0===this.p&&(Ta(this.b,Yc),this.b=null,ec(this.a),this.a=null);!this.i&&0===a.button&&mi(this,this.f)};
-k.ig=function(a){qi(this,a);var b=new ki(ti,this.c,a);this.dispatchEvent(b);this.f=a;this.i=!1;null===this.b&&(this.a=new uh(document),this.b=[y(this.a,ui,this.Vg,!1,this),y(this.a,ri,this.ne,!1,this),y(this.d,si,this.ne,!1,this)]);a.preventDefault()};k.Vg=function(a){if(a.clientX!=this.f.clientX||a.clientY!=this.f.clientY){this.i=!0;var b=new ki(vi,this.c,a);this.dispatchEvent(b)}a.preventDefault()};k.li=function(a){this.dispatchEvent(new ki(a.type,this.c,a))};
-k.I=function(){null!==this.l&&(Yc(this.l),this.l=null);null!==this.g&&(Yc(this.g),this.g=null);null!==this.b&&(Ta(this.b,Yc),this.b=null);null!==this.a&&(ec(this.a),this.a=null);null!==this.d&&(ec(this.d),this.d=null);li.K.I.call(this)};var pi="singleclick",ni="click",oi="dblclick",vi="pointerdrag",ui="pointermove",ti="pointerdown",ri="pointerup",si="pointercancel",wi={Zi:pi,Oi:ni,Pi:oi,Si:vi,Vi:ui,Ri:ti,Yi:ri,Xi:"pointerover",Wi:"pointerout",Ti:"pointerenter",Ui:"pointerleave",Qi:si};function xi(a){a=a.a;return a.c&&!a.i&&a.e}function yi(a){return a.type==pi}function zi(a){a=a.a;return!a.c&&!a.i&&!a.e}function Ai(a){a=a.a;return!a.c&&!a.i&&a.e}function Bi(a){a=a.a.target.tagName;return"INPUT"!==a&&"SELECT"!==a&&"TEXTAREA"!==a}function Ci(a){return 1==a.c.pointerId};function Di(){ld.call(this);this.extent=void 0;this.i=-1;this.l={};this.o=this.k=0}t(Di,ld);Di.prototype.g=function(a,b){var c=l(b)?b:[NaN,NaN];this.Fa(a[0],a[1],c,Infinity);return c};Di.prototype.zb=cd;Di.prototype.f=function(a,b){this.Mb(Se(a,b));return this};var Ei=[0,0,0,1],Fi=[],Gi=[0,0,0,1];function Hi(a){a=l(a)?a:{};this.a=l(a.color)?a.color:null}Hi.prototype.c=function(){return this.a};function Ii(a){this.r=a.opacity;this.u=a.rotateWithView;this.g=a.rotation;this.i=a.scale;this.D=a.snapToPixel}Ii.prototype.l=function(){return this.g};Ii.prototype.k=function(){return this.i};function Ji(a){a=l(a)?a:{};this.a=l(a.color)?a.color:null;this.b=a.lineCap;this.d=l(a.lineDash)?a.lineDash:null;this.e=a.lineJoin;this.f=a.miterLimit;this.c=a.width}k=Ji.prototype;k.Dh=function(){return this.a};k.xf=function(){return this.b};k.Eh=function(){return this.d};k.yf=function(){return this.e};k.Df=function(){return this.f};k.Fh=function(){return this.c};function Ki(a){a=l(a)?a:{};this.e=Ff("CANVAS");this.c=null;this.b=l(a.fill)?a.fill:null;this.p=[0,0];this.d=a.radius;this.a=l(a.stroke)?a.stroke:null;var b,c=this.e,d;null===this.a?d=0:(b=Jg(this.a.a),d=this.a.c,l(d)||(d=1));var e=2*(this.d+d)+1;c.height=e;c.width=e;var e=c.width,f=c.getContext("2d");f.arc(e/2,e/2,this.d,0,2*Math.PI,!0);null!==this.b&&(f.fillStyle=Jg(this.b.a),f.fill());null!==this.a&&(f.strokeStyle=b,f.lineWidth=d,f.stroke());null===this.b?(c=this.c=Ff("CANVAS"),c.height=e,c.width=
-e,f=c.getContext("2d"),f.arc(e/2,e/2,this.d,0,2*Math.PI,!0),f.fillStyle=Ei,f.fill(),null!==this.a&&(f.strokeStyle=b,f.lineWidth=d,f.stroke())):this.c=c;b=e;this.f=[b/2,b/2];this.o=[b,b];Ii.call(this,{opacity:1,rotateWithView:!1,rotation:0,scale:1,snapToPixel:l(a.snapToPixel)?a.snapToPixel:!0})}t(Ki,Ii);k=Ki.prototype;k.Pb=function(){return this.f};k.zh=function(){return this.b};k.ue=function(){return this.c};k.sc=function(){return this.e};k.ve=function(){return 2};k.Wb=function(){return this.p};
-k.Ah=function(){return this.d};k.lb=function(){return this.o};k.Bh=function(){return this.a};k.fe=ca;k.we=ca;k.Ne=ca;function Li(a){a=l(a)?a:{};this.d=l(a.fill)?a.fill:null;this.e=l(a.image)?a.image:null;this.b=l(a.stroke)?a.stroke:null;this.c=l(a.text)?a.text:null;this.a=a.zIndex}k=Li.prototype;k.Gh=function(){return this.d};k.Hh=function(){return this.e};k.Ih=function(){return this.b};k.Jh=function(){return this.c};k.Qf=function(){return this.a};function Mi(a){la(a)||(a=ha(a)?a:[a],a=bd(a));return a}
-function Ni(){var a=new Hi({color:"rgba(255,255,255,0.4)"}),b=new Ji({color:"#3399CC",width:1.25}),c=[new Li({image:new Ki({fill:a,stroke:b,radius:5}),fill:a,stroke:b})];Ni=function(){return c};return c}
-function Oi(){var a={},b=[255,255,255,1],c=[0,153,255,1];a.Polygon=[new Li({fill:new Hi({color:[255,255,255,.5]})})];a.MultiPolygon=a.Polygon;a.LineString=[new Li({stroke:new Ji({color:b,width:5})}),new Li({stroke:new Ji({color:c,width:3})})];a.MultiLineString=a.LineString;a.Point=[new Li({image:new Ki({radius:6,fill:new Hi({color:c}),stroke:new Ji({color:b,width:1.5})}),zIndex:Infinity})];a.MultiPoint=a.Point;a.GeometryCollection=a.Polygon.concat(a.Point);return a};function G(a){pd.call(this);this.T=void 0;this.a="geometry";this.g=null;this.d=void 0;this.f=null;y(this,td(this.a),this.Vc,!1,this);l(a)&&(a instanceof Di||null===a?this.Ba(a):this.L(a))}t(G,pd);G.prototype.clone=function(){var a=new G(this.R());a.e(this.a);var b=this.J();null!=b&&a.Ba(b.clone());b=this.g;null===b||a.i(b);return a};G.prototype.J=function(){return this.get(this.a)};G.prototype.getGeometry=G.prototype.J;k=G.prototype;k.tf=function(){return this.T};k.sf=function(){return this.a};
-k.Qg=function(){return this.g};k.Rg=function(){return this.d};k.Zf=function(){this.n()};k.Vc=function(){null!==this.f&&(Yc(this.f),this.f=null);var a=this.J();null!=a&&(this.f=y(a,"change",this.Zf,!1,this),this.n())};k.Ba=function(a){this.set(this.a,a)};G.prototype.setGeometry=G.prototype.Ba;G.prototype.i=function(a){this.g=a;la(a)||(a=ha(a)?a:[a],a=bd(a));this.d=a;this.n()};G.prototype.b=function(a){this.T=a;this.n()};
-G.prototype.e=function(a){Xc(this,td(this.a),this.Vc,!1,this);this.a=a;y(this,td(this.a),this.Vc,!1,this);this.Vc()};function Pi(a,b,c,d,e,f,g){fc.call(this,a,b);this.vectorContext=c;this.a=d;this.frameState=e;this.context=f;this.glContext=g}t(Pi,fc);function Qi(a,b,c,d,e,f){var g=e[0],h=e[1],m=e[4],n=e[5],p=e[12];e=e[13];for(var r=l(f)?f:[],q=0;b<c;b+=d){var u=a[b],x=a[b+1];r[q++]=g*u+m*x+p;r[q++]=h*u+n*x+e}l(f)&&r.length!=q&&(r.length=q);return r};function Ri(){Di.call(this);this.b="XY";this.a=2;this.j=null}t(Ri,Di);function Si(a){if("XY"==a)return 2;if("XYZ"==a||"XYM"==a)return 3;if("XYZM"==a)return 4}k=Ri.prototype;k.zb=cd;k.s=function(a){if(this.i!=this.c){var b=this.j,c=this.j.length,d=this.a,e=Zd(this.extent);this.extent=ce(e,b,0,c,d);this.i=this.c}return qe(this.extent,a)};k.Xa=function(){return this.j.slice(0,this.a)};k.Ya=function(){return this.j.slice(this.j.length-this.a)};k.Za=function(){return this.b};
-k.zd=function(a){this.o!=this.c&&(Cc(this.l),this.k=0,this.o=this.c);if(0>a||0!==this.k&&a<=this.k)return this;var b=a.toString();if(this.l.hasOwnProperty(b))return this.l[b];var c=this.Qb(a);if(c.j.length<this.j.length)return this.l[b]=c;this.k=a;return this};k.Qb=function(){return this};function Ui(a,b,c){a.a=Si(b);a.b=b;a.j=c}
-function Vi(a,b,c,d){if(l(b))c=Si(b);else{for(b=0;b<d;++b){if(0===c.length){a.b="XY";a.a=2;return}c=c[0]}c=c.length;b=2==c?"XY":3==c?"XYZ":4==c?"XYZM":void 0}a.b=b;a.a=c}k.Mb=function(a){null!==this.j&&(a(this.j,this.j,this.a),this.n())};function Wi(a,b){var c=0,d,e;d=0;for(e=b.length;d<e;++d)a[c++]=b[d];return c}function Xi(a,b,c,d){var e,f;e=0;for(f=c.length;e<f;++e){var g=c[e],h;for(h=0;h<d;++h)a[b++]=g[h]}return b}function Yi(a,b,c,d,e){e=l(e)?e:[];var f=0,g,h;g=0;for(h=c.length;g<h;++g)b=Xi(a,b,c[g],d),e[f++]=b;e.length=f;return e};function Zi(a,b,c){Ri.call(this);this.He(a,l(b)?b:0,c)}t(Zi,Ri);k=Zi.prototype;k.clone=function(){var a=new Zi(null);Ui(a,this.b,this.j.slice());a.n();return a};k.Fa=function(a,b,c,d){var e=this.j;a-=e[0];var f=b-e[1];b=a*a+f*f;if(b<d){if(0===b)for(d=0;d<this.a;++d)c[d]=e[d];else for(d=this.pe()/Math.sqrt(b),c[0]=e[0]+d*a,c[1]=e[1]+d*f,d=2;d<this.a;++d)c[d]=e[d];c.length=this.a;return b}return d};k.zb=function(a,b){var c=this.j,d=a-c[0],c=b-c[1];return d*d+c*c<=$i(this)};
-k.Dd=function(){return this.j.slice(0,this.a)};k.s=function(a){if(this.i!=this.c){var b=this.j,c=b[this.a]-b[0];this.extent=Ud(b[0]-c,b[1]-c,b[0]+c,b[1]+c,this.extent);this.i=this.c}return qe(this.extent,a)};k.pe=function(){return Math.sqrt($i(this))};function $i(a){var b=a.j[a.a]-a.j[0];a=a.j[a.a+1]-a.j[1];return b*b+a*a}k.G=function(){return"Circle"};k.Zg=function(a){var b=this.a,c=a.slice();c[b]=c[0]+(this.j[b]-this.j[0]);var d;for(d=1;d<b;++d)c[b+d]=a[d];Ui(this,this.b,c);this.n()};
-k.He=function(a,b,c){if(null===a)Ui(this,"XY",null);else{Vi(this,c,a,0);null===this.j&&(this.j=[]);c=this.j;a=Wi(c,a);c[a++]=c[0]+b;var d;b=1;for(d=this.a;b<d;++b)c[a++]=c[b];c.length=a}this.n()};k.yi=function(a){this.j[this.a]=this.j[0]+a;this.n()};function aj(a){Di.call(this);this.e=l(a)?a:null;bj(this)}t(aj,Di);function cj(a){var b=[],c,d;c=0;for(d=a.length;c<d;++c)b.push(a[c].clone());return b}function dj(a){var b,c;if(null!==a.e)for(b=0,c=a.e.length;b<c;++b)Xc(a.e[b],"change",a.n,!1,a)}function bj(a){var b,c;if(null!==a.e)for(b=0,c=a.e.length;b<c;++b)y(a.e[b],"change",a.n,!1,a)}k=aj.prototype;k.clone=function(){var a=new aj(null);a.Ie(this.e);return a};
-k.Fa=function(a,b,c,d){if(d<Xd(this.s(),a,b))return d;var e=this.e,f,g;f=0;for(g=e.length;f<g;++f)d=e[f].Fa(a,b,c,d);return d};k.zb=function(a,b){var c=this.e,d,e;d=0;for(e=c.length;d<e;++d)if(c[d].zb(a,b))return!0;return!1};k.s=function(a){if(this.i!=this.c){var b=Zd(this.extent),c=this.e,d,e;d=0;for(e=c.length;d<e;++d)be(b,c[d].s());this.extent=b;this.i=this.c}return qe(this.extent,a)};k.$d=function(){return cj(this.e)};
-k.zd=function(a){this.o!=this.c&&(Cc(this.l),this.k=0,this.o=this.c);if(0>a||0!==this.k&&a<this.k)return this;var b=a.toString();if(this.l.hasOwnProperty(b))return this.l[b];var c=[],d=this.e,e=!1,f,g;f=0;for(g=d.length;f<g;++f){var h=d[f],m=h.zd(a);c.push(m);m!==h&&(e=!0)}if(e)return a=new aj(null),dj(a),a.e=c,bj(a),a.n(),this.l[b]=a;this.k=a;return this};k.G=function(){return"GeometryCollection"};k.ka=function(){return 0==this.e.length};k.Ie=function(a){a=cj(a);dj(this);this.e=a;bj(this);this.n()};
-k.Mb=function(a){var b=this.e,c,d;c=0;for(d=b.length;c<d;++c)b[c].Mb(a);this.n()};k.I=function(){dj(this);aj.K.I.call(this)};function ej(a,b,c,d,e,f){var g=e-c,h=f-d;if(0!==g||0!==h){var m=((a-c)*g+(b-d)*h)/(g*g+h*h);1<m?(c=e,d=f):0<m&&(c+=g*m,d+=h*m)}return fj(a,b,c,d)}function fj(a,b,c,d){a=c-a;b=d-b;return a*a+b*b};function gj(a,b,c,d,e,f,g){var h=a[b],m=a[b+1],n=a[c]-h,p=a[c+1]-m;if(0!==n||0!==p)if(f=((e-h)*n+(f-m)*p)/(n*n+p*p),1<f)b=c;else if(0<f){for(e=0;e<d;++e)g[e]=Lb(a[b+e],a[c+e],f);g.length=d;return}for(e=0;e<d;++e)g[e]=a[b+e];g.length=d}function hj(a,b,c,d,e){var f=a[b],g=a[b+1];for(b+=d;b<c;b+=d){var h=a[b],m=a[b+1],f=fj(f,g,h,m);f>e&&(e=f);f=h;g=m}return e}function ij(a,b,c,d,e){var f,g;f=0;for(g=c.length;f<g;++f){var h=c[f];e=hj(a,b,h,d,e);b=h}return e}
-function jj(a,b,c,d,e,f,g,h,m,n,p){if(b==c)return n;var r;if(0===e){r=fj(g,h,a[b],a[b+1]);if(r<n){for(p=0;p<d;++p)m[p]=a[b+p];m.length=d;return r}return n}for(var q=l(p)?p:[NaN,NaN],u=b+d;u<c;)if(gj(a,u-d,u,d,g,h,q),r=fj(g,h,q[0],q[1]),r<n){n=r;for(p=0;p<d;++p)m[p]=q[p];m.length=d;u+=d}else u+=d*Math.max((Math.sqrt(r)-Math.sqrt(n))/e|0,1);if(f&&(gj(a,c-d,b,d,g,h,q),r=fj(g,h,q[0],q[1]),r<n)){n=r;for(p=0;p<d;++p)m[p]=q[p];m.length=d}return n}
-function kj(a,b,c,d,e,f,g,h,m,n,p){p=l(p)?p:[NaN,NaN];var r,q;r=0;for(q=c.length;r<q;++r){var u=c[r];n=jj(a,b,u,d,e,f,g,h,m,n,p);b=u}return n};function lj(a,b,c,d,e){e=l(e)?e:[];for(var f=0;b<c;b+=d)e[f++]=a.slice(b,b+d);e.length=f;return e}function mj(a,b,c,d,e){e=l(e)?e:[];var f=0,g,h;g=0;for(h=c.length;g<h;++g){var m=c[g];e[f++]=lj(a,b,m,d,e[f]);b=m}e.length=f;return e};function nj(a,b,c,d,e){var f=NaN,g=NaN,h=(c-b)/d;if(0!==h)if(1==h)f=a[b],g=a[b+1];else if(2==h)f=.5*a[b]+.5*a[b+d],g=.5*a[b+1]+.5*a[b+d+1];else{var g=a[b],h=a[b+1],m=0,f=[0],n;for(n=b+d;n<c;n+=d){var p=a[n],r=a[n+1],m=m+Math.sqrt((p-g)*(p-g)+(r-h)*(r-h));f.push(m);g=p;h=r}c=.5*m;for(var q,g=hb,h=0,m=f.length;h<m;)n=h+m>>1,p=g(c,f[n]),0<p?h=n+1:(m=n,q=!p);q=q?h:~h;0>q?(c=(c-f[-q-2])/(f[-q-1]-f[-q-2]),b+=(-q-2)*d,f=Lb(a[b],a[b+d],c),g=Lb(a[b+1],a[b+d+1],c)):(f=a[b+q*d],g=a[b+q*d+1])}return null!=e?
-(e[0]=f,e[1]=g,e):[f,g]}function oj(a,b,c,d,e,f){if(c==b)return null;if(e<a[b+d-1])return f?(c=a.slice(b,b+d),c[d-1]=e,c):null;if(a[c-1]<e)return f?(c=a.slice(c-d,c),c[d-1]=e,c):null;if(e==a[b+d-1])return a.slice(b,b+d);b/=d;for(c/=d;b<c;)f=b+c>>1,e<a[(f+1)*d-1]?c=f:b=f+1;c=a[b*d-1];if(e==c)return a.slice((b-1)*d,(b-1)*d+d);f=(e-c)/(a[(b+1)*d-1]-c);c=[];var g;for(g=0;g<d-1;++g)c.push(Lb(a[(b-1)*d+g],a[b*d+g],f));c.push(e);return c}
-function pj(a,b,c,d,e,f){var g=0;if(f)return oj(a,g,b[b.length-1],c,d,e);if(d<a[c-1])return e?(a=a.slice(0,c),a[c-1]=d,a):null;if(a[a.length-1]<d)return e?(a=a.slice(a.length-c),a[c-1]=d,a):null;e=0;for(f=b.length;e<f;++e){var h=b[e];if(g!=h){if(d<a[g+c-1])break;if(d<=a[h-1])return oj(a,g,h,c,d,!1);g=h}}return null};function qj(a,b,c,d,e,f,g){var h=(c-b)/d;if(3>h){for(;b<c;b+=d)f[g++]=a[b],f[g++]=a[b+1];return g}var m=Array(h);m[0]=1;m[h-1]=1;c=[b,c-d];for(var n=0,p;0<c.length;){var r=c.pop(),q=c.pop(),u=0,x=a[q],B=a[q+1],E=a[r],F=a[r+1];for(p=q+d;p<r;p+=d){var w=ej(a[p],a[p+1],x,B,E,F);w>u&&(n=p,u=w)}u>e&&(m[(n-b)/d]=1,q+d<n&&c.push(q,n),n+d<r&&c.push(n,r))}for(p=0;p<h;++p)m[p]&&(f[g++]=a[b+p*d],f[g++]=a[b+p*d+1]);return g}
-function rj(a,b,c,d,e,f,g,h){var m,n;m=0;for(n=c.length;m<n;++m){var p=c[m];a:{var r=a,q=p,u=d,x=e,B=f;if(b!=q){var E=x*Math.round(r[b]/x),F=x*Math.round(r[b+1]/x);b+=u;B[g++]=E;B[g++]=F;var w=void 0,U=void 0;do if(w=x*Math.round(r[b]/x),U=x*Math.round(r[b+1]/x),b+=u,b==q){B[g++]=w;B[g++]=U;break a}while(w==E&&U==F);for(;b<q;){var Q,ea;Q=x*Math.round(r[b]/x);ea=x*Math.round(r[b+1]/x);b+=u;if(Q!=w||ea!=U){var Y=w-E,za=U-F,kb=Q-E,Aa=ea-F;Y*Aa==za*kb&&(0>Y&&kb<Y||Y==kb||0<Y&&kb>Y)&&(0>za&&Aa<za||za==
-Aa||0<za&&Aa>za)||(B[g++]=w,B[g++]=U,E=w,F=U);w=Q;U=ea}}B[g++]=w;B[g++]=U}}h.push(g);b=p}return g};function H(a,b){Ri.call(this);this.d=null;this.q=this.r=this.p=-1;this.P(a,b)}t(H,Ri);k=H.prototype;k.$e=function(a){null===this.j?this.j=a.slice():Pb(this.j,a);this.n()};k.clone=function(){var a=new H(null);sj(a,this.b,this.j.slice());return a};k.Fa=function(a,b,c,d){if(d<Xd(this.s(),a,b))return d;this.q!=this.c&&(this.r=Math.sqrt(hj(this.j,0,this.j.length,this.a,0)),this.q=this.c);return jj(this.j,0,this.j.length,this.a,this.r,!1,a,b,c,d)};
-k.$g=function(a,b){return"XYM"!=this.b&&"XYZM"!=this.b?null:oj(this.j,0,this.j.length,this.a,a,l(b)?b:!1)};k.H=function(){return lj(this.j,0,this.j.length,this.a)};k.ah=function(){var a=this.j,b=this.a,c=a[0],d=a[1],e=0,f;for(f=0+b;f<this.j.length;f+=b)var g=a[f],h=a[f+1],e=e+Math.sqrt((g-c)*(g-c)+(h-d)*(h-d)),c=g,d=h;return e};function tj(a){a.p!=a.c&&(a.d=nj(a.j,0,a.j.length,a.a,a.d),a.p=a.c);return a.d}
-k.Qb=function(a){var b=[];b.length=qj(this.j,0,this.j.length,this.a,a,b,0);a=new H(null);sj(a,"XY",b);return a};k.G=function(){return"LineString"};k.P=function(a,b){null===a?sj(this,"XY",null):(Vi(this,b,a,1),null===this.j&&(this.j=[]),this.j.length=Xi(this.j,0,a,this.a),this.n())};function sj(a,b,c){Ui(a,b,c);a.n()};function uj(a,b){Ri.call(this);this.d=[];this.p=this.q=-1;this.P(a,b)}t(uj,Ri);k=uj.prototype;k.af=function(a){null===this.j?this.j=a.j.slice():Pb(this.j,a.j.slice());this.d.push(this.j.length);this.n()};k.clone=function(){var a=new uj(null);vj(a,this.b,this.j.slice(),this.d.slice());return a};k.Fa=function(a,b,c,d){if(d<Xd(this.s(),a,b))return d;this.p!=this.c&&(this.q=Math.sqrt(ij(this.j,0,this.d,this.a,0)),this.p=this.c);return kj(this.j,0,this.d,this.a,this.q,!1,a,b,c,d)};
-k.dh=function(a,b,c){return"XYM"!=this.b&&"XYZM"!=this.b||0===this.j.length?null:pj(this.j,this.d,this.a,a,l(b)?b:!1,l(c)?c:!1)};k.H=function(){return mj(this.j,0,this.d,this.a)};k.zf=function(a){if(0>a||this.d.length<=a)return null;var b=new H(null);sj(b,this.b,this.j.slice(0===a?0:this.d[a-1],this.d[a]));return b};k.oc=function(){var a=this.j,b=this.d,c=this.b,d=[],e=0,f,g;f=0;for(g=b.length;f<g;++f){var h=b[f],m=new H(null);sj(m,c,a.slice(e,h));d.push(m);e=h}return d};
-function wj(a){var b=[],c=a.j,d=0,e=a.d;a=a.a;var f,g;f=0;for(g=e.length;f<g;++f){var h=e[f],d=nj(c,d,h,a);Pb(b,d);d=h}return b}k.Qb=function(a){var b=[],c=[],d=this.j,e=this.d,f=this.a,g=0,h=0,m,n;m=0;for(n=e.length;m<n;++m){var p=e[m],h=qj(d,g,p,f,a,b,h);c.push(h);g=p}b.length=h;a=new uj(null);vj(a,"XY",b,c);return a};k.G=function(){return"MultiLineString"};
-k.P=function(a,b){if(null===a)vj(this,"XY",null,this.d);else{Vi(this,b,a,2);null===this.j&&(this.j=[]);var c=Yi(this.j,0,a,this.a,this.d);this.j.length=0===c.length?0:c[c.length-1];this.n()}};function vj(a,b,c,d){Ui(a,b,c);a.d=d;a.n()}function xj(a,b){var c="XY",d=[],e=[],f,g;f=0;for(g=b.length;f<g;++f){var h=b[f];0===f&&(c=h.b);Pb(d,h.j);e.push(d.length)}vj(a,c,d,e)};function yj(a,b){Ri.call(this);this.P(a,b)}t(yj,Ri);k=yj.prototype;k.clone=function(){var a=new yj(null);zj(a,this.b,this.j.slice());return a};k.Fa=function(a,b,c,d){var e=this.j;a=fj(a,b,e[0],e[1]);if(a<d){d=this.a;for(b=0;b<d;++b)c[b]=e[b];c.length=d;return a}return d};k.H=function(){return null===this.j?[]:this.j.slice()};k.s=function(a){this.i!=this.c&&(this.extent=$d(this.j,this.extent),this.i=this.c);return qe(this.extent,a)};k.G=function(){return"Point"};
-k.P=function(a,b){null===a?zj(this,"XY",null):(Vi(this,b,a,0),null===this.j&&(this.j=[]),this.j.length=Wi(this.j,a),this.n())};function zj(a,b,c){Ui(a,b,c);a.n()};function Aj(a,b){Ri.call(this);this.P(a,b)}t(Aj,Ri);k=Aj.prototype;k.cf=function(a){null===this.j?this.j=a.j.slice():Pb(this.j,a.j);this.n()};k.clone=function(){var a=new Aj(null);Ui(a,this.b,this.j.slice());a.n();return a};k.Fa=function(a,b,c,d){if(d<Xd(this.s(),a,b))return d;var e=this.j,f=this.a,g,h,m;g=0;for(h=e.length;g<h;g+=f)if(m=fj(a,b,e[g],e[g+1]),m<d){d=m;for(m=0;m<f;++m)c[m]=e[g+m];c.length=f}return d};k.H=function(){return lj(this.j,0,this.j.length,this.a)};
-k.If=function(a){var b=null===this.j?0:this.j.length/this.a;if(0>a||b<=a)return null;b=new yj(null);zj(b,this.b,this.j.slice(a*this.a,(a+1)*this.a));return b};k.Qc=function(){var a=this.j,b=this.b,c=this.a,d=[],e,f;e=0;for(f=a.length;e<f;e+=c){var g=new yj(null);zj(g,b,a.slice(e,e+c));d.push(g)}return d};k.G=function(){return"MultiPoint"};k.P=function(a,b){null===a?Ui(this,"XY",null):(Vi(this,b,a,1),null===this.j&&(this.j=[]),this.j.length=Xi(this.j,0,a,this.a));this.n()};function Bj(a,b,c,d){for(var e=0,f=a[c-d],g=a[c-d+1];b<c;b+=d)var h=a[b],m=a[b+1],e=e+(g*h-f*m),f=h,g=m;return e/2}function Cj(a,b,c,d){var e=0,f,g;f=0;for(g=c.length;f<g;++f){var h=c[f],e=e+Bj(a,b,h,d);b=h}return e};function Dj(a,b){Ri.call(this);this.d=this.p=-1;this.P(a,b)}t(Dj,Ri);k=Dj.prototype;k.clone=function(){var a=new Dj(null);Ej(a,this.b,this.j.slice());return a};k.Fa=function(a,b,c,d){if(d<Xd(this.s(),a,b))return d;this.d!=this.c&&(this.p=Math.sqrt(hj(this.j,0,this.j.length,this.a,0)),this.d=this.c);return jj(this.j,0,this.j.length,this.a,this.p,!0,a,b,c,d)};k.bh=function(){return Bj(this.j,0,this.j.length,this.a)};k.H=function(){return lj(this.j,0,this.j.length,this.a)};
-k.Qb=function(a){var b=[];b.length=qj(this.j,0,this.j.length,this.a,a,b,0);a=new Dj(null);Ej(a,"XY",b);return a};k.G=function(){return"LinearRing"};k.P=function(a,b){null===a?Ej(this,"XY",null):(Vi(this,b,a,1),null===this.j&&(this.j=[]),this.j.length=Xi(this.j,0,a,this.a),this.n())};function Ej(a,b,c){Ui(a,b,c);a.n()};function Fj(a,b,c,d,e,f){for(var g=!1,h=a[c-d],m=a[c-d+1];b<c;b+=d){var n=a[b],p=a[b+1];m>f!=p>f&&e<(n-h)*(f-m)/(p-m)+h&&(g=!g);h=n;m=p}return g}function Gj(a,b,c,d,e,f){if(0===c.length||!Fj(a,b,c[0],d,e,f))return!1;var g;b=1;for(g=c.length;b<g;++b)if(Fj(a,c[b-1],c[b],d,e,f))return!1;return!0};function Hj(a,b,c,d,e,f,g){var h,m,n,p,r,q=e[f+1],u=[],x=c[0];n=a[x-d];r=a[x-d+1];for(h=b;h<x;h+=d){p=a[h];m=a[h+1];if(q<=r&&m<=q||r<=q&&q<=m)n=(q-r)/(m-r)*(p-n)+n,u.push(n);n=p;r=m}x=NaN;r=-Infinity;u.sort();n=u[0];h=1;for(m=u.length;h<m;++h){p=u[h];var B=Math.abs(p-n);B>r&&(n=(n+p)/2,Gj(a,b,c,d,n,q)&&(x=n,r=B));n=p}isNaN(x)&&(x=e[f]);return l(g)?(g.push(x,q),g):[x,q]};function Ij(a,b,c,d){for(var e=0,f=a[c-d],g=a[c-d+1];b<c;b+=d)var h=a[b],m=a[b+1],e=e+(h-f)*(m+g),f=h,g=m;return 0<e}function Jj(a,b,c){var d=0,e,f;e=0;for(f=b.length;e<f;++e){var g=b[e],d=Ij(a,d,g,c);if(0===e?!d:d)return!1;d=g}return!0}function Kj(a,b,c,d){var e,f;e=0;for(f=c.length;e<f;++e){var g=c[e],h=Ij(a,b,g,d);if(0===e?!h:h)for(var h=a,m=g,n=d;b<m-n;){var p;for(p=0;p<n;++p){var r=h[b+p];h[b+p]=h[m-n+p];h[m-n+p]=r}b+=n;m-=n}b=g}return b};function I(a,b){Ri.call(this);this.d=[];this.q=-1;this.r=null;this.F=this.u=this.D=-1;this.p=null;this.P(a,b)}t(I,Ri);k=I.prototype;k.bf=function(a){null===this.j?this.j=a.j.slice():Pb(this.j,a.j);this.d.push(this.j.length);this.n()};k.clone=function(){var a=new I(null);Lj(a,this.b,this.j.slice(),this.d.slice());return a};
-k.Fa=function(a,b,c,d){if(d<Xd(this.s(),a,b))return d;this.u!=this.c&&(this.D=Math.sqrt(ij(this.j,0,this.d,this.a,0)),this.u=this.c);return kj(this.j,0,this.d,this.a,this.D,!0,a,b,c,d)};k.zb=function(a,b){return Gj(Mj(this),0,this.d,this.a,a,b)};k.fh=function(){return Cj(Mj(this),0,this.d,this.a)};k.H=function(){return mj(this.j,0,this.d,this.a)};function Nj(a){if(a.q!=a.c){var b=je(a.s());a.r=Hj(Mj(a),0,a.d,a.a,b,0);a.q=a.c}return a.r}k.vf=function(){return new yj(Nj(this))};k.Bf=function(){return this.d.length};
-k.Af=function(a){if(0>a||this.d.length<=a)return null;var b=new Dj(null);Ej(b,this.b,this.j.slice(0===a?0:this.d[a-1],this.d[a]));return b};k.Nc=function(){var a=this.b,b=this.j,c=this.d,d=[],e=0,f,g;f=0;for(g=c.length;f<g;++f){var h=c[f],m=new Dj(null);Ej(m,a,b.slice(e,h));d.push(m);e=h}return d};function Mj(a){if(a.F!=a.c){var b=a.j;Jj(b,a.d,a.a)?a.p=b:(a.p=b.slice(),a.p.length=Kj(a.p,0,a.d,a.a));a.F=a.c}return a.p}
-k.Qb=function(a){var b=[],c=[];b.length=rj(this.j,0,this.d,this.a,Math.sqrt(a),b,0,c);a=new I(null);Lj(a,"XY",b,c);return a};k.G=function(){return"Polygon"};k.P=function(a,b){if(null===a)Lj(this,"XY",null,this.d);else{Vi(this,b,a,2);null===this.j&&(this.j=[]);var c=Yi(this.j,0,a,this.a,this.d);this.j.length=0===c.length?0:c[c.length-1];this.n()}};function Lj(a,b,c,d){Ui(a,b,c);a.d=d;a.n()}
-function Oj(a,b,c,d){var e=l(d)?d:32;d=[];var f;for(f=0;f<e;++f)db(d,a.offset(b,c,2*Math.PI*f/e));d.push(d[0],d[1]);a=new I(null);Lj(a,"XY",d,[d.length]);return a};function Pj(a,b){Ri.call(this);this.d=[];this.q=-1;this.r=null;this.F=this.u=this.D=-1;this.p=null;this.P(a,b)}t(Pj,Ri);k=Pj.prototype;k.df=function(a){if(null===this.j)this.j=a.j.slice(),a=a.d.slice(),this.d.push();else{var b=this.j.length;Pb(this.j,a.j);a=a.d.slice();var c,d;c=0;for(d=a.length;c<d;++c)a[c]+=b}this.d.push(a);this.n()};k.clone=function(){var a=new Pj(null);Qj(a,this.b,this.j.slice(),this.d.slice());return a};
-k.Fa=function(a,b,c,d){if(d<Xd(this.s(),a,b))return d;if(this.u!=this.c){var e=this.d,f=0,g=0,h,m;h=0;for(m=e.length;h<m;++h)var n=e[h],g=ij(this.j,f,n,this.a,g),f=n[n.length-1];this.D=Math.sqrt(g);this.u=this.c}e=Rj(this);f=this.d;g=this.a;h=this.D;m=0;var n=l(void 0)?void 0:[NaN,NaN],p,r;p=0;for(r=f.length;p<r;++p){var q=f[p];d=kj(e,m,q,g,h,!0,a,b,c,d,n);m=q[q.length-1]}return d};
-k.zb=function(a,b){var c;a:{c=Rj(this);var d=this.d,e=0;if(0!==d.length){var f,g;f=0;for(g=d.length;f<g;++f){var h=d[f];if(Gj(c,e,h,this.a,a,b)){c=!0;break a}e=h[h.length-1]}}c=!1}return c};k.eh=function(){var a=Rj(this),b=this.d,c=0,d=0,e,f;e=0;for(f=b.length;e<f;++e)var g=b[e],d=d+Cj(a,c,g,this.a),c=g[g.length-1];return d};k.H=function(){var a=this.j,b=this.d,c=this.a,d=0,e=l(void 0)?void 0:[],f=0,g,h;g=0;for(h=b.length;g<h;++g){var m=b[g];e[f++]=mj(a,d,m,c,e[f]);d=m[m.length-1]}e.length=f;return e};
-function Sj(a){if(a.q!=a.c){var b=a.j,c=a.d,d=a.a,e=0,f=[],g,h,m=Sd();g=0;for(h=c.length;g<h;++g){var n=c[g],m=b,p=n[0],r=d,q=Zd(void 0),m=ce(q,m,e,p,r);f.push((m[0]+m[2])/2,(m[1]+m[3])/2);e=n[n.length-1]}b=Rj(a);c=a.d;d=a.a;g=0;h=[];n=0;for(m=c.length;n<m;++n)e=c[n],h=Hj(b,g,e,d,f,2*n,h),g=e[e.length-1];a.r=h;a.q=a.c}return a.r}k.wf=function(){var a=new Aj(null),b=Sj(this).slice();Ui(a,"XY",b);a.n();return a};
-function Rj(a){if(a.F!=a.c){var b=a.j,c;a:{c=a.d;var d,e;d=0;for(e=c.length;d<e;++d)if(!Jj(b,c[d],a.a)){c=!1;break a}c=!0}if(c)a.p=b;else{a.p=b.slice();c=b=a.p;d=a.d;e=a.a;var f=0,g,h;g=0;for(h=d.length;g<h;++g)f=Kj(c,f,d[g],e);b.length=f}a.F=a.c}return a.p}k.Qb=function(a){var b=[],c=[],d=this.j,e=this.d,f=this.a;a=Math.sqrt(a);var g=0,h=0,m,n;m=0;for(n=e.length;m<n;++m){var p=e[m],r=[],h=rj(d,g,p,f,a,b,h,r);c.push(r);g=p[p.length-1]}b.length=h;d=new Pj(null);Qj(d,"XY",b,c);return d};
-k.Jf=function(a){if(0>a||this.d.length<=a)return null;var b;0===a?b=0:(b=this.d[a-1],b=b[b.length-1]);a=this.d[a].slice();var c=a[a.length-1];if(0!==b){var d,e;d=0;for(e=a.length;d<e;++d)a[d]-=b}d=new I(null);Lj(d,this.b,this.j.slice(b,c),a);return d};k.Rc=function(){var a=this.b,b=this.j,c=this.d,d=[],e=0,f,g,h,m;f=0;for(g=c.length;f<g;++f){var n=c[f].slice(),p=n[n.length-1];if(0!==e)for(h=0,m=n.length;h<m;++h)n[h]-=e;h=new I(null);Lj(h,a,b.slice(e,p),n);d.push(h);e=p}return d};k.G=function(){return"MultiPolygon"};
-k.P=function(a,b){if(null===a)Qj(this,"XY",null,this.d);else{Vi(this,b,a,3);null===this.j&&(this.j=[]);var c=this.j,d=this.a,e=this.d,f=0,e=l(e)?e:[],g=0,h,m;h=0;for(m=a.length;h<m;++h)f=Yi(c,f,a[h],d,e[g]),e[g++]=f,f=f[f.length-1];e.length=g;0===e.length?this.j.length=0:(c=e[e.length-1],this.j.length=0===c.length?0:c[c.length-1]);this.n()}};function Qj(a,b,c,d){Ui(a,b,c);a.d=d;a.n()}
-function Tj(a,b){var c="XY",d=[],e=[],f,g,h;f=0;for(g=b.length;f<g;++f){var m=b[f];0===f&&(c=m.b);var n=d.length;h=m.d;var p,r;p=0;for(r=h.length;p<r;++p)h[p]+=n;Pb(d,m.j);e.push(h)}Qj(a,c,d,e)};var Uj=["Polygon","LineString","Image","Text"];function Vj(a,b){return na(a)-na(b)}function Wj(a,b){var c=.5*a/b;return c*c}function Xj(a,b,c,d,e,f,g){var h=!1,m,n;m=c.e;null===m?Yj(a,b,c,d,e):(n=m.ve(),2==n||3==n?(m.Ne(f,g),2==n&&Yj(a,b,c,d,e)):(0==n&&m.we(),m.fe(f,g),h=!0));return h}function Yj(a,b,c,d,e){b=b.J();null!=b&&(d=b.zd(d),(0,Zj[d.G()])(a,d,c,e))}
-var Zj={Point:function(a,b,c,d){var e=c.e;if(null!==e){var f=ak(a,c.a,"Image");f.$b(e);f.nc(b,d)}e=c.c;null!==e&&(a=ak(a,c.a,"Text"),a.Ca(e),a.eb(b.H(),0,2,2,b,d))},LineString:function(a,b,c,d){var e=c.b;if(null!==e){var f=ak(a,c.a,"LineString");f.Aa(null,e);f.sb(b,d)}e=c.c;null!==e&&(a=ak(a,c.a,"Text"),a.Ca(e),a.eb(tj(b),0,2,2,b,d))},Polygon:function(a,b,c,d){var e=c.d,f=c.b;if(null!==e||null!==f){var g=ak(a,c.a,"Polygon");g.Aa(e,f);g.Ob(b,d)}e=c.c;null!==e&&(a=ak(a,c.a,"Text"),a.Ca(e),a.eb(Nj(b),
-0,2,2,b,d))},MultiPoint:function(a,b,c,d){var e=c.e;if(null!==e){var f=ak(a,c.a,"Image");f.$b(e);f.lc(b,d)}e=c.c;null!==e&&(a=ak(a,c.a,"Text"),a.Ca(e),c=b.j,a.eb(c,0,c.length,b.a,b,d))},MultiLineString:function(a,b,c,d){var e=c.b;if(null!==e){var f=ak(a,c.a,"LineString");f.Aa(null,e);f.kc(b,d)}e=c.c;null!==e&&(a=ak(a,c.a,"Text"),a.Ca(e),c=wj(b),a.eb(c,0,c.length,2,b,d))},MultiPolygon:function(a,b,c,d){var e=c.d,f=c.b;if(null!==f||null!==e){var g=ak(a,c.a,"Polygon");g.Aa(e,f);g.mc(b,d)}e=c.c;null!==
-e&&(a=ak(a,c.a,"Text"),a.Ca(e),c=Sj(b),a.eb(c,0,c.length,2,b,d))},GeometryCollection:function(a,b,c,d){b=b.e;var e,f;e=0;for(f=b.length;e<f;++e)(0,Zj[b[e].G()])(a,b[e],c,d)},Circle:function(a,b,c,d){var e=c.d,f=c.b;if(null!==e||null!==f){var g=ak(a,c.a,"Polygon");g.Aa(e,f);g.jc(b,d)}e=c.c;null!==e&&(a=ak(a,c.a,"Text"),a.Ca(e),a.eb(b.Dd(),0,2,2,b,d))}};function bk(a){a=l(a)?a:{};this.g=this.d=this.e=this.c=this.b=this.a=null;this.f=void 0;this.me(l(a.style)?a.style:Ni);l(a.features)?ha(a.features)?this.xc(new A(cb(a.features))):this.xc(a.features):this.xc(new A);l(a.map)&&this.setMap(a.map)}k=bk.prototype;k.ke=function(a){this.a.push(a)};k.Lg=function(){return this.a};k.le=function(){ck(this)};k.Xf=function(a){a=a.element;this.c[na(a).toString()]=y(a,"change",this.le,!1,this);ck(this)};
-k.Yf=function(a){a=na(a.element).toString();Yc(this.c[a]);delete this.c[a];ck(this)};k.Og=function(){ck(this)};k.Pg=function(a){if(null!==this.a){var b=this.f;l(b)||(b=Ni);var c=a.a;a=a.frameState;var d=a.pixelRatio,e=a.viewState.resolution,f,g,h,m;this.a.forEach(function(a){m=a.d;h=l(m)?m.call(a,e):b(a,e);if(null!=h)for(g=h.length,f=0;f<g;++f)Xj(c,a,h[f],Wj(e,d),a,this.Og,this)},this)}};k.cd=function(a){this.a.remove(a)};function ck(a){null===a.e||a.e.O()}
-k.xc=function(a){null!==this.b&&(Ta(this.b,Yc),this.b=null);null!==this.c&&(Ta(vc(this.c),Yc),this.c=null);this.a=a;null!==a&&(this.b=[y(a,"add",this.Xf,!1,this),y(a,"remove",this.Yf,!1,this)],this.c={},a.forEach(function(a){this.c[na(a).toString()]=y(a,"change",this.le,!1,this)},this));ck(this)};k.setMap=function(a){null!==this.d&&(Yc(this.d),this.d=null);ck(this);this.e=a;null!==a&&(this.d=y(a,"postcompose",this.Pg,!1,this),a.O())};k.me=function(a){this.g=a;this.f=Mi(a);ck(this)};k.Mg=function(){return this.g};
-k.Ng=function(){return this.f};function dk(){this.defaultDataProjection=null}function ek(a,b,c){var d;l(c)&&(d={dataProjection:l(c.dataProjection)?c.dataProjection:a.ra(b),featureProjection:c.featureProjection});return fk(a,d)}function fk(a,b){var c;l(b)&&(c={featureProjection:b.featureProjection,dataProjection:null!=b.dataProjection?b.dataProjection:a.defaultDataProjection});return c}
-function gk(a,b,c){var d=l(c)?Be(c.featureProjection):null;c=l(c)?Be(c.dataProjection):null;return null===d||null===c||Re(d,c)?a:a instanceof Di?(b?a.clone():a).f(b?d:c,b?c:d):Ue(b?cb(a):a,b?d:c,b?c:d)};var hk=ba.JSON.parse;function ik(){this.defaultDataProjection=null}t(ik,dk);function jk(a){return ma(a)?a:ja(a)?(a=hk(a),l(a)?a:null):null}k=ik.prototype;k.G=function(){return"json"};k.ob=function(a,b){return kk(this,jk(a),ek(this,a,b))};k.la=function(a,b){return this.c(jk(a),ek(this,a,b))};k.uc=function(a,b){var c=jk(a),d=ek(this,a,b);return lk(c,d)};k.ra=function(a){a=jk(a).crs;return null!=a?"name"==a.type?Be(a.properties.name):"EPSG"==a.type?Be("EPSG:"+a.properties.code):null:this.defaultDataProjection};
-k.md=function(a,b){return mk(a,fk(this,b))};k.Hb=function(a,b){var c=fk(this,b),d=[],e,f;e=0;for(f=a.length;e<f;++e)d.push(mk(a[e],c));return{type:"FeatureCollection",features:d}};k.Ac=function(a,b){return this.b(a,fk(this,b))};function nk(a){a=l(a)?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Be(null!=a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326");this.a=a.geometryName}t(nk,ik);function lk(a,b){return null===a?null:gk((0,ok[a.type])(a),!1,b)}function pk(a,b){return(0,qk[a.G()])(gk(a,!0,b))}
-var ok={Point:function(a){return new yj(a.coordinates)},LineString:function(a){return new H(a.coordinates)},Polygon:function(a){return new I(a.coordinates)},MultiPoint:function(a){return new Aj(a.coordinates)},MultiLineString:function(a){return new uj(a.coordinates)},MultiPolygon:function(a){return new Pj(a.coordinates)},GeometryCollection:function(a,b){var c=Va(a.geometries,function(a){return lk(a,b)});return new aj(c)}},qk={Point:function(a){return{type:"Point",coordinates:a.H()}},LineString:function(a){return{type:"LineString",
-coordinates:a.H()}},Polygon:function(a){return{type:"Polygon",coordinates:a.H()}},MultiPoint:function(a){return{type:"MultiPoint",coordinates:a.H()}},MultiLineString:function(a){return{type:"MultiLineString",coordinates:a.H()}},MultiPolygon:function(a){return{type:"MultiPolygon",coordinates:a.H()}},GeometryCollection:function(a,b){return{type:"GeometryCollection",geometries:Va(a.e,function(a){return pk(a,b)})}},Circle:function(){return{type:"GeometryCollection",geometries:[]}}};
-function kk(a,b,c){c=lk(b.geometry,c);var d=new G;l(a.a)&&d.e(a.a);d.Ba(c);l(b.id)&&d.b(b.id);l(b.properties)&&d.L(b.properties);return d}nk.prototype.c=function(a,b){if("Feature"==a.type)return[kk(this,a,b)];if("FeatureCollection"==a.type){var c=[],d=a.features,e,f;e=0;for(f=d.length;e<f;++e)c.push(kk(this,d[e],b));return c}return[]};
-function mk(a,b){var c={type:"Feature"},d=a.T;null!=d&&(c.id=d);d=a.J();null!=d&&(d=pk(d,b),c.geometry=d);d=a.R();Dc(d,"geometry");Bc(d)||(c.properties=d);return c}nk.prototype.b=pk;var rk;a:if(document.implementation&&document.implementation.createDocument)rk=document.implementation.createDocument("","",null);else{if("undefined"!=typeof ActiveXObject){var sk=new ActiveXObject("MSXML2.DOMDocument");if(sk){sk.resolveExternals=!1;sk.validateOnParse=!1;try{sk.setProperty("ProhibitDTD",!0),sk.setProperty("MaxXMLSize",2048),sk.setProperty("MaxElementDepth",256)}catch(tk){}}if(sk){rk=sk;break a}}throw Error("Your browser does not support creating new documents");}var uk=rk;
-function vk(a,b){return uk.createElementNS(a,b)}function wk(a,b){null===a&&(a="");return uk.createNode(1,b,a)}var xk=document.implementation&&document.implementation.createDocument?vk:wk;function yk(a){return zk(a,!1,[]).join("")}function zk(a,b,c){if(4==a.nodeType||3==a.nodeType)b?c.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):c.push(a.nodeValue);else for(a=a.firstChild;null!==a;a=a.nextSibling)zk(a,b,c);return c}function Ak(a){return a.localName}
-function Bk(a){var b=a.localName;return l(b)?b:a.baseName}var Ck=ub?Bk:Ak;function Dk(a){return a instanceof Document}function Ek(a){return ma(a)&&9==a.nodeType}var Fk=ub?Ek:Dk;function Gk(a){return a instanceof Node}function Hk(a){return ma(a)&&l(a.nodeType)}var Ik=ub?Hk:Gk;function Jk(a,b,c){return a.getAttributeNS(b,c)||""}function Kk(a,b,c){var d="";a=Lk(a,b,c);l(a)&&(d=a.nodeValue);return d}var Mk=document.implementation&&document.implementation.createDocument?Jk:Kk;
-function Nk(a,b,c){return a.getAttributeNodeNS(b,c)}function Ok(a,b,c){var d=null;a=a.attributes;for(var e,f,g=0,h=a.length;g<h;++g)if(e=a[g],e.namespaceURI==b&&(f=e.prefix?e.prefix+":"+c:c,f==e.nodeName)){d=e;break}return d}var Lk=document.implementation&&document.implementation.createDocument?Nk:Ok;function Pk(a,b,c,d){a.setAttributeNS(b,c,d)}function Qk(a,b,c,d){null===b?a.setAttribute(c,d):(b=a.ownerDocument.createNode(2,c,b),b.nodeValue=d,a.setAttributeNode(b))}
-var Rk=document.implementation&&document.implementation.createDocument?Pk:Qk;function Sk(a){return(new DOMParser).parseFromString(a,"application/xml")}function Tk(a,b){return function(c,d){var e=a.call(b,c,d);l(e)&&db(d[d.length-1],e)}}function Uk(a,b){return function(c,d){var e=a.call(b,c,d);l(e)&&d[d.length-1].push(e)}}function Vk(a){return function(b,c){var d=a.call(void 0,b,c);l(d)&&(c[c.length-1]=d)}}
-function Wk(a){return function(b,c){var d=a.call(void 0,b,c);l(d)&&Ec(c[c.length-1],l(void 0)?void 0:b.localName).push(d)}}function J(a,b){return function(c,d){var e=a.call(void 0,c,d);l(e)&&(d[d.length-1][l(b)?b:c.localName]=e)}}function Xk(a,b,c){return Yk(a,b,c)}function K(a){return function(b,c,d){a.call(void 0,b,c,d);d[d.length-1].node.appendChild(b)}}function Zk(a){var b,c;return function(d,e,f){if(!l(b)){b={};var g={};g[d.localName]=a;b[d.namespaceURI]=g;c=$k(d.localName)}al(b,c,e,f)}}
-function $k(a,b){return function(c,d,e){c=d[d.length-1].node;d=a;l(d)||(d=e);e=b;l(b)||(e=c.namespaceURI);return xk(e,d)}}var bl=$k();function cl(a,b){for(var c=b.length,d=Array(c),e=0;e<c;++e)d[e]=a[b[e]];return d}function Yk(a,b,c){c=l(c)?c:{};var d,e;d=0;for(e=a.length;d<e;++d)c[a[d]]=b;return c}function dl(a,b,c,d){for(b=b.firstElementChild;null!==b;b=b.nextElementSibling){var e=a[b.namespaceURI];l(e)&&(e=e[b.localName],l(e)&&e.call(d,b,c))}}
-function L(a,b,c,d,e){d.push(a);dl(b,c,d,e);return d.pop()}function al(a,b,c,d,e,f){for(var g=(l(e)?e:c).length,h,m,n=0;n<g;++n)h=c[n],l(h)&&(m=b.call(f,h,d,l(e)?e[n]:void 0),l(m)&&a[m.namespaceURI][m.localName].call(f,m,h,d))}function el(a,b,c,d,e,f){e.push(a);al(b,c,d,e,f,void 0);e.pop()};function fl(){this.defaultDataProjection=null}t(fl,dk);k=fl.prototype;k.G=function(){return"xml"};k.ob=function(a,b){if(Fk(a))return gl(this,a,b);if(Ik(a))return this.De(a,b);if(ja(a)){var c=Sk(a);return gl(this,c,b)}return null};function gl(a,b,c){a=hl(a,b,c);return 0<a.length?a[0]:null}k.la=function(a,b){if(Fk(a))return hl(this,a,b);if(Ik(a))return this.Cb(a,b);if(ja(a)){var c=Sk(a);return hl(this,c,b)}return[]};
-function hl(a,b,c){var d=[];for(b=b.firstChild;null!==b;b=b.nextSibling)1==b.nodeType&&db(d,a.Cb(b,c));return d}k.uc=function(a,b){if(Fk(a))return this.p(a,b);if(Ik(a)){var c=il(a,[ek(this,a,l(b)?b:{})]);return l(c)?c:null}return ja(a)?(c=Sk(a),this.p(c,b)):null};k.ra=function(a){return Fk(a)?this.wc(a):Ik(a)?this.Yb(a):ja(a)?(a=Sk(a),this.wc(a)):null};k.md=function(a,b){return this.u(a,fk(this,b))};k.Hb=function(a,b){return this.c(a,fk(this,b))};
-k.Ac=function(a,b){var c=fk(this,b),d=xk("http://www.opengis.net/gml","geom"),e={node:d,srsName:this.a,curve:this.b,surface:this.i,multiSurface:this.e,multiCurve:this.d};l(c)&&Hc(e,c);jl(d,a,[e]);return d};function kl(a){a=yk(a);return ll(a)}function ll(a){if(a=/^\s*(true|1)|(false|0)\s*$/.exec(a))return l(a[1])||!1}function ml(a){a=yk(a);if(a=/^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/.exec(a)){var b=Date.UTC(parseInt(a[1],10),parseInt(a[2],10)-1,parseInt(a[3],10),parseInt(a[4],10),parseInt(a[5],10),parseInt(a[6],10))/1E3;if("Z"!=a[7]){var c="-"==a[8]?-1:1,b=b+60*c*parseInt(a[9],10);l(a[10])&&(b+=3600*c*parseInt(a[10],10))}return b}}
-function nl(a){a=yk(a);return pl(a)}function pl(a){if(a=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(a))return parseFloat(a[1])}function ql(a){a=yk(a);return rl(a)}function rl(a){if(a=/^\s*(\d+)\s*$/.exec(a))return parseInt(a[1],10)}function M(a){a=yk(a);return ya(a)}function sl(a,b){tl(a,b?"1":"0")}function ul(a,b){a.appendChild(uk.createTextNode(b.toPrecision()))}function vl(a,b){a.appendChild(uk.createTextNode(b.toString()))}function tl(a,b){a.appendChild(uk.createTextNode(b))};function wl(a){a=l(a)?a:{};this.g=a.featureType;this.f=a.featureNS;this.a=a.srsName;this.i=l(a.surface)?a.surface:!1;this.b=l(a.curve)?a.curve:!1;this.d=l(a.multiCurve)?a.multiCurve:!0;this.e=l(a.multiSurface)?a.multiSurface:!0;this.l=l(a.schemaLocation)?a.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd";this.defaultDataProjection=null}t(wl,fl);
-function xl(a,b){var c=Ck(a),d=b[0],e=v(d,"featureType"),f;if("FeatureCollection"==c)f=L(null,yl,a,b);else if("featureMembers"==c||"featureMember"==c){f={};var g={};f[e]="featureMembers"==c?Uk(zl):Vk(zl);g[v(d,"featureNS")]=f;f=L([],g,a,b)}l(f)||(f=[]);return f}var yl={"http://www.opengis.net/gml":{featureMember:Uk(xl),featureMembers:Vk(xl)}};function il(a,b){var c=b[0],d=a.firstElementChild.getAttribute("srsName");c.srsName=d;d=L(null,Al,a,b);if(null!=d)return gk(d,!1,c)}
-function zl(a,b){var c,d=a.getAttribute("fid")||Mk(a,"http://www.opengis.net/gml","id"),e={},f;for(c=a.firstElementChild;null!==c;c=c.nextElementSibling)if(0===c.childNodes.length||1===c.childNodes.length&&3===c.firstChild.nodeType){var g=yk(c);/^[\s\xa0]*$/.test(g)&&(g=void 0);e[Ck(c)]=g}else f=Ck(c),e[f]=il(c,b);c=new G(e);l(f)&&c.e(f);d&&c.b(d);return c}function Bl(a,b){dl(Cl,a,b)}function Dl(a,b){dl(El,a,b)}function Fl(a,b){dl(Gl,a,b)}function Hl(a,b){dl(Il,a,b)}function Jl(a,b){dl(Kl,a,b)}
-function Ll(a,b){var c=Ml(a,b);if(null!=c){var d=new H(null);sj(d,"XYZ",c);return d}}function Nl(a,b){var c=L([null],Ol,a,b);if(l(c)&&null!==c[0]){var d=new I(null),e=c[0],f=[e.length],g,h;g=1;for(h=c.length;g<h;++g)Pb(e,c[g]),f.push(e.length);Lj(d,"XYZ",e,f);return d}}function Pl(a,b){var c=L([null],Ql,a,b);if(l(c)&&null!==c[0]){var d=new I(null),e=c[0],f=[e.length],g,h;g=1;for(h=c.length;g<h;++g)Pb(e,c[g]),f.push(e.length);Lj(d,"XYZ",e,f);return d}}
-function Rl(a,b){var c=L([null],Sl,a,b);if(l(c)){var d=new H(null);sj(d,"XYZ",c);return d}}function Ml(a,b){return L(null,Tl,a,b)}
-function Ul(a,b){var c=yk(a).replace(/^\s*|\s*$/g,""),d=v(b[0],"srsName"),e=a.parentNode.getAttribute("srsDimension"),f="enu";null===d||(f=ze(Be(d)));c=c.split(/\s+/);d=2;ga(a.getAttribute("srsDimension"))?ga(a.getAttribute("dimension"))?null===e||(d=rl(e)):d=rl(a.getAttribute("dimension")):d=rl(a.getAttribute("srsDimension"));for(var g,h,m=[],n=0,p=c.length;n<p;n+=d)e=parseFloat(c[n]),g=parseFloat(c[n+1]),h=3===d?parseFloat(c[n+2]):0,"en"===f.substr(0,2)?m.push(e,g,h):m.push(g,e,h);return m}
-var Al={"http://www.opengis.net/gml":{Point:Vk(function(a,b){var c=Ml(a,b);if(null!=c){var d=new yj(null);zj(d,"XYZ",c);return d}}),MultiPoint:Vk(function(a,b){var c=L([],Vl,a,b);if(l(c))return new Aj(c)}),LineString:Vk(Ll),MultiLineString:Vk(function(a,b){var c=L([],Wl,a,b);if(l(c)){var d=new uj(null);xj(d,c);return d}}),LinearRing:Vk(function(a,b){var c=Ml(a,b);if(l(c)){var d=new Dj(null);Ej(d,"XYZ",c);return d}}),Polygon:Vk(Nl),MultiPolygon:Vk(function(a,b){var c=L([],Yl,a,b);if(l(c)){var d=new Pj(null);
-Tj(d,c);return d}}),Surface:Vk(Pl),MultiSurface:Vk(function(a,b){var c=L([],Zl,a,b);if(l(c)){var d=new Pj(null);Tj(d,c);return d}}),Curve:Vk(Rl),MultiCurve:Vk(function(a,b){var c=L([],$l,a,b);if(l(c)){var d=new uj(null);xj(d,c);return d}}),Envelope:Vk(function(a,b){var c=L([null],am,a,b);return Ud(c[1][0],c[1][1],c[2][0],c[2][1])})}},Tl={"http://www.opengis.net/gml":{pos:Vk(function(a,b){for(var c=yk(a),d=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*/,e=[],f;f=d.exec(c);)e.push(parseFloat(f[1])),c=c.substr(f[0].length);
-if(""===c){c=v(b[0],"srsName");d="enu";null===c||(d=ze(Be(c)));if("neu"===d)for(c=0,d=e.length;c<d;c+=3)f=e[c],e[c]=e[c+1],e[c+1]=f;c=e.length;2==c&&e.push(0);return 0===c?void 0:e}}),posList:Vk(Ul)}},Ol={"http://www.opengis.net/gml":{interior:function(a,b){var c=L(void 0,bm,a,b);l(c)&&b[b.length-1].push(c)},exterior:function(a,b){var c=L(void 0,bm,a,b);l(c)&&(b[b.length-1][0]=c)}}},Vl={"http://www.opengis.net/gml":{pointMember:Uk(Bl),pointMembers:Uk(Bl)}},Wl={"http://www.opengis.net/gml":{lineStringMember:Uk(Dl),
-lineStringMembers:Uk(Dl)}},$l={"http://www.opengis.net/gml":{curveMember:Uk(Fl),curveMembers:Uk(Fl)}},Zl={"http://www.opengis.net/gml":{surfaceMember:Uk(Hl),surfaceMembers:Uk(Hl)}},Yl={"http://www.opengis.net/gml":{polygonMember:Uk(Jl),polygonMembers:Uk(Jl)}},Cl={"http://www.opengis.net/gml":{Point:Uk(Ml)}},El={"http://www.opengis.net/gml":{LineString:Uk(Ll)}},Gl={"http://www.opengis.net/gml":{LineString:Uk(Ll),Curve:Uk(Rl)}},Il={"http://www.opengis.net/gml":{Polygon:Uk(Nl),Surface:Uk(Pl)}},Kl={"http://www.opengis.net/gml":{Polygon:Uk(Nl)}},
-Ql={"http://www.opengis.net/gml":{patches:Vk(function(a,b){return L([null],cm,a,b)})}},Sl={"http://www.opengis.net/gml":{segments:Vk(function(a,b){return L([null],dm,a,b)})}},am={"http://www.opengis.net/gml":{lowerCorner:Uk(Ul),upperCorner:Uk(Ul)}},cm={"http://www.opengis.net/gml":{PolygonPatch:Vk(function(a,b){return L([null],Ol,a,b)})}},dm={"http://www.opengis.net/gml":{LineStringSegment:Vk(function(a,b){return L([null],Tl,a,b)})}},bm={"http://www.opengis.net/gml":{LinearRing:Vk(function(a,b){var c=
-L(null,Tl,a,b);if(null!=c)return c})}};wl.prototype.Cb=function(a,b){var c={featureType:this.g,featureNS:this.f};l(b)&&Hc(c,ek(this,a,b));return xl(a,[c])};wl.prototype.Yb=function(a){return Be(l(this.a)?this.a:a.firstElementChild.getAttribute("srsName"))};function em(a,b,c){c=v(c[c.length-1],"srsName");b=b.H();for(var d=b.length,e=Array(d),f,g=0;g<d;++g){f=b[g];var h=g,m="enu";null!=c&&(m=ze(Be(c)));e[h]="en"===m.substr(0,2)?f[0]+" "+f[1]:f[1]+" "+f[0]}tl(a,e.join(" "))}
-function fm(a,b,c){var d=v(c[c.length-1],"srsName");null!=d&&a.setAttribute("srsName",d);d=xk(a.namespaceURI,"pos");a.appendChild(d);c=v(c[c.length-1],"srsName");a="enu";null!=c&&(a=ze(Be(c)));b=b.H();tl(d,"en"===a.substr(0,2)?b[0]+" "+b[1]:b[1]+" "+b[0])}var gm={"http://www.opengis.net/gml":{lowerCorner:K(tl),upperCorner:K(tl)}};function hm(a,b,c){var d=v(c[c.length-1],"srsName");null!=d&&a.setAttribute("srsName",d);d=xk(a.namespaceURI,"posList");a.appendChild(d);em(d,b,c)}
-function im(a,b){var c=b[b.length-1],d=c.node,e=v(c,"exteriorWritten");l(e)||(c.exteriorWritten=!0);return xk(d.namespaceURI,l(e)?"interior":"exterior")}
-function jm(a,b,c){var d=v(c[c.length-1],"srsName");"PolygonPatch"!==a.nodeName&&null!=d&&a.setAttribute("srsName",d);"Polygon"===a.nodeName||"PolygonPatch"===a.nodeName?(b=b.Nc(),el({node:a,srsName:d},km,im,b,c)):"Surface"===a.nodeName&&(d=xk(a.namespaceURI,"patches"),a.appendChild(d),a=xk(d.namespaceURI,"PolygonPatch"),d.appendChild(a),jm(a,b,c))}
-function lm(a,b,c){var d=v(c[c.length-1],"srsName");"LineStringSegment"!==a.nodeName&&null!=d&&a.setAttribute("srsName",d);"LineString"===a.nodeName||"LineStringSegment"===a.nodeName?(d=xk(a.namespaceURI,"posList"),a.appendChild(d),em(d,b,c)):"Curve"===a.nodeName&&(d=xk(a.namespaceURI,"segments"),a.appendChild(d),a=xk(d.namespaceURI,"LineStringSegment"),d.appendChild(a),lm(a,b,c))}
-function mm(a,b,c){var d=c[c.length-1],e=v(d,"srsName"),d=v(d,"surface");null!=e&&a.setAttribute("srsName",e);b=b.Rc();el({node:a,srsName:e,surface:d},nm,om,b,c)}function pm(a,b,c){var d=c[c.length-1],e=v(d,"srsName"),d=v(d,"curve");null!=e&&a.setAttribute("srsName",e);b=b.oc();el({node:a,srsName:e,curve:d},qm,om,b,c)}function rm(a,b,c){var d=xk(a.namespaceURI,"LinearRing");a.appendChild(d);hm(d,b,c)}function sm(a,b,c){var d=tm(b,c);l(d)&&(a.appendChild(d),jm(d,b,c))}
-function um(a,b,c){var d=tm(b,c);l(d)&&(a.appendChild(d),lm(d,b,c))}function jl(a,b,c){var d=c[c.length-1],e=Fc(d);e.node=a;var f;ha(b)?l(d.dataProjection)?f=Ue(b,d.featureProjection,d.dataProjection):f=b:f=gk(b,!0,d);el(e,vm,tm,[f],c)}
-function wm(a,b,c){var d=b.T;l(d)&&a.setAttribute("fid",d);var d=c[c.length-1],e=v(d,"featureNS"),f=b.a;l(d.Db)||(d.Db={},d.Db[e]={});var g=b.R();b=[];var h=[],m;for(m in g){var n=g[m];null!==n&&(b.push(m),h.push(n),m==f?m in d.Db[e]||(d.Db[e][m]=K(jl)):m in d.Db[e]||(d.Db[e][m]=K(tl)))}m=Fc(d);m.node=a;el(m,d.Db,$k(void 0,e),h,c,b)}
-var nm={"http://www.opengis.net/gml":{surfaceMember:K(sm),polygonMember:K(sm)}},xm={"http://www.opengis.net/gml":{pointMember:K(function(a,b,c){var d=xk(a.namespaceURI,"Point");a.appendChild(d);fm(d,b,c)})}},qm={"http://www.opengis.net/gml":{lineStringMember:K(um),curveMember:K(um)}},km={"http://www.opengis.net/gml":{exterior:K(rm),interior:K(rm)}},vm={"http://www.opengis.net/gml":{Curve:K(lm),MultiCurve:K(pm),Point:K(fm),MultiPoint:K(function(a,b,c){var d=v(c[c.length-1],"srsName");null!=d&&a.setAttribute("srsName",
-d);b=b.Qc();el({node:a,srsName:d},xm,$k("pointMember"),b,c)}),LineString:K(lm),MultiLineString:K(pm),LinearRing:K(hm),Polygon:K(jm),MultiPolygon:K(mm),Surface:K(jm),MultiSurface:K(mm),Envelope:K(function(a,b,c){var d=v(c[c.length-1],"srsName");l(d)&&a.setAttribute("srsName",d);el({node:a},gm,bl,[b[0]+" "+b[1],b[2]+" "+b[3]],c,["lowerCorner","upperCorner"])})}},ym={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};
-function om(a,b){return xk("http://www.opengis.net/gml",ym[b[b.length-1].node.nodeName])}function tm(a,b){var c=b[b.length-1],d=v(c,"multiSurface"),e=v(c,"surface"),f=v(c,"curve"),c=v(c,"multiCurve"),g;ha(a)?g="Envelope":(g=a.G(),"MultiPolygon"===g&&!0===d?g="MultiSurface":"Polygon"===g&&!0===e?g="Surface":"LineString"===g&&!0===f?g="Curve":"MultiLineString"===g&&!0===c&&(g="MultiCurve"));return xk("http://www.opengis.net/gml",g)}
-wl.prototype.c=function(a,b){var c=xk("http://www.opengis.net/gml","featureMembers");Rk(c,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.l);var d={srsName:this.a,curve:this.b,surface:this.i,multiSurface:this.e,multiCurve:this.d,featureNS:this.f,featureType:this.g};l(b)&&Hc(d,b);var d=[d],e=d[d.length-1],f=v(e,"featureType"),g=v(e,"featureNS"),h={};h[g]={};h[g][f]=K(wm);e=Fc(e);e.node=c;el(e,h,$k(f,g),a,d);return c};function zm(a){a=l(a)?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Be("EPSG:4326");this.f=a.readExtensions}t(zm,fl);var Am=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function Bm(a,b,c){a.push(parseFloat(b.getAttribute("lon")),parseFloat(b.getAttribute("lat")));"ele"in c?(a.push(v(c,"ele")),Dc(c,"ele")):a.push(0);"time"in c?(a.push(v(c,"time")),Dc(c,"time")):a.push(0);return a}
-function Cm(a,b){var c=b[b.length-1],d=a.getAttribute("href");null!==d&&(c.link=d);dl(Dm,a,b)}function Em(a,b){b[b.length-1].extensionsNode_=a}function Fm(a,b){var c=b[0],d=L({flatCoordinates:[]},Gm,a,b);if(l(d)){var e=v(d,"flatCoordinates");Dc(d,"flatCoordinates");var f=new H(null);sj(f,"XYZM",e);gk(f,!1,c);c=new G(f);c.L(d);return c}}
-function Hm(a,b){var c=b[0],d=L({flatCoordinates:[],ends:[]},Im,a,b);if(l(d)){var e=v(d,"flatCoordinates");Dc(d,"flatCoordinates");var f=v(d,"ends");Dc(d,"ends");var g=new uj(null);vj(g,"XYZM",e,f);gk(g,!1,c);c=new G(g);c.L(d);return c}}function Jm(a,b){var c=b[0],d=L({},Km,a,b);if(l(d)){var e=Bm([],a,d),e=new yj(e,"XYZM");gk(e,!1,c);c=new G(e);c.L(d);return c}}
-var Lm={rte:Fm,trk:Hm,wpt:Jm},Mm=Xk(Am,{rte:Uk(Fm),trk:Uk(Hm),wpt:Uk(Jm)}),Dm=Xk(Am,{text:J(M,"linkText"),type:J(M,"linkType")}),Gm=Xk(Am,{name:J(M),cmt:J(M),desc:J(M),src:J(M),link:Cm,number:J(ql),extensions:Em,type:J(M),rtept:function(a,b){var c=L({},Nm,a,b);l(c)&&Bm(v(b[b.length-1],"flatCoordinates"),a,c)}}),Nm=Xk(Am,{ele:J(nl),time:J(ml)}),Im=Xk(Am,{name:J(M),cmt:J(M),desc:J(M),src:J(M),link:Cm,number:J(ql),type:J(M),extensions:Em,trkseg:function(a,b){var c=b[b.length-1];dl(Om,a,b);v(c,"ends").push(v(c,
-"flatCoordinates").length)}}),Om=Xk(Am,{trkpt:function(a,b){var c=L({},Pm,a,b);l(c)&&Bm(v(b[b.length-1],"flatCoordinates"),a,c)}}),Pm=Xk(Am,{ele:J(nl),time:J(ml)}),Km=Xk(Am,{ele:J(nl),time:J(ml),magvar:J(nl),geoidheight:J(nl),name:J(M),cmt:J(M),desc:J(M),src:J(M),link:Cm,sym:J(M),type:J(M),fix:J(M),sat:J(ql),hdop:J(nl),vdop:J(nl),pdop:J(nl),ageofdgpsdata:J(nl),dgpsid:J(ql),extensions:Em});
-function Qm(a,b){null===b&&(b=[]);for(var c=0,d=b.length;c<d;++c){var e=b[c];if(l(a.f)){var f=e.get("extensionsNode_")||null;a.f(e,f)}e.set("extensionsNode_",void 0)}}zm.prototype.De=function(a,b){if(!Za(Am,a.namespaceURI))return null;var c=Lm[a.localName];if(!l(c))return null;c=c(a,[ek(this,a,b)]);if(!l(c))return null;Qm(this,[c]);return c};zm.prototype.Cb=function(a,b){if(!Za(Am,a.namespaceURI))return[];if("gpx"==a.localName){var c=L([],Mm,a,[ek(this,a,b)]);if(l(c))return Qm(this,c),c}return[]};
-zm.prototype.wc=function(){return this.defaultDataProjection};zm.prototype.Yb=function(){return this.defaultDataProjection};function Rm(a,b,c){a.setAttribute("href",b);b=v(c[c.length-1],"properties");el({node:a},Sm,bl,[v(b,"linkText"),v(b,"linkType")],c,Tm)}
-function Um(a,b,c){var d=c[c.length-1],e=d.node.namespaceURI,f=v(d,"properties");Rk(a,null,"lat",b[1]);Rk(a,null,"lon",b[0]);switch(v(d,"geometryLayout")){case "XYZM":0!==b[3]&&(f.time=b[3]);case "XYZ":0!==b[2]&&(f.ele=b[2]);break;case "XYM":0!==b[2]&&(f.time=b[2])}b=Vm[e];d=cl(f,b);el({node:a,properties:f},Wm,bl,d,c,b)}
-var Tm=["text","type"],Sm=Yk(Am,{text:K(tl),type:K(tl)}),Xm=Yk(Am,"name cmt desc src link number type rtept".split(" ")),Ym=Yk(Am,{name:K(tl),cmt:K(tl),desc:K(tl),src:K(tl),link:K(Rm),number:K(vl),type:K(tl),rtept:Zk(K(Um))}),Zm=Yk(Am,"name cmt desc src link number type trkseg".split(" ")),bn=Yk(Am,{name:K(tl),cmt:K(tl),desc:K(tl),src:K(tl),link:K(Rm),number:K(vl),type:K(tl),trkseg:Zk(K(function(a,b,c){el({node:a,geometryLayout:b.b,properties:{}},$m,an,b.H(),c)}))}),an=$k("trkpt"),$m=Yk(Am,{trkpt:K(Um)}),
-Vm=Yk(Am,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),Wm=Yk(Am,{ele:K(ul),time:K(function(a,b){var c=new Date(1E3*b),c=c.getUTCFullYear()+"-"+Ma(c.getUTCMonth()+1)+"-"+Ma(c.getUTCDate())+"T"+Ma(c.getUTCHours())+":"+Ma(c.getUTCMinutes())+":"+Ma(c.getUTCSeconds())+"Z";a.appendChild(uk.createTextNode(c))}),magvar:K(ul),geoidheight:K(ul),name:K(tl),cmt:K(tl),desc:K(tl),src:K(tl),link:K(Rm),sym:K(tl),type:K(tl),fix:K(tl),sat:K(vl),
-hdop:K(ul),vdop:K(ul),pdop:K(ul),ageofdgpsdata:K(ul),dgpsid:K(vl)}),cn={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function dn(a,b){var c=a.J();if(l(c))return xk(b[b.length-1].node.namespaceURI,cn[c.G()])}
-var en=Yk(Am,{rte:K(function(a,b,c){var d=c[0],e=b.R();a={node:a,properties:e};b=b.J();l(b)&&(b=gk(b,!0,d),a.geometryLayout=b.b,d=b.H(),e.rtept=d);d=Xm[c[c.length-1].node.namespaceURI];e=cl(e,d);el(a,Ym,bl,e,c,d)}),trk:K(function(a,b,c){var d=c[0],e=b.R();a={node:a,properties:e};b=b.J();l(b)&&(b=gk(b,!0,d),d=b.oc(),e.trkseg=d);d=Zm[c[c.length-1].node.namespaceURI];e=cl(e,d);el(a,bn,bl,e,c,d)}),wpt:K(function(a,b,c){var d=c[0],e=c[c.length-1],f=b.R();e.properties=f;b=b.J();l(b)&&(b=gk(b,!0,d),e.geometryLayout=
-b.b,Um(a,b.H(),c))})});zm.prototype.c=function(a,b){var c=xk("http://www.topografix.com/GPX/1/1","gpx");el({node:c},en,dn,a,[b]);return c};function fn(a){a=gn(a);return Va(a,function(a){return a.b.substring(a.c,a.a)})}function hn(a,b,c){this.b=a;this.c=b;this.a=c}function gn(a){for(var b=RegExp("\r\n|\r|\n","g"),c=0,d,e=[];d=b.exec(a);)c=new hn(a,c,d.index),e.push(c),c=b.lastIndex;c<a.length&&(c=new hn(a,c,a.length),e.push(c));return e};function jn(){this.defaultDataProjection=null}t(jn,dk);k=jn.prototype;k.G=function(){return"text"};k.ob=function(a,b){return this.tc(ja(a)?a:"",fk(this,b))};k.la=function(a,b){return this.Jd(ja(a)?a:"",fk(this,b))};k.uc=function(a,b){return this.vc(ja(a)?a:"",fk(this,b))};k.ra=function(a){return this.Ld(ja(a)?a:"")};k.md=function(a,b){return this.od(a,fk(this,b))};k.Hb=function(a,b){return this.Qe(a,fk(this,b))};k.Ac=function(a,b){return this.Bc(a,fk(this,b))};function kn(a){a=l(a)?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Be("EPSG:4326");this.a=l(a.altitudeMode)?a.altitudeMode:"none"}t(kn,jn);var ln=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,mn=/^H.([A-Z]{3}).*?:(.*)/,nn=/^HFDTE(\d{2})(\d{2})(\d{2})/;
-kn.prototype.tc=function(a,b){var c=this.a,d=fn(a),e={},f=[],g=2E3,h=0,m=1,n,p;n=0;for(p=d.length;n<p;++n){var r=d[n],q;if("B"==r.charAt(0)){if(q=ln.exec(r)){var r=parseInt(q[1],10),u=parseInt(q[2],10),x=parseInt(q[3],10),B=parseInt(q[4],10)+parseInt(q[5],10)/6E4;"S"==q[6]&&(B=-B);var E=parseInt(q[7],10)+parseInt(q[8],10)/6E4;"W"==q[9]&&(E=-E);f.push(E,B);"none"!=c&&f.push("gps"==c?parseInt(q[11],10):"barometric"==c?parseInt(q[12],10):0);f.push(Date.UTC(g,h,m,r,u,x)/1E3)}}else if("H"==r.charAt(0))if(q=
-nn.exec(r))m=parseInt(q[1],10),h=parseInt(q[2],10)-1,g=2E3+parseInt(q[3],10);else if(q=mn.exec(r))e[q[1]]=ya(q[2]),nn.exec(r)}if(0===f.length)return null;d=new H(null);sj(d,"none"==c?"XYM":"XYZM",f);c=new G(gk(d,!1,b));c.L(e);return c};kn.prototype.Jd=function(a,b){var c=this.tc(a,b);return null===c?[]:[c]};kn.prototype.Ld=function(){return this.defaultDataProjection};function on(a,b){this.c={};this.a=[];this.b=0;var c=arguments.length;if(1<c){if(c%2)throw Error("Uneven number of arguments");for(var d=0;d<c;d+=2)this.set(arguments[d],arguments[d+1])}else if(a){a instanceof on?(c=a.N(),d=a.ub()):(c=xc(a),d=vc(a));for(var e=0;e<c.length;e++)this.set(c[e],d[e])}}k=on.prototype;k.fb=function(){return this.b};k.ub=function(){pn(this);for(var a=[],b=0;b<this.a.length;b++)a.push(this.c[this.a[b]]);return a};k.N=function(){pn(this);return this.a.concat()};
-k.ka=function(){return 0==this.b};k.clear=function(){this.c={};this.b=this.a.length=0};k.remove=function(a){return qn(this.c,a)?(delete this.c[a],this.b--,this.a.length>2*this.b&&pn(this),!0):!1};function pn(a){if(a.b!=a.a.length){for(var b=0,c=0;b<a.a.length;){var d=a.a[b];qn(a.c,d)&&(a.a[c++]=d);b++}a.a.length=c}if(a.b!=a.a.length){for(var e={},c=b=0;b<a.a.length;)d=a.a[b],qn(e,d)||(a.a[c++]=d,e[d]=1),b++;a.a.length=c}}k.get=function(a,b){return qn(this.c,a)?this.c[a]:b};
-k.set=function(a,b){qn(this.c,a)||(this.b++,this.a.push(a));this.c[a]=b};k.forEach=function(a,b){for(var c=this.N(),d=0;d<c.length;d++){var e=c[d],f=this.get(e);a.call(b,f,e,this)}};k.clone=function(){return new on(this)};function qn(a,b){return Object.prototype.hasOwnProperty.call(a,b)};var rn=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/;function sn(a){if(tn){tn=!1;var b=ba.location;if(b){var c=b.href;if(c&&(c=(c=sn(c)[3]||null)&&decodeURIComponent(c))&&c!=b.hostname)throw tn=!0,Error();}}return a.match(rn)}var tn=wb;function un(a){if(a[1]){var b=a[0],c=b.indexOf("#");0<=c&&(a.push(b.substr(c)),a[0]=b=b.substr(0,c));c=b.indexOf("?");0>c?a[1]="?":c==b.length-1&&(a[1]=void 0)}return a.join("")}
-function vn(a,b,c){if(ha(b))for(var d=0;d<b.length;d++)vn(a,String(b[d]),c);else null!=b&&c.push("&",a,""===b?"":"=",encodeURIComponent(String(b)))}function wn(a,b){for(var c in b)vn(c,b[c],a);return a};function xn(a,b){var c;a instanceof xn?(this.vb=l(b)?b:a.vb,yn(this,a.pb),this.Fb=a.Fb,this.Wa=a.Wa,zn(this,a.Xb),this.Ta=a.Ta,An(this,a.a.clone()),this.tb=a.tb):a&&(c=sn(String(a)))?(this.vb=!!b,yn(this,c[1]||"",!0),this.Fb=Bn(c[2]||""),this.Wa=Bn(c[3]||""),zn(this,c[4]),this.Ta=Bn(c[5]||""),An(this,c[6]||"",!0),this.tb=Bn(c[7]||"")):(this.vb=!!b,this.a=new Cn(null,0,this.vb))}k=xn.prototype;k.pb="";k.Fb="";k.Wa="";k.Xb=null;k.Ta="";k.tb="";k.vb=!1;
-k.toString=function(){var a=[],b=this.pb;b&&a.push(Dn(b,En),":");if(b=this.Wa){a.push("//");var c=this.Fb;c&&a.push(Dn(c,En),"@");a.push(encodeURIComponent(String(b)));b=this.Xb;null!=b&&a.push(":",String(b))}if(b=this.Ta)this.Wa&&"/"!=b.charAt(0)&&a.push("/"),a.push(Dn(b,"/"==b.charAt(0)?Fn:Gn));(b=this.a.toString())&&a.push("?",b);(b=this.tb)&&a.push("#",Dn(b,Hn));return a.join("")};k.clone=function(){return new xn(this)};function yn(a,b,c){a.pb=c?Bn(b):b;a.pb&&(a.pb=a.pb.replace(/:$/,""))}
-function zn(a,b){if(b){b=Number(b);if(isNaN(b)||0>b)throw Error("Bad port number "+b);a.Xb=b}else a.Xb=null}function An(a,b,c){b instanceof Cn?(a.a=b,In(a.a,a.vb)):(c||(b=Dn(b,Jn)),a.a=new Cn(b,0,a.vb))}function Kn(a){return a instanceof xn?a.clone():new xn(a,void 0)}
-function Ln(a,b){a instanceof xn||(a=Kn(a));b instanceof xn||(b=Kn(b));var c=a,d=b,e=c.clone(),f=!!d.pb;f?yn(e,d.pb):f=!!d.Fb;f?e.Fb=d.Fb:f=!!d.Wa;f?e.Wa=d.Wa:f=null!=d.Xb;var g=d.Ta;if(f)zn(e,d.Xb);else if(f=!!d.Ta)if("/"!=g.charAt(0)&&(c.Wa&&!c.Ta?g="/"+g:(c=e.Ta.lastIndexOf("/"),-1!=c&&(g=e.Ta.substr(0,c+1)+g))),c=g,".."==c||"."==c)g="";else if(La(c,"./")||La(c,"/.")){for(var g=0==c.lastIndexOf("/",0),c=c.split("/"),h=[],m=0;m<c.length;){var n=c[m++];"."==n?g&&m==c.length&&h.push(""):".."==n?((1<
-h.length||1==h.length&&""!=h[0])&&h.pop(),g&&m==c.length&&h.push("")):(h.push(n),g=!0)}g=h.join("/")}else g=c;f?e.Ta=g:f=""!==d.a.toString();f?An(e,Bn(d.a.toString())):f=!!d.tb;f&&(e.tb=d.tb);return e}function Bn(a){return a?decodeURIComponent(a):""}function Dn(a,b){return ja(a)?encodeURI(a).replace(b,Mn):null}function Mn(a){a=a.charCodeAt(0);return"%"+(a>>4&15).toString(16)+(a&15).toString(16)}var En=/[#\/\?@]/g,Gn=/[\#\?:]/g,Fn=/[\#\?]/g,Jn=/[\#\?@]/g,Hn=/#/g;
-function Cn(a,b,c){this.a=a||null;this.c=!!c}function Nn(a){if(!a.ca&&(a.ca=new on,a.ja=0,a.a))for(var b=a.a.split("&"),c=0;c<b.length;c++){var d=b[c].indexOf("="),e=null,f=null;0<=d?(e=b[c].substring(0,d),f=b[c].substring(d+1)):e=b[c];e=decodeURIComponent(e.replace(/\+/g," "));e=On(a,e);a.add(e,f?decodeURIComponent(f.replace(/\+/g," ")):"")}}k=Cn.prototype;k.ca=null;k.ja=null;k.fb=function(){Nn(this);return this.ja};
-k.add=function(a,b){Nn(this);this.a=null;a=On(this,a);var c=this.ca.get(a);c||this.ca.set(a,c=[]);c.push(b);this.ja++;return this};k.remove=function(a){Nn(this);a=On(this,a);return qn(this.ca.c,a)?(this.a=null,this.ja-=this.ca.get(a).length,this.ca.remove(a)):!1};k.clear=function(){this.ca=this.a=null;this.ja=0};k.ka=function(){Nn(this);return 0==this.ja};function Pn(a,b){Nn(a);b=On(a,b);return qn(a.ca.c,b)}
-k.N=function(){Nn(this);for(var a=this.ca.ub(),b=this.ca.N(),c=[],d=0;d<b.length;d++)for(var e=a[d],f=0;f<e.length;f++)c.push(b[d]);return c};k.ub=function(a){Nn(this);var b=[];if(ja(a))Pn(this,a)&&(b=bb(b,this.ca.get(On(this,a))));else{a=this.ca.ub();for(var c=0;c<a.length;c++)b=bb(b,a[c])}return b};k.set=function(a,b){Nn(this);this.a=null;a=On(this,a);Pn(this,a)&&(this.ja-=this.ca.get(a).length);this.ca.set(a,[b]);this.ja++;return this};
-k.get=function(a,b){var c=a?this.ub(a):[];return 0<c.length?String(c[0]):b};function Qn(a,b,c){a.remove(b);0<c.length&&(a.a=null,a.ca.set(On(a,b),cb(c)),a.ja+=c.length)}k.toString=function(){if(this.a)return this.a;if(!this.ca)return"";for(var a=[],b=this.ca.N(),c=0;c<b.length;c++)for(var d=b[c],e=encodeURIComponent(String(d)),d=this.ub(d),f=0;f<d.length;f++){var g=e;""!==d[f]&&(g+="="+encodeURIComponent(String(d[f])));a.push(g)}return this.a=a.join("&")};
-k.clone=function(){var a=new Cn;a.a=this.a;this.ca&&(a.ca=this.ca.clone(),a.ja=this.ja);return a};function On(a,b){var c=String(b);a.c&&(c=c.toLowerCase());return c}function In(a,b){b&&!a.c&&(Nn(a),a.a=null,a.ca.forEach(function(a,b){var e=b.toLowerCase();b!=e&&(this.remove(b),Qn(this,e,a))},a));a.c=b};function Rn(a){a=l(a)?a:{};this.e=l(a.anchor)?a.anchor:[.5,.5];this.d=null;this.c=l(a.anchorOrigin)?a.anchorOrigin:"top-left";this.p=l(a.anchorXUnits)?a.anchorXUnits:"fraction";this.o=l(a.anchorYUnits)?a.anchorYUnits:"fraction";var b=l(a.crossOrigin)?a.crossOrigin:null,c=l(a.img)?a.img:null,d=a.src;l(d)&&0!==d.length||null===c||(d=c.src);var e=l(a.src)?0:2,f=Sn.gb(),g=f.get(d,b);null===g&&(g=new Tn(c,d,b,e),f.set(d,b,g));this.a=g;this.F=l(a.offset)?a.offset:[0,0];this.b=l(a.offsetOrigin)?a.offsetOrigin:
-"top-left";this.f=null;this.q=l(a.size)?a.size:null;Ii.call(this,{opacity:l(a.opacity)?a.opacity:1,rotation:l(a.rotation)?a.rotation:0,scale:l(a.scale)?a.scale:1,snapToPixel:l(a.snapToPixel)?a.snapToPixel:!0,rotateWithView:l(a.rotateWithView)?a.rotateWithView:!1})}t(Rn,Ii);k=Rn.prototype;
-k.Pb=function(){if(null!==this.d)return this.d;var a=this.e,b=this.lb();if("fraction"==this.p||"fraction"==this.o){if(null===b)return null;a=this.e.slice();"fraction"==this.p&&(a[0]*=b[0]);"fraction"==this.o&&(a[1]*=b[1])}if("top-left"!=this.c){if(null===b)return null;a===this.e&&(a=this.e.slice());if("top-right"==this.c||"bottom-right"==this.c)a[0]=-a[0]+b[0];if("bottom-left"==this.c||"bottom-right"==this.c)a[1]=-a[1]+b[1]}return this.d=a};k.sc=function(){return this.a.a};k.ve=function(){return this.a.b};
-k.ue=function(){var a=this.a;if(null===a.e)if(a.i){var b=a.c[0],c=a.c[1],d=lg(b,c);d.fillRect(0,0,b,c);a.e=d.canvas}else a.e=a.a;return a.e};k.Wb=function(){if(null!==this.f)return this.f;var a=this.F;if("top-left"!=this.b){var b=this.lb(),c=this.a.c;if(null===b||null===c)return null;a=a.slice();if("top-right"==this.b||"bottom-right"==this.b)a[0]=c[0]-b[0]-a[0];if("bottom-left"==this.b||"bottom-right"==this.b)a[1]=c[1]-b[1]-a[1]}return this.f=a};k.Ch=function(){return this.a.f};
-k.lb=function(){return null===this.q?this.a.c:this.q};k.fe=function(a,b){return y(this.a,"change",a,!1,b)};k.we=function(){var a=this.a;if(0==a.b){a.b=1;a.d=[Wc(a.a,"error",a.g,!1,a),Wc(a.a,"load",a.l,!1,a)];try{a.a.src=a.f}catch(b){a.g()}}};k.Ne=function(a,b){Xc(this.a,"change",a,!1,b)};function Tn(a,b,c,d){hd.call(this);this.e=null;this.a=null===a?new Image:a;null!==c&&(this.a.crossOrigin=c);this.d=null;this.b=d;this.c=null;this.f=b;this.i=!1}t(Tn,hd);
-Tn.prototype.g=function(){this.b=3;Ta(this.d,Yc);this.d=null;this.dispatchEvent("change")};Tn.prototype.l=function(){this.b=2;this.c=[this.a.width,this.a.height];Ta(this.d,Yc);this.d=null;var a=lg(1,1);a.drawImage(this.a,0,0);try{a.getImageData(0,0,1,1)}catch(b){this.i=!0}this.dispatchEvent("change")};function Sn(){this.a={};this.c=0}da(Sn);Sn.prototype.clear=function(){this.a={};this.c=0};Sn.prototype.get=function(a,b){var c=b+":"+a;return c in this.a?this.a[c]:null};
-Sn.prototype.set=function(a,b,c){this.a[b+":"+a]=c;++this.c};function Un(a){a=l(a)?a:{};this.d=a.font;this.g=a.rotation;this.c=a.scale;this.b=a.text;this.l=a.textAlign;this.k=a.textBaseline;this.a=l(a.fill)?a.fill:null;this.i=l(a.stroke)?a.stroke:null;this.e=l(a.offsetX)?a.offsetX:0;this.f=l(a.offsetY)?a.offsetY:0}k=Un.prototype;k.rf=function(){return this.d};k.Ef=function(){return this.e};k.Ff=function(){return this.f};k.Kh=function(){return this.a};k.Lh=function(){return this.g};k.Mh=function(){return this.c};k.Nh=function(){return this.i};k.Oh=function(){return this.b};
-k.Kf=function(){return this.l};k.Lf=function(){return this.k};function Vn(a){function b(a){return ha(a)?a:ja(a)?(!(a in d)&&"#"+a in d&&(a="#"+a),b(d[a])):c}a=l(a)?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Be("EPSG:4326");var c=l(a.defaultStyle)?a.defaultStyle:Wn,d={};this.g=l(a.extractStyles)?a.extractStyles:!0;this.f=d;this.l=function(){var a=this.get("Style");if(l(a))return a;a=this.get("styleUrl");return l(a)?b(a):c}}t(Vn,fl);
-var Xn=["http://www.google.com/kml/ext/2.2"],Yn=[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"],Zn=[255,255,255,1],$n=new Hi({color:Zn}),ao=[2,20],bo=[32,32],co=new Rn({anchor:ao,anchorXUnits:"pixels",anchorYUnits:"pixels",crossOrigin:"anonymous",rotation:0,scale:1,size:bo,src:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"}),eo=new Ji({color:Zn,width:1}),Wn=[new Li({fill:$n,image:co,text:null,
-stroke:eo,zIndex:0})],fo={fraction:"fraction",pixels:"pixels"};function go(a){a=yk(a);if(a=/^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(a))return a=a[1],[parseInt(a.substr(6,2),16),parseInt(a.substr(4,2),16),parseInt(a.substr(2,2),16),parseInt(a.substr(0,2),16)/255]}
-function ho(a){a=yk(a);for(var b=[],c=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i,d;d=c.exec(a);)b.push(parseFloat(d[1]),parseFloat(d[2]),d[3]?parseFloat(d[3]):0),a=a.substr(d[0].length);return""!==a?void 0:b}function io(a){var b=yk(a);return null!=a.baseURI?Ln(a.baseURI,ya(b)).toString():ya(b)}function jo(a,b){return L(null,ko,a,b)}
-function lo(a,b){var c=L({j:[],Pe:[]},mo,a,b);if(l(c)){var d=c.j,c=c.Pe,e,f;e=0;for(f=Math.min(d.length,c.length);e<f;++e)d[4*e+3]=c[e];c=new H(null);sj(c,"XYZM",d);return c}}function no(a,b){var c=L(null,oo,a,b);if(l(c)){var d=new H(null);sj(d,"XYZ",c);return d}}function po(a,b){var c=L(null,oo,a,b);if(l(c)){var d=new I(null);Lj(d,"XYZ",c,[c.length]);return d}}
-function qo(a,b){var c=L([],ro,a,b);if(!l(c))return null;if(0===c.length)return new aj(c);var d=!0,e=c[0].G(),f,g,h;g=1;for(h=c.length;g<h;++g)if(f=c[g],f.G()!=e){d=!1;break}if(d){if("Point"==e){f=c[0];d=f.b;e=f.j;g=1;for(h=c.length;g<h;++g)f=c[g],Pb(e,f.j);c=new Aj(null);Ui(c,d,e);c.n();return c}return"LineString"==e?(f=new uj(null),xj(f,c),f):"Polygon"==e?(f=new Pj(null),Tj(f,c),f):"GeometryCollection"==e?new aj(c):null}return new aj(c)}
-function so(a,b){var c=L(null,oo,a,b);if(null!=c){var d=new yj(null);zj(d,"XYZ",c);return d}}function to(a,b){var c=L([null],uo,a,b);if(null!=c&&null!==c[0]){var d=new I(null),e=c[0],f=[e.length],g,h;g=1;for(h=c.length;g<h;++g)Pb(e,c[g]),f.push(e.length);Lj(d,"XYZ",e,f);return d}}
-function vo(a,b){var c=L({},wo,a,b);if(!l(c))return null;var d=v(c,"fillStyle",$n),e=v(c,"fill");l(e)&&!e&&(d=null);var e=v(c,"imageStyle",co),f=v(c,"strokeStyle",eo),c=v(c,"outline");l(c)&&!c&&(f=null);return[new Li({fill:d,image:e,stroke:f,text:null,zIndex:void 0})]}
-var xo=Xk(Yn,{value:Vk(M)}),zo=Xk(Yn,{Data:function(a,b){var c=a.getAttribute("name");if(null!==c){var d=L(void 0,xo,a,b);l(d)&&(b[b.length-1][c]=d)}},SchemaData:function(a,b){dl(yo,a,b)}}),ko=Xk(Yn,{coordinates:Vk(ho)}),uo=Xk(Yn,{innerBoundaryIs:function(a,b){var c=L(void 0,Ao,a,b);l(c)&&b[b.length-1].push(c)},outerBoundaryIs:function(a,b){var c=L(void 0,Bo,a,b);l(c)&&(b[b.length-1][0]=c)}}),mo=Xk(Yn,{when:function(a,b){var c=b[b.length-1].Pe,d=yk(a);if(d=/^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/.exec(d)){var e=
-Date.UTC(parseInt(d[1],10),l(d[3])?parseInt(d[3],10)-1:0,l(d[5])?parseInt(d[5],10):1,l(d[7])?parseInt(d[7],10):0,l(d[8])?parseInt(d[8],10):0,l(d[9])?parseInt(d[9],10):0);if(l(d[10])&&"Z"!=d[10]){var f="-"==d[11]?-1:1,e=e+60*f*parseInt(d[12],10);l(d[13])&&(e+=3600*f*parseInt(d[13],10))}c.push(e)}else c.push(0)}},Xk(Xn,{coord:function(a,b){var c=b[b.length-1].j,d=yk(a);(d=/^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i.exec(d))?
-c.push(parseFloat(d[1]),parseFloat(d[2]),parseFloat(d[3]),0):c.push(0,0,0,0)}})),oo=Xk(Yn,{coordinates:Vk(ho)}),Co=Xk(Yn,{href:J(io)},Xk(Xn,{x:J(nl),y:J(nl),w:J(nl),h:J(nl)})),Do=Xk(Yn,{Icon:J(function(a,b){var c=L({},Co,a,b);return l(c)?c:null}),heading:J(nl),hotSpot:J(function(a){var b=a.getAttribute("xunits"),c=a.getAttribute("yunits");return{x:parseFloat(a.getAttribute("x")),Qd:fo[b],y:parseFloat(a.getAttribute("y")),Rd:fo[c]}}),scale:J(function(a){a=nl(a);if(l(a))return Math.sqrt(a)})}),Ao=Xk(Yn,
-{LinearRing:Vk(jo)}),Eo=Xk(Yn,{color:J(go),width:J(nl)}),ro=Xk(Yn,{LineString:Uk(no),LinearRing:Uk(po),MultiGeometry:Uk(qo),Point:Uk(so),Polygon:Uk(to)}),Fo=Xk(Xn,{Track:Uk(lo)}),Bo=Xk(Yn,{LinearRing:Vk(jo)}),Go=Xk(Yn,{Style:J(vo),key:J(M),styleUrl:J(function(a){var b=ya(yk(a));return null!=a.baseURI?Ln(a.baseURI,b).toString():b})}),Io=Xk(Yn,{ExtendedData:function(a,b){dl(zo,a,b)},MultiGeometry:J(qo,"geometry"),LineString:J(no,"geometry"),LinearRing:J(po,"geometry"),Point:J(so,"geometry"),Polygon:J(to,
-"geometry"),Style:J(vo),StyleMap:function(a,b){var c=L(void 0,Ho,a,b);if(l(c)){var d=b[b.length-1];ha(c)?d.Style=c:ja(c)&&(d.styleUrl=c)}},address:J(M),description:J(M),name:J(M),open:J(kl),phoneNumber:J(M),styleUrl:J(io),visibility:J(kl)},Xk(Xn,{MultiTrack:J(function(a,b){var c=L([],Fo,a,b);if(l(c)){var d=new uj(null);xj(d,c);return d}},"geometry"),Track:J(lo,"geometry")})),Jo=Xk(Yn,{color:J(go),fill:J(kl),outline:J(kl)}),yo=Xk(Yn,{SimpleData:function(a,b){var c=a.getAttribute("name");if(null!==
-c){var d=M(a);b[b.length-1][c]=d}}}),wo=Xk(Yn,{IconStyle:function(a,b){var c=L({},Do,a,b);if(l(c)){var d=b[b.length-1],e=v(c,"Icon",{}),f;f=v(e,"href");f=l(f)?f:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";var g,h,m,n=v(c,"hotSpot");l(n)?(g=[n.x,n.y],h=n.Qd,m=n.Rd):"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"===f?(g=ao,m=h="pixels"):/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(f)&&(g=[.5,0],m=h="fraction");var p,n=v(e,"x"),r=v(e,"y");l(n)&&l(r)&&(p=[n,r]);var q,
-n=v(e,"w"),e=v(e,"h");l(n)&&l(e)&&(q=[n,e]);var u,e=v(c,"heading");l(e)&&(u=Mb(e));c=v(c,"scale");"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"==f&&(q=bo);g=new Rn({anchor:g,anchorOrigin:"bottom-left",anchorXUnits:h,anchorYUnits:m,crossOrigin:"anonymous",offset:p,offsetOrigin:"bottom-left",rotation:u,scale:c,size:q,src:f});d.imageStyle=g}},LineStyle:function(a,b){var c=L({},Eo,a,b);l(c)&&(b[b.length-1].strokeStyle=new Ji({color:v(c,"color",Zn),width:v(c,"width",1)}))},PolyStyle:function(a,
-b){var c=L({},Jo,a,b);if(l(c)){var d=b[b.length-1];d.fillStyle=new Hi({color:v(c,"color",Zn)});var e=v(c,"fill");l(e)&&(d.fill=e);c=v(c,"outline");l(c)&&(d.outline=c)}}}),Ho=Xk(Yn,{Pair:function(a,b){var c=L({},Go,a,b);if(l(c)){var d=v(c,"key");l(d)&&"normal"==d&&(d=v(c,"styleUrl"),l(d)&&(b[b.length-1]=d),c=v(c,"Style"),l(c)&&(b[b.length-1]=c))}}});k=Vn.prototype;
-k.Ce=function(a,b){Ck(a);var c=Xk(Yn,{Folder:Tk(this.Ce,this),Placemark:Uk(this.Kd,this),Style:sa(this.ki,this),StyleMap:sa(this.ji,this)}),c=L([],c,a,b,this);if(l(c))return c};k.Kd=function(a,b){var c=L({geometry:null},Io,a,b);if(l(c)){var d=new G,e=a.getAttribute("id");null===e||d.b(e);e=b[0];null!=c.geometry&&gk(c.geometry,!1,e);d.L(c);this.g&&d.i(this.l);return d}};
-k.ki=function(a,b){var c=a.getAttribute("id");if(null!==c){var d=vo(a,b);l(d)&&(c=null!=a.baseURI?Ln(a.baseURI,"#"+c).toString():"#"+c,this.f[c]=d)}};k.ji=function(a,b){var c=a.getAttribute("id");if(null!==c){var d=L(void 0,Ho,a,b);l(d)&&(c=null!=a.baseURI?Ln(a.baseURI,"#"+c).toString():"#"+c,this.f[c]=d)}};k.De=function(a,b){if(!Za(Yn,a.namespaceURI))return null;var c=this.Kd(a,[ek(this,a,b)]);return l(c)?c:null};
-k.Cb=function(a,b){if(!Za(Yn,a.namespaceURI))return[];var c;c=Ck(a);if("Document"==c||"Folder"==c)return c=this.Ce(a,[ek(this,a,b)]),l(c)?c:[];if("Placemark"==c)return c=this.Kd(a,[ek(this,a,b)]),l(c)?[c]:[];if("kml"==c){c=[];var d;for(d=a.firstElementChild;null!==d;d=d.nextElementSibling){var e=this.Cb(d,b);l(e)&&db(c,e)}return c}return[]};k.ii=function(a){if(Fk(a))return Ko(this,a);if(Ik(a))return Lo(this,a);if(ja(a))return a=Sk(a),Ko(this,a)};
-function Ko(a,b){var c;for(c=b.firstChild;null!==c;c=c.nextSibling)if(1==c.nodeType){var d=Lo(a,c);if(l(d))return d}}function Lo(a,b){var c;for(c=b.firstElementChild;null!==c;c=c.nextElementSibling)if(Za(Yn,c.namespaceURI)&&"name"==c.localName)return M(c);for(c=b.firstElementChild;null!==c;c=c.nextElementSibling){var d=Ck(c);if(Za(Yn,c.namespaceURI)&&("Document"==d||"Folder"==d||"Placemark"==d||"kml"==d)&&(d=Lo(a,c),l(d)))return d}}k.wc=function(){return this.defaultDataProjection};k.Yb=function(){return this.defaultDataProjection};
-function Mo(a,b){var c=Hg(b),c=[255*(4==c.length?c[3]:1),c[2],c[1],c[0]],d;for(d=0;4>d;++d){var e=parseInt(c[d],10).toString(16);c[d]=1==e.length?"0"+e:e}tl(a,c.join(""))}function No(a,b,c){el({node:a},Oo,Po,[b],c)}
-function Qo(a,b,c){var d={node:a};null!=b.T&&a.setAttribute("id",b.T);a=b.R();var e=b.d;l(e)&&(e=e.call(b,0),null!==e&&0<e.length&&(a.Style=e[0],e=e[0].c,null!==e&&(a.name=e.b)));e=Ro[c[c.length-1].node.namespaceURI];a=cl(a,e);el(d,So,bl,a,c,e);a=c[0];b=b.J();null!=b&&(b=gk(b,!0,a));el(d,So,To,[b],c)}function Uo(a,b,c){var d=b.j;a={node:a};a.layout=b.b;a.stride=b.a;el(a,Vo,Wo,[d],c)}function Xo(a,b,c){b=b.Nc();var d=b.shift();a={node:a};el(a,Yo,Zo,b,c);el(a,Yo,$o,[d],c)}
-function ap(a,b){ul(a,b*b)}
-var bp=Yk(Yn,["Document","Placemark"]),ep=Yk(Yn,{Document:K(function(a,b,c){el({node:a},cp,dp,b,c)}),Placemark:K(Qo)}),cp=Yk(Yn,{Placemark:K(Qo)}),fp={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry"},gp=Yk(Yn,["href"],Yk(Xn,["x","y","w","h"])),hp=Yk(Yn,{href:K(tl)},Yk(Xn,{x:K(ul),y:K(ul),w:K(ul),h:K(ul)})),ip=Yk(Yn,["scale","heading","Icon","hotSpot"]),kp=Yk(Yn,{Icon:K(function(a,
-b,c){a={node:a};var d=gp[c[c.length-1].node.namespaceURI],e=cl(b,d);el(a,hp,bl,e,c,d);d=gp[Xn[0]];e=cl(b,d);el(a,hp,jp,e,c,d)}),heading:K(ul),hotSpot:K(function(a,b){a.setAttribute("x",b.x);a.setAttribute("y",b.y);a.setAttribute("xunits",b.Qd);a.setAttribute("yunits",b.Rd)}),scale:K(ap)}),lp=Yk(Yn,["color","scale"]),mp=Yk(Yn,{color:K(Mo),scale:K(ap)}),np=Yk(Yn,["color","width"]),op=Yk(Yn,{color:K(Mo),width:K(ul)}),Oo=Yk(Yn,{LinearRing:K(Uo)}),pp=Yk(Yn,{LineString:K(Uo),Point:K(Uo),Polygon:K(Xo)}),
-Ro=Yk(Yn,"name open visibility address phoneNumber description styleUrl Style".split(" ")),So=Yk(Yn,{MultiGeometry:K(function(a,b,c){a={node:a};var d=b.G(),e,f;"MultiPoint"==d?(e=b.Qc(),f=qp):"MultiLineString"==d?(e=b.oc(),f=rp):"MultiPolygon"==d&&(e=b.Rc(),f=sp);el(a,pp,f,e,c)}),LineString:K(Uo),LinearRing:K(Uo),Point:K(Uo),Polygon:K(Xo),Style:K(function(a,b,c){a={node:a};var d={},e=b.d,f=b.b,g=b.e;b=b.c;null!==g&&(d.IconStyle=g);null!==b&&(d.LabelStyle=b);null!==f&&(d.LineStyle=f);null!==e&&(d.PolyStyle=
-e);b=tp[c[c.length-1].node.namespaceURI];d=cl(d,b);el(a,up,bl,d,c,b)}),address:K(tl),description:K(tl),name:K(tl),open:K(sl),phoneNumber:K(tl),styleUrl:K(tl),visibility:K(sl)}),Vo=Yk(Yn,{coordinates:K(function(a,b,c){c=c[c.length-1];var d=v(c,"layout");c=v(c,"stride");var e;"XY"==d||"XYM"==d?e=2:("XYZ"==d||"XYZM"==d)&&(e=3);var f,g=b.length,h="";if(0<g){h+=b[0];for(d=1;d<e;++d)h+=","+b[d];for(f=c;f<g;f+=c)for(h+=" "+b[f],d=1;d<e;++d)h+=","+b[f+d]}tl(a,h)})}),Yo=Yk(Yn,{outerBoundaryIs:K(No),innerBoundaryIs:K(No)}),
-vp=Yk(Yn,{color:K(Mo)}),tp=Yk(Yn,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),up=Yk(Yn,{IconStyle:K(function(a,b,c){a={node:a};var d={},e=b.lb(),f=b.a.c,g={href:b.a.f};if(null!==e){g.w=e[0];g.h=e[1];var h=b.Pb(),m=b.Wb();null!==m&&null!==f&&0!==m[0]&&m[1]!==e[1]&&(g.x=m[0],g.y=f[1]-(m[1]+e[1]));null!==h&&0!==h[0]&&h[1]!==e[1]&&(d.hotSpot={x:h[0],Qd:"pixels",y:e[1]-h[1],Rd:"pixels"})}d.Icon=g;e=b.i;1!==e&&(d.scale=e);b=b.g;0!==b&&(d.heading=b);b=ip[c[c.length-1].node.namespaceURI];d=cl(d,b);
-el(a,kp,bl,d,c,b)}),LabelStyle:K(function(a,b,c){a={node:a};var d={},e=b.a;null!==e&&(d.color=e.a);b=b.c;l(b)&&1!==b&&(d.scale=b);b=lp[c[c.length-1].node.namespaceURI];d=cl(d,b);el(a,mp,bl,d,c,b)}),LineStyle:K(function(a,b,c){a={node:a};var d=np[c[c.length-1].node.namespaceURI];b=cl({color:b.a,width:b.c},d);el(a,op,bl,b,c,d)}),PolyStyle:K(function(a,b,c){el({node:a},vp,wp,[b.a],c)})});function jp(a,b,c){return xk(Xn[0],"gx:"+c)}
-function dp(a,b){return xk(b[b.length-1].node.namespaceURI,"Placemark")}function To(a,b){if(null!=a)return xk(b[b.length-1].node.namespaceURI,fp[a.G()])}var wp=$k("color"),Wo=$k("coordinates"),Zo=$k("innerBoundaryIs"),qp=$k("Point"),rp=$k("LineString"),Po=$k("LinearRing"),sp=$k("Polygon"),$o=$k("outerBoundaryIs");
-Vn.prototype.c=function(a,b){var c=xk(Yn[4],"kml");Rk(c,"http://www.w3.org/2000/xmlns/","xmlns:gx",Xn[0]);Rk(c,"http://www.w3.org/2000/xmlns/","xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");Rk(c,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation","http://www.opengis.net/kml/2.2 https://developers.google.com/kml/schema/kml22gx.xsd");var d={node:c},e={};1<a.length?e.Document=a:1==a.length&&(e.Placemark=a[0]);var f=bp[c.namespaceURI],e=cl(e,f);el(d,ep,bl,e,[b],f);return c};function xp(){this.defaultDataProjection=null;this.defaultDataProjection=Be("EPSG:4326")}t(xp,fl);function yp(a,b){var c=a.getAttribute("k"),d=a.getAttribute("v");b[b.length-1].yc[c]=d}
-var zp=[null],Ap=Xk(zp,{nd:function(a,b){b[b.length-1].Ub.push(a.getAttribute("ref"))},tag:yp}),Cp=Xk(zp,{node:function(a,b){var c=b[0],d=b[b.length-1],e=a.getAttribute("id"),f=[parseFloat(a.getAttribute("lon")),parseFloat(a.getAttribute("lat"))];d.ie[e]=f;var g=L({yc:{}},Bp,a,b);Bc(g.yc)||(f=new yj(f),gk(f,!1,c),c=new G(f),c.b(e),c.L(g.yc),d.features.push(c))},way:function(a,b){for(var c=b[0],d=a.getAttribute("id"),e=L({Ub:[],yc:{}},Ap,a,b),f=b[b.length-1],g=[],h=0,m=e.Ub.length;h<m;h++)db(g,v(f.ie,
-e.Ub[h]));e.Ub[0]==e.Ub[e.Ub.length-1]?(h=new I(null),Lj(h,"XY",g,[g.length])):(h=new H(null),sj(h,"XY",g));gk(h,!1,c);c=new G(h);c.b(d);c.L(e.yc);f.features.push(c)}}),Bp=Xk(zp,{tag:yp});xp.prototype.Cb=function(a,b){var c=ek(this,a,b);return"osm"==a.localName&&(c=L({ie:{},features:[]},Cp,a,[c]),l(c.features))?c.features:[]};xp.prototype.wc=function(){return this.defaultDataProjection};xp.prototype.Yb=function(){return this.defaultDataProjection};function Dp(a){return a.getAttributeNS("http://www.w3.org/1999/xlink","href")};function Ep(){}Ep.prototype.a=function(a){return Fk(a)?Fp(this,a):Ik(a)?Gp(this,a):ja(a)?(a=Sk(a),Fp(this,a)):null};function Hp(a){a=l(a)?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Be("EPSG:4326");this.a=l(a.factor)?a.factor:1E5}t(Hp,jn);function Ip(a,b,c){c=l(c)?c:1E5;var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;var f,g;f=0;for(g=a.length;f<g;)for(d=0;d<b;++d,++f){var h=a[f],m=h-e[d];e[d]=h;a[f]=m}return Jp(a,c)}function Kp(a,b,c){var d=l(c)?c:1E5,e=Array(b);for(c=0;c<b;++c)e[c]=0;a=Lp(a,d);var f,d=0;for(f=a.length;d<f;)for(c=0;c<b;++c,++d)e[c]+=a[d],a[d]=e[c];return a}
-function Jp(a,b){var c=l(b)?b:1E5,d,e;d=0;for(e=a.length;d<e;++d)a[d]=Math.round(a[d]*c);c=0;for(d=a.length;c<d;++c)e=a[c],a[c]=0>e?~(e<<1):e<<1;c="";d=0;for(e=a.length;d<e;++d){for(var f=a[d],g=void 0,h="";32<=f;)g=(32|f&31)+63,h+=String.fromCharCode(g),f>>=5;g=f+63;h+=String.fromCharCode(g);c+=h}return c}
-function Lp(a,b){var c=l(b)?b:1E5,d=[],e=0,f=0,g,h;g=0;for(h=a.length;g<h;++g){var m=a.charCodeAt(g)-63,e=e|(m&31)<<f;32>m?(d.push(e),f=e=0):f+=5}e=0;for(f=d.length;e<f;++e)g=d[e],d[e]=g&1?~(g>>1):g>>1;e=0;for(f=d.length;e<f;++e)d[e]/=c;return d}k=Hp.prototype;k.tc=function(a,b){var c=this.vc(a,b);return new G(c)};k.Jd=function(a,b){return[this.tc(a,b)]};k.vc=function(a,b){var c=Kp(a,2,this.a),c=lj(c,0,c.length,2);return gk(new H(c),!1,fk(this,b))};k.Ld=function(){return this.defaultDataProjection};
-k.od=function(a,b){var c=a.J();return null!=c?this.Bc(c,b):""};k.Qe=function(a,b){return this.od(a[0],b)};k.Bc=function(a,b){a=gk(a,!0,fk(this,b));return Ip(a.j,a.a,this.a)};function Mp(a){a=l(a)?a:{};this.defaultDataProjection=null;this.defaultDataProjection=Be(null!=a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326")}t(Mp,ik);function Np(a,b){var c=[],d,e,f,g;f=0;for(g=a.length;f<g;++f)d=a[f],0<f&&c.pop(),0<=d?e=b[d]:e=b[~d].slice().reverse(),c.push.apply(c,e);d=0;for(e=c.length;d<e;++d)c[d]=c[d].slice();return c}function Op(a,b,c,d,e){a=a.geometries;var f=[],g,h;g=0;for(h=a.length;g<h;++g)f[g]=Pp(a[g],b,c,d,e);return f}
-function Pp(a,b,c,d,e){var f=a.type,g=Qp[f];b="Point"===f||"MultiPoint"===f?g(a,c,d):g(a,b);c=new G;c.Ba(gk(b,!1,e));l(a.id)&&c.b(a.id);l(a.properties)&&c.L(a.properties);return c}
-Mp.prototype.c=function(a,b){if("Topology"==a.type){var c,d=null,e=null;l(a.transform)&&(c=a.transform,d=c.scale,e=c.translate);var f=a.arcs;if(l(c)){c=d;var g=e,h,m;h=0;for(m=f.length;h<m;++h)for(var n=f[h],p=c,r=g,q=0,u=0,x=void 0,B=void 0,E=void 0,B=0,E=n.length;B<E;++B)x=n[B],q+=x[0],u+=x[1],x[0]=q,x[1]=u,Rp(x,p,r)}c=[];g=vc(a.objects);h=0;for(m=g.length;h<m;++h)"GeometryCollection"===g[h].type?(n=g[h],c.push.apply(c,Op(n,f,d,e,b))):(n=g[h],c.push(Pp(n,f,d,e,b)));return c}return[]};
-function Rp(a,b,c){a[0]=a[0]*b[0]+c[0];a[1]=a[1]*b[1]+c[1]}Mp.prototype.ra=function(){return this.defaultDataProjection};
-var Qp={Point:function(a,b,c){a=a.coordinates;null===b||null===c||Rp(a,b,c);return new yj(a)},LineString:function(a,b){var c=Np(a.arcs,b);return new H(c)},Polygon:function(a,b){var c=[],d,e;d=0;for(e=a.arcs.length;d<e;++d)c[d]=Np(a.arcs[d],b);return new I(c)},MultiPoint:function(a,b,c){a=a.coordinates;var d,e;if(null!==b&&null!==c)for(d=0,e=a.length;d<e;++d)Rp(a[d],b,c);return new Aj(a)},MultiLineString:function(a,b){var c=[],d,e;d=0;for(e=a.arcs.length;d<e;++d)c[d]=Np(a.arcs[d],b);return new uj(c)},
-MultiPolygon:function(a,b){var c=[],d,e,f,g,h,m;h=0;for(m=a.arcs.length;h<m;++h){d=a.arcs[h];e=[];f=0;for(g=d.length;f<g;++f)e[f]=Np(d[f],b);c[h]=e}return new Pj(c)}};function Sp(a){a=l(a)?a:{};this.l=a.featureType;this.f=a.featureNS;this.g=l(a.schemaLocation)?a.schemaLocation:"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";this.defaultDataProjection=null}t(Sp,fl);Sp.prototype.Cb=function(a,b){var c={featureType:this.l,featureNS:this.f};Hc(c,ek(this,a,l(b)?b:{}));c=L([],yl,a,[c]);l(c)||(c=[]);return c};Sp.prototype.o=function(a){if(Fk(a))return Tp(a);if(Ik(a))return L({},Up,a,[]);if(ja(a))return a=Sk(a),Tp(a)};
-Sp.prototype.k=function(a){if(Fk(a))return Vp(a);if(Ik(a))return Wp(a);if(ja(a))return a=Sk(a),Vp(a)};function Vp(a){for(a=a.firstChild;null!==a;a=a.nextSibling)if(1==a.nodeType)return Wp(a)}var Xp={"http://www.opengis.net/gml":{boundedBy:J(il,"bounds")}};function Wp(a){var b={},c=rl(a.getAttribute("numberOfFeatures"));b.numberOfFeatures=c;return L(b,Xp,a,[])}
-var Yp={"http://www.opengis.net/wfs":{totalInserted:J(ql),totalUpdated:J(ql),totalDeleted:J(ql)}},Zp={"http://www.opengis.net/ogc":{FeatureId:Uk(function(a){return a.getAttribute("fid")})}},$p={"http://www.opengis.net/wfs":{Feature:function(a,b){dl(Zp,a,b)}}},Up={"http://www.opengis.net/wfs":{TransactionSummary:J(function(a,b){return L({},Yp,a,b)},"transactionSummary"),InsertResults:J(function(a,b){return L([],$p,a,b)},"insertIds")}};
-function Tp(a){for(a=a.firstChild;null!==a;a=a.nextSibling)if(1==a.nodeType)return L({},Up,a,[])}var aq={"http://www.opengis.net/wfs":{PropertyName:K(tl)}};function bq(a,b){var c=xk("http://www.opengis.net/ogc","Filter"),d=xk("http://www.opengis.net/ogc","FeatureId");c.appendChild(d);d.setAttribute("fid",b);a.appendChild(c)}
-var cq={"http://www.opengis.net/wfs":{Insert:K(function(a,b,c){var d=c[c.length-1],d=xk(v(d,"featureNS"),v(d,"featureType"));a.appendChild(d);wm(d,b,c)}),Update:K(function(a,b,c){var d=c[c.length-1],e=v(d,"featureType"),f=v(d,"featurePrefix"),f=l(f)?f:"feature",g=v(d,"featureNS");a.setAttribute("typeName",f+":"+e);Rk(a,"http://www.w3.org/2000/xmlns/","xmlns:"+f,g);e=b.T;if(l(e)){for(var f=b.N(),g=[],h=0,m=f.length;h<m;h++){var n=b.get(f[h]);l(n)&&g.push({name:f[h],value:n})}el({node:a,srsName:v(d,
-"srsName")},cq,$k("Property"),g,c);bq(a,e)}}),Delete:K(function(a,b,c){var d=c[c.length-1];c=v(d,"featureType");var e=v(d,"featurePrefix"),e=l(e)?e:"feature",d=v(d,"featureNS");a.setAttribute("typeName",e+":"+c);Rk(a,"http://www.w3.org/2000/xmlns/","xmlns:"+e,d);b=b.T;l(b)&&bq(a,b)}),Property:K(function(a,b,c){var d=xk("http://www.opengis.net/wfs","Name");a.appendChild(d);tl(d,b.name);null!=b.value&&(d=xk("http://www.opengis.net/wfs","Value"),a.appendChild(d),b.value instanceof Di?jl(d,b.value,c):
-tl(d,b.value))}),Native:K(function(a,b){l(b.Hi)&&a.setAttribute("vendorId",b.Hi);l(b.ui)&&a.setAttribute("safeToIgnore",b.ui);l(b.value)&&tl(a,b.value)})}},dq={"http://www.opengis.net/wfs":{Query:K(function(a,b,c){var d=c[c.length-1],e=v(d,"featurePrefix"),f=v(d,"featureNS"),g=v(d,"propertyNames"),h=v(d,"srsName");a.setAttribute("typeName",(l(e)?e+":":"")+b);l(h)&&a.setAttribute("srsName",h);l(f)&&Rk(a,"http://www.w3.org/2000/xmlns/","xmlns:"+e,f);b=Fc(d);b.node=a;el(b,aq,$k("PropertyName"),g,c);
-d=v(d,"bbox");l(d)&&(g=xk("http://www.opengis.net/ogc","Filter"),b=v(c[c.length-1],"geometryName"),e=xk("http://www.opengis.net/ogc","BBOX"),g.appendChild(e),f=xk("http://www.opengis.net/ogc","PropertyName"),tl(f,b),e.appendChild(f),jl(e,d,c),a.appendChild(g))})}};
-Sp.prototype.q=function(a){var b=xk("http://www.opengis.net/wfs","GetFeature");b.setAttribute("service","WFS");b.setAttribute("version","1.1.0");l(a)&&(l(a.handle)&&b.setAttribute("handle",a.handle),l(a.outputFormat)&&b.setAttribute("outputFormat",a.outputFormat),l(a.maxFeatures)&&b.setAttribute("maxFeatures",a.maxFeatures),l(a.resultType)&&b.setAttribute("resultType",a.resultType),l(a.Ai)&&b.setAttribute("startIndex",a.Ai),l(a.count)&&b.setAttribute("count",a.count));Rk(b,"http://www.w3.org/2001/XMLSchema-instance",
-"xsi:schemaLocation",this.g);var c=a.featureTypes;a=[{node:b,srsName:a.srsName,featureNS:l(a.featureNS)?a.featureNS:this.f,featurePrefix:a.featurePrefix,geometryName:a.geometryName,bbox:a.bbox,Be:l(a.Be)?a.Be:[]}];var d=Fc(a[a.length-1]);d.node=b;el(d,dq,$k("Query"),c,a);return b};
-Sp.prototype.r=function(a,b,c,d){var e=[],f=xk("http://www.opengis.net/wfs","Transaction");f.setAttribute("service","WFS");f.setAttribute("version","1.1.0");var g,h;l(d)&&(g=l(d.gmlOptions)?d.gmlOptions:{},l(d.handle)&&f.setAttribute("handle",d.handle));Rk(f,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.g);null!=a&&(h={node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:d.featurePrefix},Hc(h,g),el(h,cq,$k("Insert"),a,e));null!=b&&(h={node:f,featureNS:d.featureNS,
-featureType:d.featureType,featurePrefix:d.featurePrefix},Hc(h,g),el(h,cq,$k("Update"),b,e));null!=c&&el({node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:d.featurePrefix},cq,$k("Delete"),c,e);l(d.nativeElements)&&el({node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:d.featurePrefix},cq,$k("Native"),d.nativeElements,e);return f};Sp.prototype.wc=function(a){for(a=a.firstChild;null!==a;a=a.nextSibling)if(1==a.nodeType)return this.Yb(a);return null};
-Sp.prototype.Yb=function(a){a=a.firstElementChild.firstElementChild;if(null!=a)for(a=a.firstElementChild;null!==a;a=a.nextElementSibling)if(0!==a.childNodes.length&&(1!==a.childNodes.length||3!==a.firstChild.nodeType)){var b=[{}];il(a,b);return Be(b.pop().srsName)}return null};function eq(a){a=l(a)?a:{};this.defaultDataProjection=null;this.a=l(a.splitCollection)?a.splitCollection:!1}t(eq,jn);function fq(a){a=a.H();return 0==a.length?"":a[0]+" "+a[1]}function gq(a){a=a.H();for(var b=[],c=0,d=a.length;c<d;++c)b.push(a[c][0]+" "+a[c][1]);return b.join(",")}function hq(a){var b=[];a=a.Nc();for(var c=0,d=a.length;c<d;++c)b.push("("+gq(a[c])+")");return b.join(",")}function iq(a){var b=a.G();a=(0,jq[b])(a);b=b.toUpperCase();return 0===a.length?b+" EMPTY":b+"("+a+")"}
-var jq={Point:fq,LineString:gq,Polygon:hq,MultiPoint:function(a){var b=[];a=a.Qc();for(var c=0,d=a.length;c<d;++c)b.push("("+fq(a[c])+")");return b.join(",")},MultiLineString:function(a){var b=[];a=a.oc();for(var c=0,d=a.length;c<d;++c)b.push("("+gq(a[c])+")");return b.join(",")},MultiPolygon:function(a){var b=[];a=a.Rc();for(var c=0,d=a.length;c<d;++c)b.push("("+hq(a[c])+")");return b.join(",")},GeometryCollection:function(a){var b=[];a=a.$d();for(var c=0,d=a.length;c<d;++c)b.push(iq(a[c]));return b.join(",")}};
-k=eq.prototype;k.tc=function(a,b){var c=this.vc(a,b);if(l(c)){var d=new G;d.Ba(c);return d}return null};k.Jd=function(a,b){var c=[],d=this.vc(a,b);this.a&&"GeometryCollection"==d.G()?c=d.e:c=[d];for(var e=[],f=0,g=c.length;f<g;++f)d=new G,d.Ba(c[f]),e.push(d);return e};k.vc=function(a,b){var c;c=new kq(new lq(a));c.a=mq(c.c);c=nq(c);return l(c)?gk(c,!1,b):null};k.Ld=function(){return null};k.od=function(a,b){var c=a.J();return l(c)?this.Bc(c,b):""};
-k.Qe=function(a,b){if(1==a.length)return this.od(a[0],b);for(var c=[],d=0,e=a.length;d<e;++d)c.push(a[d].J());c=new aj(c);return this.Bc(c,b)};k.Bc=function(a,b){return iq(gk(a,!0,b))};function lq(a){this.c=a;this.a=-1}function oq(a,b){var c=l(b)?b:!1;return"0"<=a&&"9">=a||"."==a&&!c}
-function mq(a){var b=a.c.charAt(++a.a),c={position:a.a,value:b};if("("==b)c.type=2;else if(","==b)c.type=5;else if(")"==b)c.type=3;else if(oq(b)||"-"==b){c.type=4;var d,b=a.a,e=!1;do"."==d&&(e=!0),d=a.c.charAt(++a.a);while(oq(d,e));a=parseFloat(a.c.substring(b,a.a--));c.value=a}else if("a"<=b&&"z">=b||"A"<=b&&"Z">=b){c.type=1;b=a.a;do d=a.c.charAt(++a.a);while("a"<=d&&"z">=d||"A"<=d&&"Z">=d);a=a.c.substring(b,a.a--).toUpperCase();c.value=a}else{if(" "==b||"\t"==b||"\r"==b||"\n"==b)return mq(a);if(""===
-b)c.type=6;else throw Error("Unexpected character: "+b);}return c}function kq(a){this.c=a}k=kq.prototype;k.match=function(a){if(a=this.a.type==a)this.a=mq(this.c);return a};
-function nq(a){var b=a.a;if(a.match(1)){var c=b.value;if("GEOMETRYCOLLECTION"==c){a:{if(a.match(2)){b=[];do b.push(nq(a));while(a.match(5));if(a.match(3)){a=b;break a}}else if(pq(a)){a=[];break a}qq(a);a=void 0}return new aj(a)}var d=rq[c],b=sq[c];if(!l(d)||!l(b))throw Error("Invalid geometry type: "+c);a=d.call(a);return new b(a)}qq(a)}k.Hd=function(){if(this.match(2)){var a=tq(this);if(this.match(3))return a}else if(pq(this))return null;qq(this)};
-k.Gd=function(){if(this.match(2)){var a=uq(this);if(this.match(3))return a}else if(pq(this))return[];qq(this)};k.Id=function(){if(this.match(2)){var a=vq(this);if(this.match(3))return a}else if(pq(this))return[];qq(this)};k.Zh=function(){if(this.match(2)){var a;if(2==this.a.type)for(a=[this.Hd()];this.match(5);)a.push(this.Hd());else a=uq(this);if(this.match(3))return a}else if(pq(this))return[];qq(this)};
-k.Yh=function(){if(this.match(2)){var a=vq(this);if(this.match(3))return a}else if(pq(this))return[];qq(this)};k.$h=function(){if(this.match(2)){for(var a=[this.Id()];this.match(5);)a.push(this.Id());if(this.match(3))return a}else if(pq(this))return[];qq(this)};function tq(a){for(var b=[],c=0;2>c;++c){var d=a.a;if(a.match(4))b.push(d.value);else break}if(2==b.length)return b;qq(a)}function uq(a){for(var b=[tq(a)];a.match(5);)b.push(tq(a));return b}
-function vq(a){for(var b=[a.Gd()];a.match(5);)b.push(a.Gd());return b}function pq(a){var b=1==a.a.type&&"EMPTY"==a.a.value;b&&(a.a=mq(a.c));return b}function qq(a){throw Error("Unexpected `"+a.a.value+"` at position "+a.a.position+" in `"+a.c.c+"`");}var sq={POINT:yj,LINESTRING:H,POLYGON:I,MULTIPOINT:Aj,MULTILINESTRING:uj,MULTIPOLYGON:Pj},rq={POINT:kq.prototype.Hd,LINESTRING:kq.prototype.Gd,POLYGON:kq.prototype.Id,MULTIPOINT:kq.prototype.Zh,MULTILINESTRING:kq.prototype.Yh,MULTIPOLYGON:kq.prototype.$h};function wq(){this.version=void 0}t(wq,Ep);function Fp(a,b){for(var c=b.firstChild;null!==c;c=c.nextSibling)if(1==c.nodeType)return Gp(a,c);return null}function Gp(a,b){a.version=ya(b.getAttribute("version"));var c=L({version:a.version},xq,b,[]);return l(c)?c:null}function yq(a,b){return L({},zq,a,b)}function Aq(a,b){return L({},Bq,a,b)}function Cq(a,b){var c=yq(a,b);if(l(c)){var d=[rl(a.getAttribute("width")),rl(a.getAttribute("height"))];c.size=d;return c}}function Dq(a,b){return L([],Eq,a,b)}
-var Fq=[null,"http://www.opengis.net/wms"],xq=Xk(Fq,{Service:J(function(a,b){return L({},Gq,a,b)}),Capability:J(function(a,b){return L({},Hq,a,b)})}),Hq=Xk(Fq,{Request:J(function(a,b){return L({},Iq,a,b)}),Exception:J(function(a,b){return L([],Jq,a,b)}),Layer:J(function(a,b){return L({},Kq,a,b)})}),Gq=Xk(Fq,{Name:J(M),Title:J(M),Abstract:J(M),KeywordList:J(Dq),OnlineResource:J(Dp),ContactInformation:J(function(a,b){return L({},Lq,a,b)}),Fees:J(M),AccessConstraints:J(M),LayerLimit:J(ql),MaxWidth:J(ql),
-MaxHeight:J(ql)}),Lq=Xk(Fq,{ContactPersonPrimary:J(function(a,b){return L({},Mq,a,b)}),ContactPosition:J(M),ContactAddress:J(function(a,b){return L({},Nq,a,b)}),ContactVoiceTelephone:J(M),ContactFacsimileTelephone:J(M),ContactElectronicMailAddress:J(M)}),Mq=Xk(Fq,{ContactPerson:J(M),ContactOrganization:J(M)}),Nq=Xk(Fq,{AddressType:J(M),Address:J(M),City:J(M),StateOrProvince:J(M),PostCode:J(M),Country:J(M)}),Jq=Xk(Fq,{Format:Uk(M)}),Kq=Xk(Fq,{Name:J(M),Title:J(M),Abstract:J(M),KeywordList:J(Dq),CRS:Wk(M),
-EX_GeographicBoundingBox:J(function(a,b){var c=L({},Oq,a,b);if(l(c)){var d=v(c,"westBoundLongitude"),e=v(c,"southBoundLatitude"),f=v(c,"eastBoundLongitude"),c=v(c,"northBoundLatitude");return l(d)&&l(e)&&l(f)&&l(c)?[d,e,f,c]:void 0}}),BoundingBox:Wk(function(a){var b=[pl(a.getAttribute("minx")),pl(a.getAttribute("miny")),pl(a.getAttribute("maxx")),pl(a.getAttribute("maxy"))],c=[pl(a.getAttribute("resx")),pl(a.getAttribute("resy"))];return{crs:a.getAttribute("CRS"),extent:b,res:c}}),Dimension:Wk(function(a){return{name:a.getAttribute("name"),
-units:a.getAttribute("units"),unitSymbol:a.getAttribute("unitSymbol"),"default":a.getAttribute("default"),multipleValues:ll(a.getAttribute("multipleValues")),nearestValue:ll(a.getAttribute("nearestValue")),current:ll(a.getAttribute("current")),values:M(a)}}),Attribution:J(function(a,b){return L({},Pq,a,b)}),AuthorityURL:Wk(function(a,b){var c=yq(a,b);if(l(c)){var d=a.getAttribute("name");c.name=d;return c}}),Identifier:Wk(M),MetadataURL:Wk(function(a,b){var c=yq(a,b);if(l(c)){var d=a.getAttribute("type");
-c.type=d;return c}}),DataURL:Wk(yq),FeatureListURL:Wk(yq),Style:Wk(function(a,b){return L({},Qq,a,b)}),MinScaleDenominator:J(nl),MaxScaleDenominator:J(nl),Layer:Wk(function(a,b){var c=b[b.length-1],d=L({},Kq,a,b);if(l(d)){var e=ll(a.getAttribute("queryable"));l(e)||(e=v(c,"queryable"));d.queryable=l(e)?e:!1;e=rl(a.getAttribute("cascaded"));l(e)||(e=v(c,"cascaded"));d.cascaded=e;e=ll(a.getAttribute("opaque"));l(e)||(e=v(c,"opaque"));d.opaque=l(e)?e:!1;e=ll(a.getAttribute("noSubsets"));l(e)||(e=v(c,
-"noSubsets"));d.noSubsets=l(e)?e:!1;e=pl(a.getAttribute("fixedWidth"));l(e)||(e=v(c,"fixedWidth"));d.fixedWidth=e;e=pl(a.getAttribute("fixedHeight"));l(e)||(e=v(c,"fixedHeight"));d.fixedHeight=e;Ta(["Style","CRS","AuthorityURL"],function(a){l(v(c,a))&&Ec(d,a)});Ta("EX_GeographicBoundingBox BoundingBox Dimension Attribution MinScaleDenominator MaxScaleDenominator".split(" "),function(a){l(v(d,a))||(d[a]=v(c,a))});return d}})}),Pq=Xk(Fq,{Title:J(M),OnlineResource:J(Dp),LogoURL:J(Cq)}),Oq=Xk(Fq,{westBoundLongitude:J(nl),
-eastBoundLongitude:J(nl),southBoundLatitude:J(nl),northBoundLatitude:J(nl)}),Iq=Xk(Fq,{GetCapabilities:J(Aq),GetMap:J(Aq),GetFeatureInfo:J(Aq)}),Bq=Xk(Fq,{Format:Wk(M),DCPType:Wk(function(a,b){return L({},Rq,a,b)})}),Rq=Xk(Fq,{HTTP:J(function(a,b){return L({},Sq,a,b)})}),Sq=Xk(Fq,{Get:J(yq),Post:J(yq)}),Qq=Xk(Fq,{Name:J(M),Title:J(M),Abstract:J(M),LegendURL:Wk(Cq),StyleSheetURL:J(yq),StyleURL:J(yq)}),zq=Xk(Fq,{Format:J(M),OnlineResource:J(Dp)}),Eq=Xk(Fq,{Keyword:Uk(M)});var Tq=new ue(6378137);function N(a){pd.call(this);a=l(a)?a:{};this.a=null;this.e=Te;this.d=void 0;y(this,td("projection"),this.Sg,!1,this);y(this,td("tracking"),this.Tg,!1,this);l(a.projection)&&this.l(Be(a.projection));l(a.trackingOptions)&&this.k(a.trackingOptions);this.b(l(a.tracking)?a.tracking:!1)}t(N,pd);k=N.prototype;k.I=function(){this.b(!1);N.K.I.call(this)};k.Sg=function(){var a=this.g();null!=a&&(this.e=Ae(Be("EPSG:4326"),a),null===this.a||this.set("position",this.e(this.a)))};
-k.Tg=function(){if(xg){var a=this.i();a&&!l(this.d)?this.d=ba.navigator.geolocation.watchPosition(sa(this.gi,this),sa(this.hi,this),this.f()):!a&&l(this.d)&&(ba.navigator.geolocation.clearWatch(this.d),this.d=void 0)}};
-k.gi=function(a){a=a.coords;this.set("accuracy",a.accuracy);this.set("altitude",null===a.altitude?void 0:a.altitude);this.set("altitudeAccuracy",null===a.altitudeAccuracy?void 0:a.altitudeAccuracy);this.set("heading",null===a.heading?void 0:Mb(a.heading));null===this.a?this.a=[a.longitude,a.latitude]:(this.a[0]=a.longitude,this.a[1]=a.latitude);var b=this.e(this.a);this.set("position",b);this.set("speed",null===a.speed?void 0:a.speed);a=Oj(Tq,this.a,a.accuracy);a.Mb(this.e);this.set("accuracyGeometry",
-a);this.n()};k.hi=function(a){a.type="error";this.b(!1);this.dispatchEvent(a)};k.Zd=function(){return this.get("accuracy")};N.prototype.getAccuracy=N.prototype.Zd;N.prototype.o=function(){return this.get("accuracyGeometry")||null};N.prototype.getAccuracyGeometry=N.prototype.o;N.prototype.q=function(){return this.get("altitude")};N.prototype.getAltitude=N.prototype.q;N.prototype.r=function(){return this.get("altitudeAccuracy")};N.prototype.getAltitudeAccuracy=N.prototype.r;N.prototype.D=function(){return this.get("heading")};
-N.prototype.getHeading=N.prototype.D;N.prototype.F=function(){return this.get("position")};N.prototype.getPosition=N.prototype.F;N.prototype.g=function(){return this.get("projection")};N.prototype.getProjection=N.prototype.g;N.prototype.u=function(){return this.get("speed")};N.prototype.getSpeed=N.prototype.u;N.prototype.i=function(){return this.get("tracking")};N.prototype.getTracking=N.prototype.i;N.prototype.f=function(){return this.get("trackingOptions")};N.prototype.getTrackingOptions=N.prototype.f;
-N.prototype.l=function(a){this.set("projection",a)};N.prototype.setProjection=N.prototype.l;N.prototype.b=function(a){this.set("tracking",a)};N.prototype.setTracking=N.prototype.b;N.prototype.k=function(a){this.set("trackingOptions",a)};N.prototype.setTrackingOptions=N.prototype.k;function Uq(a,b,c){for(var d=[],e=a(0),f=a(1),g=b(e),h=b(f),m=[f,e],n=[h,g],p=[1,0],r={},q=1E5,u,x,B,E,F;0<--q&&0<p.length;)B=p.pop(),e=m.pop(),g=n.pop(),f=B.toString(),f in r||(d.push(g[0],g[1]),r[f]=!0),E=p.pop(),f=m.pop(),h=n.pop(),F=(B+E)/2,u=a(F),x=b(u),ej(x[0],x[1],g[0],g[1],h[0],h[1])<c?(d.push(h[0],h[1]),f=E.toString(),r[f]=!0):(p.push(E,F,F,B),n.push(h,x,x,g),m.push(f,u,u,e));return d}function Vq(a,b,c,d,e){var f=Be("EPSG:4326");return Uq(function(d){return[a,b+(c-b)*d]},Se(f,d),e)}
-function Wq(a,b,c,d,e){var f=Be("EPSG:4326");return Uq(function(d){return[b+(c-b)*d,a]},Se(f,d),e)};function Xq(a){a=l(a)?a:{};this.i=this.g=null;this.d=this.b=Infinity;this.f=this.e=-Infinity;this.r=l(a.targetSize)?a.targetSize:100;this.o=l(a.maxLines)?a.maxLines:100;this.a=[];this.c=[];this.q=l(a.strokeStyle)?a.strokeStyle:Yq;this.p=this.l=void 0;this.k=null;this.setMap(l(a.map)?a.map:null)}var Yq=new Ji({color:"rgba(0,0,0,0.2)"}),Zq=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001];
-function $q(a,b,c,d,e){var f=e;b=Vq(b,a.e,a.b,a.i,c);f=l(a.a[f])?a.a[f]:new H(null);sj(f,"XY",b);pe(f.s(),d)&&(a.a[e++]=f);return e}function ar(a,b,c,d,e){var f=e;b=Wq(b,a.f,a.d,a.i,c);f=l(a.c[f])?a.c[f]:new H(null);sj(f,"XY",b);pe(f.s(),d)&&(a.c[e++]=f);return e}k=Xq.prototype;k.Ug=function(){return this.g};k.Cf=function(){return this.a};k.Hf=function(){return this.c};
-k.de=function(a){var b=a.vectorContext,c=a.frameState;a=c.extent;var d=c.viewState,e=d.center,f=d.projection,d=d.resolution,c=c.pixelRatio,c=d*d/(4*c*c);if(null===this.i||!Re(this.i,f)){var g=f.s(),h=f.d,m=h[2],n=h[1],p=h[0];this.b=h[3];this.d=m;this.e=n;this.f=p;h=Be("EPSG:4326");this.l=Se(h,f);this.p=Se(f,h);this.k=this.p(je(g));this.i=f}for(var f=this.k[0],g=this.k[1],h=-1,r,n=Math.pow(this.r*d,2),p=[],q=[],d=0,m=Zq.length;d<m;++d){r=Zq[d]/2;p[0]=f-r;p[1]=g-r;q[0]=f+r;q[1]=g+r;this.l(p,p);this.l(q,
-q);r=Math.pow(q[0]-p[0],2)+Math.pow(q[1]-p[1],2);if(r<=n)break;h=Zq[d]}d=h;if(-1==d)this.a.length=this.c.length=0;else{f=this.p(e);e=f[0];f=f[1];g=this.o;e=Math.floor(e/d)*d;n=Jb(e,this.f,this.d);m=$q(this,n,c,a,0);for(h=0;n!=this.f&&h++<g;)n=Math.max(n-d,this.f),m=$q(this,n,c,a,m);n=Jb(e,this.f,this.d);for(h=0;n!=this.d&&h++<g;)n=Math.min(n+d,this.d),m=$q(this,n,c,a,m);this.a.length=m;f=Math.floor(f/d)*d;e=Jb(f,this.e,this.b);m=ar(this,e,c,a,0);for(h=0;e!=this.e&&h++<g;)e=Math.max(e-d,this.e),m=
-ar(this,e,c,a,m);e=Jb(f,this.e,this.b);for(h=0;e!=this.b&&h++<g;)e=Math.min(e+d,this.b),m=ar(this,e,c,a,m);this.c.length=m}b.Aa(null,this.q);a=0;for(c=this.a.length;a<c;++a)e=this.a[a],b.sb(e,null);a=0;for(c=this.c.length;a<c;++a)e=this.c[a],b.sb(e,null)};k.setMap=function(a){null!==this.g&&(this.g.v("postcompose",this.de,this),this.g.O());null!==a&&(a.t("postcompose",this.de,this),a.O());this.g=a};function br(a,b,c,d,e){hd.call(this);this.g=e;this.l=a;this.f=c;this.b=b;this.state=d}t(br,hd);br.prototype.s=function(){return this.l};function cr(a,b,c,d,e,f){br.call(this,a,b,c,0,d);this.i=e;this.a=new Image;null!==f&&(this.a.crossOrigin=f);this.e={};this.c=null;this.state=0}t(cr,br);cr.prototype.d=function(a){if(l(a)){var b=na(a);if(b in this.e)return this.e[b];a=Bc(this.e)?this.a:this.a.cloneNode(!1);return this.e[b]=a}return this.a};cr.prototype.k=function(){this.state=3;Ta(this.c,Yc);this.c=null;this.dispatchEvent("change")};cr.prototype.p=function(){this.state=2;Ta(this.c,Yc);this.c=null;this.dispatchEvent("change")};
-function dr(a){0==a.state&&(a.state=1,a.c=[Wc(a.a,"error",a.k,!1,a),Wc(a.a,"load",a.p,!1,a)],a.a.src=a.i)};function er(a,b,c,d,e){br.call(this,a,b,c,2,d);this.a=e}t(er,br);er.prototype.d=function(){return this.a};function fr(a,b){hd.call(this);this.a=a;this.state=b}t(fr,hd);fr.prototype.d=function(){return na(this).toString()};fr.prototype.i=function(){return this.a};function gr(a,b,c,d,e){fr.call(this,a,b);this.l=c;this.c=new Image;null!==d&&(this.c.crossOrigin=d);this.f={};this.e=null;this.o=e}t(gr,fr);gr.prototype.b=function(a){if(l(a)){var b=na(a);if(b in this.f)return this.f[b];a=Bc(this.f)?this.c:this.c.cloneNode(!1);return this.f[b]=a}return this.c};gr.prototype.d=function(){return this.l};gr.prototype.k=function(){this.state=3;Ta(this.e,Yc);this.e=null;this.dispatchEvent("change")};
-gr.prototype.p=function(){l(this.c.naturalWidth)||(this.c.naturalWidth=this.c.width,this.c.naturalHeight=this.c.height);this.state=this.c.naturalWidth&&this.c.naturalHeight?2:4;Ta(this.e,Yc);this.e=null;this.dispatchEvent("change")};function hr(a,b,c){return function(d,e,f){return c(a,b,d,e,f)}}function ir(){};function jr(){ld.call(this);this.g=null}t(jr,ld);jr.prototype.setMap=function(a){this.g=a};function kr(a,b,c,d,e){if(null!=c){var f=b.e(),g=b.a();l(f)&&l(g)&&l(e)&&0<e&&(a.Ea(ef({rotation:f,duration:e,easing:$e})),l(d)&&a.Ea(df({source:g,duration:e,easing:$e})));b.rotate(c,d)}}function lr(a,b,c,d,e){var f=b.b();c=b.constrainResolution(f,c,0);mr(a,b,c,d,e)}
-function mr(a,b,c,d,e){if(null!=c){var f=b.b(),g=b.a();l(f)&&l(g)&&l(e)&&0<e&&(a.Ea(ff({resolution:f,duration:e,easing:$e})),l(d)&&a.Ea(df({source:g,duration:e,easing:$e})));if(null!=d){var h;a=b.a();e=b.b();l(a)&&l(e)&&(h=[d[0]-c*(d[0]-a[0])/e,d[1]-c*(d[1]-a[1])/e]);b.Ka(h)}b.d(c)}};function nr(a){a=l(a)?a:{};this.a=l(a.delta)?a.delta:1;jr.call(this);this.b=l(a.duration)?a.duration:250}t(nr,jr);nr.prototype.La=function(a){var b=!1,c=a.a;if(a.type==oi){var b=a.map,d=a.coordinate,c=c.e?-this.a:this.a,e=b.a();lr(b,e,c,d,this.b);a.preventDefault();b=!0}return!b};function or(a,b){hd.call(this);this.a=new Sh(this);var c=a;b&&(c=xf(a));this.a.ya(c,"dragenter",this.Sh);c!=a&&this.a.ya(c,"dragover",this.Th);this.a.ya(a,"dragover",this.Uh);this.a.ya(a,"drop",this.Vh)}t(or,hd);k=or.prototype;k.ic=!1;k.I=function(){or.K.I.call(this);this.a.Nb()};k.Sh=function(a){var b=a.a.dataTransfer;(this.ic=!(!b||!(b.types&&(Za(b.types,"Files")||Za(b.types,"public.file-url"))||b.files&&0<b.files.length)))&&a.preventDefault()};
-k.Th=function(a){this.ic&&(a.preventDefault(),a.a.dataTransfer.dropEffect="none")};k.Uh=function(a){this.ic&&(a.preventDefault(),a.d(),a=a.a.dataTransfer,a.effectAllowed="all",a.dropEffect="copy")};k.Vh=function(a){this.ic&&(a.preventDefault(),a.d(),a=new jc(a.a),a.type="drop",this.dispatchEvent(a))};function pr(a){a.prototype.then=a.prototype.then;a.prototype.$goog_Thenable=!0}function qr(a){if(!a)return!1;try{return!!a.$goog_Thenable}catch(b){return!1}};function rr(a){ba.setTimeout(function(){throw a;},0)}function sr(a,b){var c=a;b&&(c=sa(a,b));c=tr(c);la(ba.setImmediate)?ba.setImmediate(c):(ur||(ur=vr()),ur(c))}var ur;
-function vr(){var a=ba.MessageChannel;"undefined"===typeof a&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(a=function(){var a=document.createElement("iframe");a.style.display="none";a.src="";document.documentElement.appendChild(a);var b=a.contentWindow,a=b.document;a.open();a.write("");a.close();var c="callImmediate"+Math.random(),d="file:"==b.location.protocol?"*":b.location.protocol+"//"+b.location.host,a=sa(function(a){if(a.origin==d||a.data==c)this.port1.onmessage()},
-this);b.addEventListener("message",a,!1);this.port1={};this.port2={postMessage:function(){b.postMessage(c,d)}}});if("undefined"!==typeof a){var b=new a,c={},d=c;b.port1.onmessage=function(){c=c.next;var a=c.Td;c.Td=null;a()};return function(a){d.next={Td:a};d=d.next;b.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(a){var b=document.createElement("script");b.onreadystatechange=function(){b.onreadystatechange=null;b.parentNode.removeChild(b);
-b=null;a();a=null};document.documentElement.appendChild(b)}:function(a){ba.setTimeout(a,0)}}var tr=ed;function wr(a,b){xr||yr();zr||(xr(),zr=!0);Ar.push(new Br(a,b))}var xr;function yr(){if(ba.Promise&&ba.Promise.resolve){var a=ba.Promise.resolve();xr=function(){a.then(Cr)}}else xr=function(){sr(Cr)}}var zr=!1,Ar=[];function Cr(){for(;Ar.length;){var a=Ar;Ar=[];for(var b=0;b<a.length;b++){var c=a[b];try{c.a.call(c.c)}catch(d){rr(d)}}}zr=!1}function Br(a,b){this.a=a;this.c=b};function Dr(a,b){this.c=Er;this.f=void 0;this.a=this.b=null;this.d=this.e=!1;try{var c=this;a.call(b,function(a){Fr(c,Gr,a)},function(a){Fr(c,Hr,a)})}catch(d){Fr(this,Hr,d)}}var Er=0,Gr=2,Hr=3;Dr.prototype.then=function(a,b,c){return Ir(this,la(a)?a:null,la(b)?b:null,c)};pr(Dr);Dr.prototype.cancel=function(a){this.c==Er&&wr(function(){var b=new Jr(a);Kr(this,b)},this)};
-function Kr(a,b){if(a.c==Er)if(a.b){var c=a.b;if(c.a){for(var d=0,e=-1,f=0,g;g=c.a[f];f++)if(g=g.Fc)if(d++,g==a&&(e=f),0<=e&&1<d)break;0<=e&&(c.c==Er&&1==d?Kr(c,b):(d=c.a.splice(e,1)[0],Lr(c,d,Hr,b)))}}else Fr(a,Hr,b)}function Mr(a,b){a.a&&a.a.length||a.c!=Gr&&a.c!=Hr||Nr(a);a.a||(a.a=[]);a.a.push(b)}
-function Ir(a,b,c,d){var e={Fc:null,ye:null,ze:null};e.Fc=new Dr(function(a,g){e.ye=b?function(c){try{var e=b.call(d,c);a(e)}catch(n){g(n)}}:a;e.ze=c?function(b){try{var e=c.call(d,b);!l(e)&&b instanceof Jr?g(b):a(e)}catch(n){g(n)}}:g});e.Fc.b=a;Mr(a,e);return e.Fc}Dr.prototype.g=function(a){this.c=Er;Fr(this,Gr,a)};Dr.prototype.i=function(a){this.c=Er;Fr(this,Hr,a)};
-function Fr(a,b,c){if(a.c==Er){if(a==c)b=Hr,c=new TypeError("Promise cannot resolve to itself");else{if(qr(c)){a.c=1;c.then(a.g,a.i,a);return}if(ma(c))try{var d=c.then;if(la(d)){Or(a,c,d);return}}catch(e){b=Hr,c=e}}a.f=c;a.c=b;Nr(a);b!=Hr||c instanceof Jr||Pr(a,c)}}function Or(a,b,c){function d(b){f||(f=!0,a.i(b))}function e(b){f||(f=!0,a.g(b))}a.c=1;var f=!1;try{c.call(b,e,d)}catch(g){d(g)}}function Nr(a){a.e||(a.e=!0,wr(a.l,a))}
-Dr.prototype.l=function(){for(;this.a&&this.a.length;){var a=this.a;this.a=[];for(var b=0;b<a.length;b++)Lr(this,a[b],this.c,this.f)}this.e=!1};function Lr(a,b,c,d){if(c==Gr)b.ye(d);else{for(;a&&a.d;a=a.b)a.d=!1;b.ze(d)}}function Pr(a,b){a.d=!0;wr(function(){a.d&&Qr.call(null,b)})}var Qr=rr;function Jr(a){va.call(this,a)}t(Jr,va);Jr.prototype.name="cancel";/*
+function xe(b){this.radius=b}function ye(b,c){var d=bc(b[1]),e=bc(c[1]),f=(e-d)/2,g=bc(c[0]-b[0])/2,d=Math.sin(f)*Math.sin(f)+Math.sin(g)*Math.sin(g)*Math.cos(d)*Math.cos(e);return 2*ze.radius*Math.atan2(Math.sqrt(d),Math.sqrt(1-d))}xe.prototype.offset=function(b,c,d){var e=bc(b[1]);c/=this.radius;var f=Math.asin(Math.sin(e)*Math.cos(c)+Math.cos(e)*Math.sin(c)*Math.cos(d));return[180*(bc(b[0])+Math.atan2(Math.sin(d)*Math.sin(c)*Math.cos(e),Math.cos(c)-Math.sin(e)*Math.sin(f)))/Math.PI,180*f/Math.PI]};var ze=new xe(6370997);var Ae={};Ae.degrees=2*Math.PI*ze.radius/360;Ae.ft=.3048;Ae.m=1;function Be(b){this.a=b.code;this.c=b.units;this.g=m(b.extent)?b.extent:null;this.d=m(b.worldExtent)?b.worldExtent:null;this.b=m(b.axisOrientation)?b.axisOrientation:"enu";this.f=m(b.global)?b.global:!1;this.e=null}l=Be.prototype;l.gh=function(){return this.a};l.D=function(){return this.g};l.rj=function(){return this.c};l.ie=function(){return Ae[this.c]};l.Mh=function(){return this.d};function Ce(b){return b.b}l.wi=function(){return this.f};
+l.sj=function(b){this.g=b};l.pl=function(b){this.d=b};l.je=function(b,c){if("degrees"==this.c)return b;var d=De(this,Ee("EPSG:4326")),e=[c[0]-b/2,c[1],c[0]+b/2,c[1],c[0],c[1]-b/2,c[0],c[1]+b/2],e=d(e,e,2),d=(ye(e.slice(0,2),e.slice(2,4))+ye(e.slice(4,6),e.slice(6,8)))/2,e=this.ie();m(e)&&(d/=e);return d};var Fe={},Ge={};function He(b){Ie(b);Ta(b,function(c){Ta(b,function(b){c!==b&&Je(c,b,Ke)})})}function Le(){var b=Me,c=Ne,d=Oe;Ta(Pe,function(e){Ta(b,function(b){Je(e,b,c);Je(b,e,d)})})}
+function Qe(b){Fe[b.a]=b;Je(b,b,Ke)}function Ie(b){var c=[];Ta(b,function(b){c.push(Qe(b))})}function Re(b){return null!=b?ia(b)?Ee(b):b:Ee("EPSG:3857")}function Je(b,c,d){b=b.a;c=c.a;b in Ge||(Ge[b]={});Ge[b][c]=d}function Se(b,c,d,e){b=Ee(b);c=Ee(c);Je(b,c,Te(d));Je(c,b,Te(e))}function Te(b){return function(c,d,e){var f=c.length;e=m(e)?e:2;d=m(d)?d:Array(f);var g,h;for(h=0;h<f;h+=e)for(g=b([c[h],c[h+1]]),d[h]=g[0],d[h+1]=g[1],g=e-1;2<=g;--g)d[h+g]=c[h+g];return d}}
+function Ee(b){var c;if(b instanceof Be)c=b;else if(ia(b)){if(c=Fe[b],!m(c)&&"function"==typeof proj4){var d=proj4.defs(b);if(m(d)){c=d.units;!m(c)&&m(d.to_meter)&&(c=d.to_meter.toString(),Ae[c]=d.to_meter);c=new Be({code:b,units:c,axisOrientation:d.axis});Qe(c);var e,f,g;for(e in Fe)f=proj4.defs(e),m(f)&&(g=Ee(e),f===d?He([g,c]):(f=proj4(e,b),Se(g,c,f.forward,f.inverse)))}else c=null}}else c=null;return c}function Ue(b,c){return b===c?!0:b.c!=c.c?!1:De(b,c)===Ke}
+function Ve(b,c){var d=Ee(b),e=Ee(c);return De(d,e)}function De(b,c){var d=b.a,e=c.a,f;d in Ge&&e in Ge[d]&&(f=Ge[d][e]);m(f)||(f=We);return f}function We(b,c){if(m(c)&&b!==c){for(var d=0,e=b.length;d<e;++d)c[d]=b[d];b=c}return b}function Ke(b,c){var d;if(m(c)){d=0;for(var e=b.length;d<e;++d)c[d]=b[d];d=c}else d=b.slice();return d}function Xe(b,c,d){c=Ve(c,d);return we(b,c)};function A(b){td.call(this);b=m(b)?b:{};this.j=[0,0];var c={};c.center=m(b.center)?b.center:null;this.q=Re(b.projection);var d,e,f,g=m(b.minZoom)?b.minZoom:0;d=m(b.maxZoom)?b.maxZoom:28;var h=m(b.zoomFactor)?b.zoomFactor:2;if(m(b.resolutions))d=b.resolutions,e=d[0],f=d[d.length-1],d=fc(d);else{e=Re(b.projection);f=e.D();var k=(null===f?360*Ae.degrees/Ae[e.c]:Math.max(re(f),oe(f)))/256/Math.pow(2,0),n=k/Math.pow(2,28);e=b.maxResolution;m(e)?g=0:e=k/Math.pow(h,g);f=b.minResolution;m(f)||(f=m(b.maxZoom)?
+m(b.maxResolution)?e/Math.pow(h,d):k/Math.pow(h,d):n);d=g+Math.floor(Math.log(e/f)/Math.log(h));f=e/Math.pow(h,d-g);d=gc(h,e,d-g)}this.f=e;this.F=f;this.p=g;g=m(b.extent)?cc(b.extent):dc;(m(b.enableRotation)?b.enableRotation:1)?(e=b.constrainRotation,e=m(e)&&!0!==e?!1===e?ic:ja(e)?jc(e):ic:kc()):e=hc;this.t=new lc(g,d,e);m(b.resolution)?c.resolution=b.resolution:m(b.zoom)&&(c.resolution=this.constrainResolution(this.f,b.zoom-this.p));c.rotation=m(b.rotation)?b.rotation:0;this.G(c)}u(A,td);
+A.prototype.i=function(b){return this.t.center(b)};A.prototype.constrainResolution=function(b,c,d){return this.t.resolution(b,c||0,d||0)};A.prototype.constrainRotation=function(b,c){return this.t.rotation(b,c||0)};A.prototype.a=function(){return this.get("center")};A.prototype.getCenter=A.prototype.a;A.prototype.g=function(b){var c=this.a(),d=this.b();return[c[0]-d*b[0]/2,c[1]-d*b[1]/2,c[0]+d*b[0]/2,c[1]+d*b[1]/2]};A.prototype.J=function(){return this.q};A.prototype.b=function(){return this.get("resolution")};
+A.prototype.getResolution=A.prototype.b;A.prototype.n=function(b,c){return Math.max(re(b)/c[0],oe(b)/c[1])};function Ye(b){var c=b.f,d=Math.log(c/b.F)/Math.log(2);return function(b){return c/Math.pow(2,b*d)}}A.prototype.e=function(){return this.get("rotation")};A.prototype.getRotation=A.prototype.e;function Ze(b){var c=b.f,d=Math.log(c/b.F)/Math.log(2);return function(b){return Math.log(c/b)/Math.log(2)/d}}
+function $e(b){var c=b.a(),d=b.q,e=b.b();b=b.e();return{center:c.slice(),projection:m(d)?d:null,resolution:e,rotation:m(b)?b:0}}l=A.prototype;l.Oh=function(){var b,c=this.b();if(m(c)){var d,e=0;do{d=this.constrainResolution(this.f,e);if(d==c){b=e;break}++e}while(d>this.F)}return m(b)?this.p+b:b};l.ge=function(b,c){if(!se(b)){this.Oa(ke(b));var d=this.n(b,c),e=this.constrainResolution(d,0,0);e<d&&(e=this.constrainResolution(e,-1,0));this.d(e)}};
+l.dh=function(b,c,d){var e=m(d)?d:{};d=m(e.padding)?e.padding:[0,0,0,0];var f=m(e.constrainResolution)?e.constrainResolution:!0,g=m(e.nearest)?e.nearest:!1,h;m(e.minResolution)?h=e.minResolution:m(e.maxZoom)?h=this.constrainResolution(this.f,e.maxZoom-this.p,0):h=0;var k=b.k,n=this.e(),e=Math.cos(-n),n=Math.sin(-n),p=Infinity,q=Infinity,r=-Infinity,s=-Infinity;b=b.s;for(var v=0,y=k.length;v<y;v+=b)var C=k[v]*e-k[v+1]*n,F=k[v]*n+k[v+1]*e,p=Math.min(p,C),q=Math.min(q,F),r=Math.max(r,C),s=Math.max(s,
+F);c=this.n([p,q,r,s],[c[0]-d[1]-d[3],c[1]-d[0]-d[2]]);c=isNaN(c)?h:Math.max(c,h);f&&(h=this.constrainResolution(c,0,0),!g&&h<c&&(h=this.constrainResolution(h,-1,0)),c=h);this.d(c);n=-n;g=(p+r)/2+(d[1]-d[3])/2*c;d=(q+s)/2+(d[0]-d[2])/2*c;this.Oa([g*e-d*n,d*e+g*n])};l.Yg=function(b,c,d){var e=this.e(),f=Math.cos(-e),e=Math.sin(-e),g=b[0]*f-b[1]*e;b=b[1]*f+b[0]*e;var h=this.b(),g=g+(c[0]/2-d[0])*h;b+=(d[1]-c[1]/2)*h;e=-e;this.Oa([g*f-b*e,b*f+g*e])};function af(b){return null!=b.a()&&m(b.b())}
+l.rotate=function(b,c){if(m(c)){var d,e=this.a();m(e)&&(d=[e[0]-c[0],e[1]-c[1]],Dd(d,b-this.e()),yd(d,c));this.Oa(d)}this.r(b)};l.Oa=function(b){this.set("center",b)};A.prototype.setCenter=A.prototype.Oa;function bf(b,c){b.j[1]+=c}A.prototype.d=function(b){this.set("resolution",b)};A.prototype.setResolution=A.prototype.d;A.prototype.r=function(b){this.set("rotation",b)};A.prototype.setRotation=A.prototype.r;A.prototype.Q=function(b){b=this.constrainResolution(this.f,b-this.p,0);this.d(b)};function cf(b){return 1-Math.pow(1-b,3)};function df(b){return 3*b*b-2*b*b*b}function ef(b){return b}function ff(b){return.5>b?df(2*b):1-df(2*(b-.5))};function gf(b){var c=b.source,d=m(b.start)?b.start:ua(),e=c[0],f=c[1],g=m(b.duration)?b.duration:1E3,h=m(b.easing)?b.easing:df;return function(b,c){if(c.time<d)return c.animate=!0,c.viewHints[0]+=1,!0;if(c.time<d+g){var p=1-h((c.time-d)/g),q=e-c.viewState.center[0],r=f-c.viewState.center[1];c.animate=!0;c.viewState.center[0]+=p*q;c.viewState.center[1]+=p*r;c.viewHints[0]+=1;return!0}return!1}}
+function hf(b){var c=m(b.rotation)?b.rotation:0,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:df,g=m(b.anchor)?b.anchor:null;return function(b,k){if(k.time<d)return k.animate=!0,k.viewHints[0]+=1,!0;if(k.time<d+e){var n=1-f((k.time-d)/e),n=(c-k.viewState.rotation)*n;k.animate=!0;k.viewState.rotation+=n;if(null!==g){var p=k.viewState.center;p[0]-=g[0];p[1]-=g[1];Dd(p,n);yd(p,g)}k.viewHints[0]+=1;return!0}return!1}}
+function jf(b){var c=b.resolution,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:df;return function(b,h){if(h.time<d)return h.animate=!0,h.viewHints[0]+=1,!0;if(h.time<d+e){var k=1-f((h.time-d)/e),n=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*n;h.viewHints[0]+=1;return!0}return!1}};function kf(b,c,d,e){return m(e)?(e[0]=b,e[1]=c,e[2]=d,e):[b,c,d]}function lf(b,c,d){return b+"/"+c+"/"+d}function mf(b){var c=b[0],d=Array(c),e=1<<c-1,f,g;for(f=0;f<c;++f)g=48,b[1]&e&&(g+=1),b[2]&e&&(g+=2),d[f]=String.fromCharCode(g),e>>=1;return d.join("")}function nf(b){return lf(b[0],b[1],b[2])};function of(b,c,d,e){this.a=b;this.d=c;this.b=d;this.c=e}function pf(b,c,d,e,f){return m(f)?(f.a=b,f.d=c,f.b=d,f.c=e,f):new of(b,c,d,e)}of.prototype.contains=function(b){return qf(this,b[1],b[2])};function rf(b,c){return b.a<=c.a&&c.d<=b.d&&b.b<=c.b&&c.c<=b.c}function qf(b,c,d){return b.a<=c&&c<=b.d&&b.b<=d&&d<=b.c}function sf(b,c){return b.a==c.a&&b.b==c.b&&b.d==c.d&&b.c==c.c};function tf(b){this.c=b.html;this.a=m(b.tileRanges)?b.tileRanges:null}tf.prototype.b=function(){return this.c};var uf=!Hb||Hb&&9<=Vb;!Ib&&!Hb||Hb&&Hb&&9<=Vb||Ib&&Tb("1.9.1");Hb&&Tb("9");Fb("area base br col command embed hr img input keygen link meta param source track wbr".split(" "));Fb("action","cite","data","formaction","href","manifest","poster","src");Fb("embed","iframe","link","object","script","style","template");function vf(b,c){this.x=m(b)?b:0;this.y=m(c)?c:0}l=vf.prototype;l.clone=function(){return new vf(this.x,this.y)};l.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};l.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};l.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};l.scale=function(b,c){var d=ja(c)?c:b;this.x*=b;this.y*=d;return this};function wf(b,c){this.width=b;this.height=c}l=wf.prototype;l.clone=function(){return new wf(this.width,this.height)};l.ia=function(){return!(this.width*this.height)};l.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};l.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};l.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};
+l.scale=function(b,c){var d=ja(c)?c:b;this.width*=b;this.height*=d;return this};function xf(b){return b?new yf(zf(b)):ya||(ya=new yf)}function Af(b){var c=document;return ia(b)?c.getElementById(b):b}function Cf(b,c){ob(c,function(c,e){"style"==e?b.style.cssText=c:"class"==e?b.className=c:"for"==e?b.htmlFor=c:e in Df?b.setAttribute(Df[e],c):0==e.lastIndexOf("aria-",0)||0==e.lastIndexOf("data-",0)?b.setAttribute(e,c):b[e]=c})}
+var Df={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};function Ef(b){b=b.document.documentElement;return new wf(b.clientWidth,b.clientHeight)}
+function Ff(b,c,d){var e=arguments,f=document,g=e[0],h=e[1];if(!uf&&h&&(h.name||h.type)){g=["<",g];h.name&&g.push(' name="',Ba(h.name),'"');if(h.type){g.push(' type="',Ba(h.type),'"');var k={};Eb(k,h);delete k.type;h=k}g.push(">");g=g.join("")}g=f.createElement(g);h&&(ia(h)?g.className=h:ga(h)?g.className=h.join(" "):Cf(g,h));2<e.length&&Gf(f,g,e,2);return g}
+function Gf(b,c,d,e){function f(d){d&&c.appendChild(ia(d)?b.createTextNode(d):d)}for(;e<d.length;e++){var g=d[e];!ha(g)||la(g)&&0<g.nodeType?f(g):Ta(Hf(g)?cb(g):g,f)}}function If(b){return document.createElement(b)}function Jf(b,c){Gf(zf(b),b,arguments,1)}function Kf(b){for(var c;c=b.firstChild;)b.removeChild(c)}function Lf(b,c){c.parentNode&&c.parentNode.insertBefore(b,c.nextSibling)}function Mf(b,c,d){b.insertBefore(c,b.childNodes[d]||null)}
+function Nf(b){b&&b.parentNode&&b.parentNode.removeChild(b)}function Of(b){if(void 0!=b.firstElementChild)b=b.firstElementChild;else for(b=b.firstChild;b&&1!=b.nodeType;)b=b.nextSibling;return b}function Pf(b,c){if(b.contains&&1==c.nodeType)return b==c||b.contains(c);if("undefined"!=typeof b.compareDocumentPosition)return b==c||Boolean(b.compareDocumentPosition(c)&16);for(;c&&b!=c;)c=c.parentNode;return c==b}function zf(b){return 9==b.nodeType?b:b.ownerDocument||b.document}
+function Qf(b,c){if("textContent"in b)b.textContent=c;else if(3==b.nodeType)b.data=c;else if(b.firstChild&&3==b.firstChild.nodeType){for(;b.lastChild!=b.firstChild;)b.removeChild(b.lastChild);b.firstChild.data=c}else Kf(b),b.appendChild(zf(b).createTextNode(String(c)))}function Hf(b){if(b&&"number"==typeof b.length){if(la(b))return"function"==typeof b.item||"string"==typeof b.item;if(ka(b))return"function"==typeof b.item}return!1}function yf(b){this.a=b||ba.document||document}
+function Rf(){return!0}function Sf(b){var c=b.a;b=Jb?c.body||c.documentElement:c.documentElement;c=c.parentWindow||c.defaultView;return Hb&&Tb("10")&&c.pageYOffset!=b.scrollTop?new vf(b.scrollLeft,b.scrollTop):new vf(c.pageXOffset||b.scrollLeft,c.pageYOffset||b.scrollTop)}yf.prototype.appendChild=function(b,c){b.appendChild(c)};yf.prototype.contains=Pf;function Tf(b,c){var d=If("CANVAS");m(b)&&(d.width=b);m(c)&&(d.height=c);return d.getContext("2d")}
+var Uf=function(){var b;return function(){if(!m(b))if(ba.getComputedStyle){var c=If("P"),d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate(1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Nf(c);b=d&&"none"!==d}else b=!1;return b}}(),Vf=function(){var b;return function(){if(!m(b))if(ba.getComputedStyle){var c=If("P"),
+d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate3d(1px,1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Nf(c);b=d&&"none"!==d}else b=!1;return b}}();function Wf(b,c){var d=b.style;d.WebkitTransform=c;d.MozTransform=c;d.a=c;d.msTransform=c;d.transform=c;Hb&&!Xb&&(b.style.transformOrigin="0 0")}
+function Xf(b,c){var d;if(Vf()){if(m(6)){var e=Array(16);for(d=0;16>d;++d)e[d]=c[d].toFixed(6);d=e.join(",")}else d=c.join(",");Wf(b,"matrix3d("+d+")")}else if(Uf()){e=[c[0],c[1],c[4],c[5],c[12],c[13]];if(m(6)){var f=Array(6);for(d=0;6>d;++d)f[d]=e[d].toFixed(6);d=f.join(",")}else d=e.join(",");Wf(b,"matrix("+d+")")}else b.style.left=Math.round(c[12])+"px",b.style.top=Math.round(c[13])+"px"};var Yf=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function Zf(b,c){var d,e,f=Yf.length;for(e=0;e<f;++e)try{if(d=b.getContext(Yf[e],c),null!==d)return d}catch(g){}return null};var $f,ag=ba.devicePixelRatio||1,bg="ArrayBuffer"in ba,cg=!1,dg=function(){if(!("HTMLCanvasElement"in ba))return!1;try{var b=Tf();if(null===b)return!1;m(b.setLineDash)&&(cg=!0);return!0}catch(c){return!1}}(),eg="DeviceOrientationEvent"in ba,fg="geolocation"in ba.navigator,gg="ontouchstart"in ba,hg="PointerEvent"in ba,ig=!!ba.navigator.msPointerEnabled,jg=!1,kg,lg=[];
+if("WebGLRenderingContext"in ba)try{var mg=If("CANVAS"),ng=Zf(mg,{bh:!0});null!==ng&&(jg=!0,kg=ng.getParameter(ng.MAX_TEXTURE_SIZE),lg=ng.getSupportedExtensions())}catch(og){}$f=jg;wa=lg;va=kg;function pg(b,c,d){uc.call(this,b,d);this.element=c}u(pg,uc);function B(b){td.call(this);this.a=b||[];qg(this)}u(B,td);l=B.prototype;l.clear=function(){for(;0<this.Ib();)this.pop()};l.qf=function(b){var c,d;c=0;for(d=b.length;c<d;++c)this.push(b[c]);return this};l.forEach=function(b,c){Ta(this.a,b,c)};l.Mi=function(){return this.a};l.item=function(b){return this.a[b]};l.Ib=function(){return this.get("length")};l.rd=function(b,c){eb(this.a,b,0,c);qg(this);this.dispatchEvent(new pg("add",c,this))};
+l.pop=function(){return this.De(this.Ib()-1)};l.push=function(b){var c=this.a.length;this.rd(c,b);return c};l.remove=function(b){var c=this.a,d,e;d=0;for(e=c.length;d<e;++d)if(c[d]===b)return this.De(d)};l.De=function(b){var c=this.a[b];Sa.splice.call(this.a,b,1);qg(this);this.dispatchEvent(new pg("remove",c,this));return c};
+l.bl=function(b,c){var d=this.Ib();if(b<d)d=this.a[b],this.a[b]=c,this.dispatchEvent(new pg("remove",d,this)),this.dispatchEvent(new pg("add",c,this));else{for(;d<b;++d)this.rd(d,void 0);this.rd(b,c)}};function qg(b){b.set("length",b.a.length)};var rg=/^#(?:[0-9a-f]{3}){1,2}$/i,sg=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i,tg=/^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;function ug(b){return ga(b)?b:vg(b)}function wg(b){if(!ia(b)){var c=b[0];c!=(c|0)&&(c=c+.5|0);var d=b[1];d!=(d|0)&&(d=d+.5|0);var e=b[2];e!=(e|0)&&(e=e+.5|0);b="rgba("+c+","+d+","+e+","+b[3]+")"}return b}
+var vg=function(){var b={},c=0;return function(d){var e;if(b.hasOwnProperty(d))e=b[d];else{if(1024<=c){e=0;for(var f in b)0===(e++&3)&&(delete b[f],--c)}var g,h;rg.exec(d)?(h=3==d.length-1?1:2,e=parseInt(d.substr(1+0*h,h),16),f=parseInt(d.substr(1+1*h,h),16),g=parseInt(d.substr(1+2*h,h),16),1==h&&(e=(e<<4)+e,f=(f<<4)+f,g=(g<<4)+g),e=[e,f,g,1]):(h=tg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),h=Number(h[4]),e=[e,f,g,h],e=xg(e,e)):(h=sg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),
+e=[e,f,g,1],e=xg(e,e)):e=void 0;b[d]=e;++c}return e}}();function xg(b,c){var d=m(c)?c:[];d[0]=Yb(b[0]+.5|0,0,255);d[1]=Yb(b[1]+.5|0,0,255);d[2]=Yb(b[2]+.5|0,0,255);d[3]=Yb(b[3],0,1);return d};function yg(){this.g=Kd();this.c=void 0;this.a=Kd();this.d=void 0;this.b=Kd();this.f=void 0;this.e=Kd();this.i=void 0;this.o=Kd()}
+function zg(b,c,d,e,f){var g=!1;m(c)&&c!==b.c&&(g=b.a,Od(g),g[12]=c,g[13]=c,g[14]=c,g[15]=1,b.c=c,g=!0);if(m(d)&&d!==b.d){g=b.b;Od(g);g[0]=d;g[5]=d;g[10]=d;g[15]=1;var h=-.5*d+.5;g[12]=h;g[13]=h;g[14]=h;g[15]=1;b.d=d;g=!0}m(e)&&e!==b.f&&(g=Math.cos(e),h=Math.sin(e),Ld(b.e,.213+.787*g-.213*h,.213-.213*g+.143*h,.213-.213*g-.787*h,0,.715-.715*g-.715*h,.715+.285*g+.14*h,.715-.715*g+.715*h,0,.072-.072*g+.928*h,.072-.072*g-.283*h,.072+.928*g+.072*h,0,0,0,0,1),b.f=e,g=!0);m(f)&&f!==b.i&&(Ld(b.o,.213+.787*
+f,.213-.213*f,.213-.213*f,0,.715-.715*f,.715+.285*f,.715-.715*f,0,.072-.072*f,.072-.072*f,.072+.928*f,0,0,0,0,1),b.i=f,g=!0);g&&(g=b.g,Od(g),m(d)&&Pd(g,b.b,g),m(c)&&Pd(g,b.a,g),m(f)&&Pd(g,b.o,g),m(e)&&Pd(g,b.e,g));return b.g};function Ag(b){if(b.classList)return b.classList;b=b.className;return ia(b)&&b.match(/\S+/g)||[]}function Bg(b,c){return b.classList?b.classList.contains(c):Za(Ag(b),c)}function Cg(b,c){b.classList?b.classList.add(c):Bg(b,c)||(b.className+=0<b.className.length?" "+c:c)}function Dg(b,c){b.classList?b.classList.remove(c):Bg(b,c)&&(b.className=Ua(Ag(b),function(b){return b!=c}).join(" "))}function Eg(b,c){Bg(b,c)?Dg(b,c):Cg(b,c)};function Fg(b,c,d,e){this.top=b;this.right=c;this.bottom=d;this.left=e}l=Fg.prototype;l.clone=function(){return new Fg(this.top,this.right,this.bottom,this.left)};l.contains=function(b){return this&&b?b instanceof Fg?b.left>=this.left&&b.right<=this.right&&b.top>=this.top&&b.bottom<=this.bottom:b.x>=this.left&&b.x<=this.right&&b.y>=this.top&&b.y<=this.bottom:!1};
+l.ceil=function(){this.top=Math.ceil(this.top);this.right=Math.ceil(this.right);this.bottom=Math.ceil(this.bottom);this.left=Math.ceil(this.left);return this};l.floor=function(){this.top=Math.floor(this.top);this.right=Math.floor(this.right);this.bottom=Math.floor(this.bottom);this.left=Math.floor(this.left);return this};l.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this};
+l.scale=function(b,c){var d=ja(c)?c:b;this.left*=b;this.right*=b;this.top*=d;this.bottom*=d;return this};function Gg(b,c,d,e){this.left=b;this.top=c;this.width=d;this.height=e}l=Gg.prototype;l.clone=function(){return new Gg(this.left,this.top,this.width,this.height)};l.contains=function(b){return b instanceof Gg?this.left<=b.left&&this.left+this.width>=b.left+b.width&&this.top<=b.top&&this.top+this.height>=b.top+b.height:b.x>=this.left&&b.x<=this.left+this.width&&b.y>=this.top&&b.y<=this.top+this.height};
+function Hg(b,c){var d=c.x<b.left?b.left-c.x:Math.max(c.x-(b.left+b.width),0),e=c.y<b.top?b.top-c.y:Math.max(c.y-(b.top+b.height),0);return d*d+e*e}l.distance=function(b){return Math.sqrt(Hg(this,b))};l.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};
+l.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};l.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};l.scale=function(b,c){var d=ja(c)?c:b;this.left*=b;this.width*=b;this.top*=d;this.height*=d;return this};function Ig(b,c){var d=zf(b);return d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(b,null))?d[c]||d.getPropertyValue(c)||"":""}function Jg(b,c){return Ig(b,c)||(b.currentStyle?b.currentStyle[c]:null)||b.style&&b.style[c]}function Kg(b,c,d){var e,f=Ib&&(Kb||Nb)&&Tb("1.9");c instanceof vf?(e=c.x,c=c.y):(e=c,c=d);b.style.left=Lg(e,f);b.style.top=Lg(c,f)}
+function Mg(b){var c;try{c=b.getBoundingClientRect()}catch(d){return{left:0,top:0,right:0,bottom:0}}Hb&&b.ownerDocument.body&&(b=b.ownerDocument,c.left-=b.documentElement.clientLeft+b.body.clientLeft,c.top-=b.documentElement.clientTop+b.body.clientTop);return c}
+function Ng(b){if(Hb&&!(Hb&&8<=Vb))return b.offsetParent;var c=zf(b),d=Jg(b,"position"),e="fixed"==d||"absolute"==d;for(b=b.parentNode;b&&b!=c;b=b.parentNode)if(d=Jg(b,"position"),e=e&&"static"==d&&b!=c.documentElement&&b!=c.body,!e&&(b.scrollWidth>b.clientWidth||b.scrollHeight>b.clientHeight||"fixed"==d||"absolute"==d||"relative"==d))return b;return null}
+function Og(b){if(1==b.nodeType){var c;if(b.getBoundingClientRect)c=Mg(b),c=new vf(c.left,c.top);else{c=Sf(xf(b));var d,e=zf(b),f=Jg(b,"position"),g=Ib&&e.getBoxObjectFor&&!b.getBoundingClientRect&&"absolute"==f&&(d=e.getBoxObjectFor(b))&&(0>d.screenX||0>d.screenY),h=new vf(0,0),k;d=e?zf(e):document;k=!Hb||Hb&&9<=Vb||Rf(xf(d))?d.documentElement:d.body;if(b!=k)if(b.getBoundingClientRect)d=Mg(b),e=Sf(xf(e)),h.x=d.left+e.x,h.y=d.top+e.y;else if(e.getBoxObjectFor&&!g)d=e.getBoxObjectFor(b),e=e.getBoxObjectFor(k),
+h.x=d.screenX-e.screenX,h.y=d.screenY-e.screenY;else{d=b;do{h.x+=d.offsetLeft;h.y+=d.offsetTop;d!=b&&(h.x+=d.clientLeft||0,h.y+=d.clientTop||0);if(Jb&&"fixed"==Jg(d,"position")){h.x+=e.body.scrollLeft;h.y+=e.body.scrollTop;break}d=d.offsetParent}while(d&&d!=b);if(Gb||Jb&&"absolute"==f)h.y-=e.body.offsetTop;for(d=b;(d=Ng(d))&&d!=e.body&&d!=k;)h.x-=d.scrollLeft,Gb&&"TR"==d.tagName||(h.y-=d.scrollTop)}c=new vf(h.x-c.x,h.y-c.y)}if(Ib&&!Tb(12)){b:{h=Qa();if(void 0===b.style[h]&&(h=(Jb?"Webkit":Ib?"Moz":
+Hb?"ms":Gb?"O":null)+Ra(h),void 0!==b.style[h])){h=(Jb?"-webkit":Ib?"-moz":Hb?"-ms":Gb?"-o":null)+"-transform";break b}h="transform"}b=(b=Jg(b,h)||Jg(b,"transform"))?(b=b.match(Pg))?new vf(parseFloat(b[1]),parseFloat(b[2])):new vf(0,0):new vf(0,0);b=new vf(c.x+b.x,c.y+b.y)}else b=c;return b}c=ka(b.fh);h=b;b.targetTouches&&b.targetTouches.length?h=b.targetTouches[0]:c&&b.a.targetTouches&&b.a.targetTouches.length&&(h=b.a.targetTouches[0]);return new vf(h.clientX,h.clientY)}
+function Lg(b,c){"number"==typeof b&&(b=(c?Math.round(b):b)+"px");return b}function Qg(b){var c=Rg;if("none"!=Jg(b,"display"))return c(b);var d=b.style,e=d.display,f=d.visibility,g=d.position;d.visibility="hidden";d.position="absolute";d.display="inline";b=c(b);d.display=e;d.position=g;d.visibility=f;return b}function Rg(b){var c=b.offsetWidth,d=b.offsetHeight,e=Jb&&!c&&!d;return m(c)&&!e||!b.getBoundingClientRect?new wf(c,d):(b=Mg(b),new wf(b.right-b.left,b.bottom-b.top))}
+function Sg(b,c){b.style.display=c?"":"none"}function Tg(b,c,d,e){if(/^\d+px?$/.test(c))return parseInt(c,10);var f=b.style[d],g=b.runtimeStyle[d];b.runtimeStyle[d]=b.currentStyle[d];b.style[d]=c;c=b.style[e];b.style[d]=f;b.runtimeStyle[d]=g;return c}function Ug(b,c){var d=b.currentStyle?b.currentStyle[c]:null;return d?Tg(b,d,"left","pixelLeft"):0}
+function Vg(b,c){if(Hb){var d=Ug(b,c+"Left"),e=Ug(b,c+"Right"),f=Ug(b,c+"Top"),g=Ug(b,c+"Bottom");return new Fg(f,e,g,d)}d=Ig(b,c+"Left");e=Ig(b,c+"Right");f=Ig(b,c+"Top");g=Ig(b,c+"Bottom");return new Fg(parseFloat(f),parseFloat(e),parseFloat(g),parseFloat(d))}var Wg={thin:2,medium:4,thick:6};function Xg(b,c){if("none"==(b.currentStyle?b.currentStyle[c+"Style"]:null))return 0;var d=b.currentStyle?b.currentStyle[c+"Width"]:null;return d in Wg?Wg[d]:Tg(b,d,"left","pixelLeft")}
+function Yg(b){if(Hb&&!(Hb&&9<=Vb)){var c=Xg(b,"borderLeft"),d=Xg(b,"borderRight"),e=Xg(b,"borderTop");b=Xg(b,"borderBottom");return new Fg(e,d,b,c)}c=Ig(b,"borderLeftWidth");d=Ig(b,"borderRightWidth");e=Ig(b,"borderTopWidth");b=Ig(b,"borderBottomWidth");return new Fg(parseFloat(e),parseFloat(d),parseFloat(b),parseFloat(c))}var Pg=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;function Zg(b,c,d){uc.call(this,b);this.map=c;this.frameState=m(d)?d:null}u(Zg,uc);function $g(b){td.call(this);this.element=m(b.element)?b.element:null;this.i=m(b.target)?Af(b.target):null;this.a=null;this.n=[];this.render=m(b.render)?b.render:ca}u($g,td);$g.prototype.M=function(){Nf(this.element);$g.S.M.call(this)};$g.prototype.d=function(){return this.a};
+$g.prototype.setMap=function(b){null===this.a||Nf(this.element);0!=this.n.length&&(Ta(this.n,Yc),this.n.length=0);this.a=b;null!==this.a&&((null===this.i?b.t:this.i).appendChild(this.element),this.render!==ca&&this.n.push(z(b,"postrender",this.render,!1,this)),b.render())};function ah(b){b=m(b)?b:{};this.q=If("UL");this.j=If("LI");this.q.appendChild(this.j);Sg(this.j,!1);this.b=m(b.collapsed)?b.collapsed:!0;this.f=m(b.collapsible)?b.collapsible:!0;this.f||(this.b=!1);var c=m(b.className)?b.className:"ol-attribution",d=m(b.tipLabel)?b.tipLabel:"Attributions";this.r=m(b.collapseLabel)?b.collapseLabel:"\u00bb";this.F=m(b.label)?b.label:"i";this.t=Ff("SPAN",{},this.f&&!this.b?this.r:this.F);d=Ff("BUTTON",{type:"button",title:d},this.t);z(d,"click",this.cj,!1,this);z(d,
+["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control"+(this.b&&this.f?" ol-collapsed":"")+(this.f?"":" ol-uncollapsible"),this.q,d);$g.call(this,{element:c,render:m(b.render)?b.render:bh,target:b.target});this.p=!0;this.g={};this.e={};this.J={}}u(ah,$g);
+function bh(b){b=b.frameState;if(null===b)this.p&&(Sg(this.element,!1),this.p=!1);else{var c,d,e,f,g,h,k,n,p,q=b.layerStatesArray,r=Cb(b.attributions),s={};d=0;for(c=q.length;d<c;d++)if(e=q[d].layer.a(),null!==e&&(p=ma(e).toString(),n=e.e,null!==n))for(e=0,f=n.length;e<f;e++)if(h=n[e],k=ma(h).toString(),!(k in r)){g=b.usedTiles[p];var v;if(v=m(g))a:if(null===h.a)v=!0;else{var y=v=void 0,C=void 0,F=void 0;for(F in g)if(F in h.a)for(C=g[F],v=0,y=h.a[F].length;v<y;++v){var G=h.a[F][v];if(G.a<=C.d&&G.d>=
+C.a&&G.b<=C.c&&G.c>=C.b){v=!0;break a}}v=!1}v?(k in s&&delete s[k],r[k]=h):s[k]=h}c=[r,s];d=c[0];c=c[1];for(var w in this.g)w in d?(this.e[w]||(Sg(this.g[w],!0),this.e[w]=!0),delete d[w]):w in c?(this.e[w]&&(Sg(this.g[w],!1),delete this.e[w]),delete c[w]):(Nf(this.g[w]),delete this.g[w],delete this.e[w]);for(w in d)p=If("LI"),p.innerHTML=d[w].c,this.q.appendChild(p),this.g[w]=p,this.e[w]=!0;for(w in c)p=If("LI"),p.innerHTML=c[w].c,Sg(p,!1),this.q.appendChild(p),this.g[w]=p;w=!xb(this.e)||!xb(b.logos);
+this.p!=w&&(Sg(this.element,w),this.p=w);w&&xb(this.e)?Cg(this.element,"ol-logo-only"):Dg(this.element,"ol-logo-only");var U;b=b.logos;w=this.J;for(U in w)U in b||(Nf(w[U]),delete w[U]);for(var N in b)N in w||(U=new Image,U.src=N,d=b[N],""===d?d=U:(d=Ff("A",{href:d}),d.appendChild(U)),this.j.appendChild(d),w[N]=d);Sg(this.j,!xb(b))}}l=ah.prototype;l.cj=function(b){b.preventDefault();ch(this)};function ch(b){Eg(b.element,"ol-collapsed");Qf(b.t,b.b?b.r:b.F);b.b=!b.b}l.bj=function(){return this.f};
+l.ej=function(b){this.f!==b&&(this.f=b,Eg(this.element,"ol-uncollapsible"),!b&&this.b&&ch(this))};l.dj=function(b){this.f&&this.b!==b&&ch(this)};l.aj=function(){return this.b};function dh(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-rotate";this.b=Ff("SPAN","ol-compass",m(b.label)?b.label:"\u21e7");var d=Ff("BUTTON",{"class":c+"-reset",type:"button",title:m(b.tipLabel)?b.tipLabel:"Reset rotation"},this.b);z(d,"click",dh.prototype.j,!1,this);z(d,["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control",d);$g.call(this,{element:c,render:m(b.render)?b.render:eh,target:b.target});this.f=m(b.duration)?b.duration:250;this.e=m(b.autoHide)?b.autoHide:
+!0;this.g=void 0;this.e&&Cg(this.element,"ol-hidden")}u(dh,$g);dh.prototype.j=function(b){b.preventDefault();b=this.a;var c=b.a();if(null!==c){for(var d=c.e();d<-Math.PI;)d+=2*Math.PI;for(;d>Math.PI;)d-=2*Math.PI;m(d)&&(0<this.f&&b.Ua(hf({rotation:d,duration:this.f,easing:cf})),c.r(0))}};
+function eh(b){b=b.frameState;if(null!==b){b=b.viewState.rotation;if(b!=this.g){var c="rotate("+180*b/Math.PI+"deg)";if(this.e){var d=this.element;0===b?Cg(d,"ol-hidden"):Dg(d,"ol-hidden")}this.b.style.msTransform=c;this.b.style.webkitTransform=c;this.b.style.transform=c}this.g=b}};function fh(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-zoom",d=m(b.delta)?b.delta:1,e=m(b.zoomOutLabel)?b.zoomOutLabel:"\u2212",f=m(b.zoomOutTipLabel)?b.zoomOutTipLabel:"Zoom out",g=Ff("BUTTON",{"class":c+"-in",type:"button",title:m(b.zoomInTipLabel)?b.zoomInTipLabel:"Zoom in"},m(b.zoomInLabel)?b.zoomInLabel:"+");z(g,"click",ta(fh.prototype.e,d),!1,this);z(g,["mouseout",xc],function(){this.blur()},!1);e=Ff("BUTTON",{"class":c+"-out",type:"button",title:f},e);z(e,"click",ta(fh.prototype.e,
+-d),!1,this);z(e,["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control",g,e);$g.call(this,{element:c,target:b.target});this.b=m(b.duration)?b.duration:250}u(fh,$g);fh.prototype.e=function(b,c){c.preventDefault();var d=this.a,e=d.a();if(null!==e){var f=e.b();m(f)&&(0<this.b&&d.Ua(jf({resolution:f,duration:this.b,easing:cf})),d=e.constrainResolution(f,b),e.d(d))}};function gh(b){b=m(b)?b:{};var c=new B;(m(b.zoom)?b.zoom:1)&&c.push(new fh(b.zoomOptions));(m(b.rotate)?b.rotate:1)&&c.push(new dh(b.rotateOptions));(m(b.attribution)?b.attribution:1)&&c.push(new ah(b.attributionOptions));return c};var hh=Jb?"webkitfullscreenchange":Ib?"mozfullscreenchange":Hb?"MSFullscreenChange":"fullscreenchange";function ih(){var b=xf().a,c=b.body;return!!(c.webkitRequestFullscreen||c.mozRequestFullScreen&&b.mozFullScreenEnabled||c.msRequestFullscreen&&b.msFullscreenEnabled||c.requestFullscreen&&b.fullscreenEnabled)}
+function jh(b){b.webkitRequestFullscreen?b.webkitRequestFullscreen():b.mozRequestFullScreen?b.mozRequestFullScreen():b.msRequestFullscreen?b.msRequestFullscreen():b.requestFullscreen&&b.requestFullscreen()}function kh(){var b=xf().a;return!!(b.webkitIsFullScreen||b.mozFullScreen||b.msFullscreenElement||b.fullscreenElement)};function lh(b){b=m(b)?b:{};this.b=m(b.className)?b.className:"ol-full-screen";var c=m(b.tipLabel)?b.tipLabel:"Toggle full-screen",c=Ff("BUTTON",{"class":this.b+"-"+kh(),type:"button",title:c});z(c,"click",this.g,!1,this);z(c,["mouseout",xc],function(){this.blur()},!1);z(ba.document,hh,this.e,!1,this);var d=this.b+" ol-unselectable ol-control "+(ih()?"":"ol-unsupported"),c=Ff("DIV",d,c);$g.call(this,{element:c,target:b.target});this.f=m(b.keys)?b.keys:!1}u(lh,$g);
+lh.prototype.g=function(b){b.preventDefault();ih()&&(b=this.a,null!==b&&(kh()?(b=xf().a,b.webkitCancelFullScreen?b.webkitCancelFullScreen():b.mozCancelFullScreen?b.mozCancelFullScreen():b.msExitFullscreen?b.msExitFullscreen():b.exitFullscreen&&b.exitFullscreen()):(b=b.qc(),b=Af(b),this.f?b.mozRequestFullScreenWithKeys?b.mozRequestFullScreenWithKeys():b.webkitRequestFullscreen?b.webkitRequestFullscreen():jh(b):jh(b))))};
+lh.prototype.e=function(){var b=this.b+"-true",c=this.b+"-false",d=Of(this.element),e=this.a;kh()?Bg(d,c)&&(Dg(d,c),Cg(d,b)):Bg(d,b)&&(Dg(d,b),Cg(d,c));null===e||e.j()};function mh(b){b=m(b)?b:{};var c=Ff("DIV",m(b.className)?b.className:"ol-mouse-position");$g.call(this,{element:c,render:m(b.render)?b.render:nh,target:b.target});z(this,xd("projection"),this.J,!1,this);m(b.coordinateFormat)&&this.r(b.coordinateFormat);m(b.projection)&&this.q(Ee(b.projection));this.Q=m(b.undefinedHTML)?b.undefinedHTML:"";this.j=c.innerHTML;this.f=this.e=this.b=null}u(mh,$g);
+function nh(b){b=b.frameState;null===b?this.b=null:this.b!=b.viewState.projection&&(this.b=b.viewState.projection,this.e=null);oh(this,this.f)}mh.prototype.J=function(){this.e=null};mh.prototype.g=function(){return this.get("coordinateFormat")};mh.prototype.getCoordinateFormat=mh.prototype.g;mh.prototype.p=function(){return this.get("projection")};mh.prototype.getProjection=mh.prototype.p;mh.prototype.t=function(b){this.f=this.a.ad(b.a);oh(this,this.f)};
+mh.prototype.F=function(){oh(this,null);this.f=null};mh.prototype.setMap=function(b){mh.S.setMap.call(this,b);null!==b&&(b=b.b,this.n.push(z(b,"mousemove",this.t,!1,this),z(b,"mouseout",this.F,!1,this)))};mh.prototype.r=function(b){this.set("coordinateFormat",b)};mh.prototype.setCoordinateFormat=mh.prototype.r;mh.prototype.q=function(b){this.set("projection",b)};mh.prototype.setProjection=mh.prototype.q;
+function oh(b,c){var d=b.Q;if(null!==c&&null!==b.b){if(null===b.e){var e=b.p();b.e=m(e)?De(b.b,e):We}e=b.a.Ga(c);null!==e&&(b.e(e,e),d=b.g(),d=m(d)?d(e):e.toString())}m(b.j)&&d==b.j||(b.element.innerHTML=d,b.j=d)};function ph(b){if("function"==typeof b.kb)return b.kb();if(ia(b))return b.split("");if(ha(b)){for(var c=[],d=b.length,e=0;e<d;e++)c.push(b[e]);return c}return rb(b)}
+function qh(b,c){if("function"==typeof b.forEach)b.forEach(c,void 0);else if(ha(b)||ia(b))Ta(b,c,void 0);else{var d;if("function"==typeof b.I)d=b.I();else if("function"!=typeof b.kb)if(ha(b)||ia(b)){d=[];for(var e=b.length,f=0;f<e;f++)d.push(f)}else d=sb(b);else d=void 0;for(var e=ph(b),f=e.length,g=0;g<f;g++)c.call(void 0,e[g],d&&d[g],b)}};function rh(b,c){this.c={};this.a=[];this.b=0;var d=arguments.length;if(1<d){if(d%2)throw Error("Uneven number of arguments");for(var e=0;e<d;e+=2)this.set(arguments[e],arguments[e+1])}else if(b){b instanceof rh?(d=b.I(),e=b.kb()):(d=sb(b),e=rb(b));for(var f=0;f<d.length;f++)this.set(d[f],e[f])}}l=rh.prototype;l.Ub=function(){return this.b};l.kb=function(){sh(this);for(var b=[],c=0;c<this.a.length;c++)b.push(this.c[this.a[c]]);return b};l.I=function(){sh(this);return this.a.concat()};
+l.ia=function(){return 0==this.b};l.clear=function(){this.c={};this.b=this.a.length=0};l.remove=function(b){return th(this.c,b)?(delete this.c[b],this.b--,this.a.length>2*this.b&&sh(this),!0):!1};function sh(b){if(b.b!=b.a.length){for(var c=0,d=0;c<b.a.length;){var e=b.a[c];th(b.c,e)&&(b.a[d++]=e);c++}b.a.length=d}if(b.b!=b.a.length){for(var f={},d=c=0;c<b.a.length;)e=b.a[c],th(f,e)||(b.a[d++]=e,f[e]=1),c++;b.a.length=d}}l.get=function(b,c){return th(this.c,b)?this.c[b]:c};
+l.set=function(b,c){th(this.c,b)||(this.b++,this.a.push(b));this.c[b]=c};l.forEach=function(b,c){for(var d=this.I(),e=0;e<d.length;e++){var f=d[e],g=this.get(f);b.call(c,g,f,this)}};l.clone=function(){return new rh(this)};function th(b,c){return Object.prototype.hasOwnProperty.call(b,c)};var uh=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/;function vh(b){if(wh){wh=!1;var c=ba.location;if(c){var d=c.href;if(d&&(d=(d=vh(d)[3]||null)?decodeURI(d):d)&&d!=c.hostname)throw wh=!0,Error();}}return b.match(uh)}var wh=Jb;function xh(b){if(b[1]){var c=b[0],d=c.indexOf("#");0<=d&&(b.push(c.substr(d)),b[0]=c=c.substr(0,d));d=c.indexOf("?");0>d?b[1]="?":d==c.length-1&&(b[1]=void 0)}return b.join("")}
+function yh(b,c,d){if(ga(c))for(var e=0;e<c.length;e++)yh(b,String(c[e]),d);else null!=c&&d.push("&",b,""===c?"":"=",encodeURIComponent(String(c)))}function zh(b,c){for(var d in c)yh(d,c[d],b);return b};function Ah(b,c){var d;b instanceof Ah?(this.Wb=m(c)?c:b.Wb,Bh(this,b.Pb),this.cc=b.cc,this.pb=b.pb,Ch(this,b.sc),this.nb=b.nb,Dh(this,b.a.clone()),this.Tb=b.Tb):b&&(d=vh(String(b)))?(this.Wb=!!c,Bh(this,d[1]||"",!0),this.cc=Fh(d[2]||""),this.pb=Fh(d[3]||"",!0),Ch(this,d[4]),this.nb=Fh(d[5]||"",!0),Dh(this,d[6]||"",!0),this.Tb=Fh(d[7]||"")):(this.Wb=!!c,this.a=new Gh(null,0,this.Wb))}l=Ah.prototype;l.Pb="";l.cc="";l.pb="";l.sc=null;l.nb="";l.Tb="";l.Wb=!1;
+l.toString=function(){var b=[],c=this.Pb;c&&b.push(Hh(c,Ih,!0),":");if(c=this.pb){b.push("//");var d=this.cc;d&&b.push(Hh(d,Ih,!0),"@");b.push(encodeURIComponent(String(c)).replace(/%25([0-9a-fA-F]{2})/g,"%$1"));c=this.sc;null!=c&&b.push(":",String(c))}if(c=this.nb)this.pb&&"/"!=c.charAt(0)&&b.push("/"),b.push(Hh(c,"/"==c.charAt(0)?Jh:Kh,!0));(c=this.a.toString())&&b.push("?",c);(c=this.Tb)&&b.push("#",Hh(c,Lh));return b.join("")};l.clone=function(){return new Ah(this)};
+function Bh(b,c,d){b.Pb=d?Fh(c,!0):c;b.Pb&&(b.Pb=b.Pb.replace(/:$/,""))}function Ch(b,c){if(c){c=Number(c);if(isNaN(c)||0>c)throw Error("Bad port number "+c);b.sc=c}else b.sc=null}function Dh(b,c,d){c instanceof Gh?(b.a=c,Mh(b.a,b.Wb)):(d||(c=Hh(c,Nh)),b.a=new Gh(c,0,b.Wb))}function Oh(b){return b instanceof Ah?b.clone():new Ah(b,void 0)}
+function Ph(b,c){b instanceof Ah||(b=Oh(b));c instanceof Ah||(c=Oh(c));var d=b,e=c,f=d.clone(),g=!!e.Pb;g?Bh(f,e.Pb):g=!!e.cc;g?f.cc=e.cc:g=!!e.pb;g?f.pb=e.pb:g=null!=e.sc;var h=e.nb;if(g)Ch(f,e.sc);else if(g=!!e.nb)if("/"!=h.charAt(0)&&(d.pb&&!d.nb?h="/"+h:(d=f.nb.lastIndexOf("/"),-1!=d&&(h=f.nb.substr(0,d+1)+h))),d=h,".."==d||"."==d)h="";else if(-1!=d.indexOf("./")||-1!=d.indexOf("/.")){for(var h=0==d.lastIndexOf("/",0),d=d.split("/"),k=[],n=0;n<d.length;){var p=d[n++];"."==p?h&&n==d.length&&k.push(""):
+".."==p?((1<k.length||1==k.length&&""!=k[0])&&k.pop(),h&&n==d.length&&k.push("")):(k.push(p),h=!0)}h=k.join("/")}else h=d;g?f.nb=h:g=""!==e.a.toString();g?Dh(f,Fh(e.a.toString())):g=!!e.Tb;g&&(f.Tb=e.Tb);return f}function Fh(b,c){return b?c?decodeURI(b):decodeURIComponent(b):""}function Hh(b,c,d){return ia(b)?(b=encodeURI(b).replace(c,Qh),d&&(b=b.replace(/%25([0-9a-fA-F]{2})/g,"%$1")),b):null}function Qh(b){b=b.charCodeAt(0);return"%"+(b>>4&15).toString(16)+(b&15).toString(16)}
+var Ih=/[#\/\?@]/g,Kh=/[\#\?:]/g,Jh=/[\#\?]/g,Nh=/[\#\?@]/g,Lh=/#/g;function Gh(b,c,d){this.a=b||null;this.c=!!d}function Rh(b){if(!b.da&&(b.da=new rh,b.ta=0,b.a))for(var c=b.a.split("&"),d=0;d<c.length;d++){var e=c[d].indexOf("="),f=null,g=null;0<=e?(f=c[d].substring(0,e),g=c[d].substring(e+1)):f=c[d];f=decodeURIComponent(f.replace(/\+/g," "));f=Sh(b,f);b.add(f,g?decodeURIComponent(g.replace(/\+/g," ")):"")}}l=Gh.prototype;l.da=null;l.ta=null;l.Ub=function(){Rh(this);return this.ta};
+l.add=function(b,c){Rh(this);this.a=null;b=Sh(this,b);var d=this.da.get(b);d||this.da.set(b,d=[]);d.push(c);this.ta++;return this};l.remove=function(b){Rh(this);b=Sh(this,b);return th(this.da.c,b)?(this.a=null,this.ta-=this.da.get(b).length,this.da.remove(b)):!1};l.clear=function(){this.da=this.a=null;this.ta=0};l.ia=function(){Rh(this);return 0==this.ta};function Th(b,c){Rh(b);c=Sh(b,c);return th(b.da.c,c)}
+l.I=function(){Rh(this);for(var b=this.da.kb(),c=this.da.I(),d=[],e=0;e<c.length;e++)for(var f=b[e],g=0;g<f.length;g++)d.push(c[e]);return d};l.kb=function(b){Rh(this);var c=[];if(ia(b))Th(this,b)&&(c=bb(c,this.da.get(Sh(this,b))));else{b=this.da.kb();for(var d=0;d<b.length;d++)c=bb(c,b[d])}return c};l.set=function(b,c){Rh(this);this.a=null;b=Sh(this,b);Th(this,b)&&(this.ta-=this.da.get(b).length);this.da.set(b,[c]);this.ta++;return this};
+l.get=function(b,c){var d=b?this.kb(b):[];return 0<d.length?String(d[0]):c};function Uh(b,c,d){b.remove(c);0<d.length&&(b.a=null,b.da.set(Sh(b,c),cb(d)),b.ta+=d.length)}l.toString=function(){if(this.a)return this.a;if(!this.da)return"";for(var b=[],c=this.da.I(),d=0;d<c.length;d++)for(var e=c[d],f=encodeURIComponent(String(e)),e=this.kb(e),g=0;g<e.length;g++){var h=f;""!==e[g]&&(h+="="+encodeURIComponent(String(e[g])));b.push(h)}return this.a=b.join("&")};
+l.clone=function(){var b=new Gh;b.a=this.a;this.da&&(b.da=this.da.clone(),b.ta=this.ta);return b};function Sh(b,c){var d=String(c);b.c&&(d=d.toLowerCase());return d}function Mh(b,c){c&&!b.c&&(Rh(b),b.a=null,b.da.forEach(function(b,c){var f=c.toLowerCase();c!=f&&(this.remove(c),Uh(this,f,b))},b));b.c=c};function Vh(b,c,d){pc.call(this);this.d=b;this.b=d;this.a=c||window;this.c=sa(this.Ye,this)}u(Vh,pc);l=Vh.prototype;l.X=null;l.Ie=!1;l.start=function(){Wh(this);this.Ie=!1;var b=Xh(this),c=Yh(this);b&&!c&&this.a.mozRequestAnimationFrame?(this.X=z(this.a,"MozBeforePaint",this.c),this.a.mozRequestAnimationFrame(null),this.Ie=!0):this.X=b&&c?b.call(this.a,this.c):this.a.setTimeout(gd(this.c),20)};
+function Wh(b){if(null!=b.X){var c=Xh(b),d=Yh(b);c&&!d&&b.a.mozRequestAnimationFrame?Yc(b.X):c&&d?d.call(b.a,b.X):b.a.clearTimeout(b.X)}b.X=null}l.Ye=function(){this.Ie&&this.X&&Yc(this.X);this.X=null;this.d.call(this.b,ua())};l.M=function(){Wh(this);Vh.S.M.call(this)};function Xh(b){b=b.a;return b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||b.oRequestAnimationFrame||b.msRequestAnimationFrame||null}
+function Yh(b){b=b.a;return b.cancelRequestAnimationFrame||b.webkitCancelRequestAnimationFrame||b.mozCancelRequestAnimationFrame||b.oCancelRequestAnimationFrame||b.msCancelRequestAnimationFrame||null};function Zh(b){ba.setTimeout(function(){throw b;},0)}function $h(b,c){var d=b;c&&(d=sa(b,c));d=ai(d);!ka(ba.setImmediate)||ba.Window&&ba.Window.prototype.setImmediate==ba.setImmediate?(bi||(bi=ci()),bi(d)):ba.setImmediate(d)}var bi;
+function ci(){var b=ba.MessageChannel;"undefined"===typeof b&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(b=function(){var b=document.createElement("iframe");b.style.display="none";b.src="";document.documentElement.appendChild(b);var c=b.contentWindow,b=c.document;b.open();b.write("");b.close();var d="callImmediate"+Math.random(),e="file:"==c.location.protocol?"*":c.location.protocol+"//"+c.location.host,b=sa(function(b){if(("*"==e||b.origin==e)&&b.data==d)this.port1.onmessage()},
+this);c.addEventListener("message",b,!1);this.port1={};this.port2={postMessage:function(){c.postMessage(d,e)}}});if("undefined"!==typeof b&&!nb("Trident")&&!nb("MSIE")){var c=new b,d={},e=d;c.port1.onmessage=function(){if(m(d.next)){d=d.next;var b=d.Ue;d.Ue=null;b()}};return function(b){e.next={Ue:b};e=e.next;c.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(b){var c=document.createElement("script");c.onreadystatechange=function(){c.onreadystatechange=
+null;c.parentNode.removeChild(c);c=null;b();b=null};document.documentElement.appendChild(c)}:function(b){ba.setTimeout(b,0)}}var ai=ed;function di(){this.a=ua()}new di;di.prototype.set=function(b){this.a=b};di.prototype.get=function(){return this.a};function ei(b){jd.call(this);this.Pc=b||window;this.ld=z(this.Pc,"resize",this.oi,!1,this);this.md=Ef(this.Pc||window)}u(ei,jd);l=ei.prototype;l.ld=null;l.Pc=null;l.md=null;l.M=function(){ei.S.M.call(this);this.ld&&(Yc(this.ld),this.ld=null);this.md=this.Pc=null};l.oi=function(){var b=Ef(this.Pc||window),c=this.md;b==c||b&&c&&b.width==c.width&&b.height==c.height||(this.md=b,this.dispatchEvent("resize"))};function fi(b,c,d,e,f){if(!(Hb||Jb&&Tb("525")))return!0;if(Kb&&f)return gi(b);if(f&&!e)return!1;ja(c)&&(c=hi(c));if(!d&&(17==c||18==c||Kb&&91==c))return!1;if(Jb&&e&&d)switch(b){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return!1}if(Hb&&e&&c==b)return!1;switch(b){case 13:return!0;case 27:return!Jb}return gi(b)}
+function gi(b){if(48<=b&&57>=b||96<=b&&106>=b||65<=b&&90>=b||Jb&&0==b)return!0;switch(b){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;default:return!1}}function hi(b){if(Ib)b=ii(b);else if(Kb&&Jb)a:switch(b){case 93:b=91;break a}return b}
+function ii(b){switch(b){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return b}};function ji(b,c){jd.call(this);b&&ki(this,b,c)}u(ji,jd);l=ji.prototype;l.aa=null;l.sd=null;l.le=null;l.td=null;l.Ka=-1;l.Gb=-1;l.ae=!1;
+var li={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},mi={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},ni=Hb||Jb&&Tb("525"),oi=Kb&&Ib;
+ji.prototype.a=function(b){Jb&&(17==this.Ka&&!b.o||18==this.Ka&&!b.c||Kb&&91==this.Ka&&!b.n)&&(this.Gb=this.Ka=-1);-1==this.Ka&&(b.o&&17!=b.e?this.Ka=17:b.c&&18!=b.e?this.Ka=18:b.n&&91!=b.e&&(this.Ka=91));ni&&!fi(b.e,this.Ka,b.d,b.o,b.c)?this.handleEvent(b):(this.Gb=hi(b.e),oi&&(this.ae=b.c))};ji.prototype.c=function(b){this.Gb=this.Ka=-1;this.ae=b.c};
+ji.prototype.handleEvent=function(b){var c=b.a,d,e,f=c.altKey;Hb&&"keypress"==b.type?(d=this.Gb,e=13!=d&&27!=d?c.keyCode:0):Jb&&"keypress"==b.type?(d=this.Gb,e=0<=c.charCode&&63232>c.charCode&&gi(d)?c.charCode:0):Gb?(d=this.Gb,e=gi(d)?c.keyCode:0):(d=c.keyCode||this.Gb,e=c.charCode||0,oi&&(f=this.ae),Kb&&63==e&&224==d&&(d=191));var g=d=hi(d),h=c.keyIdentifier;d?63232<=d&&d in li?g=li[d]:25==d&&b.d&&(g=9):h&&h in mi&&(g=mi[h]);this.Ka=g;b=new pi(g,e,0,c);b.c=f;this.dispatchEvent(b)};
+function ki(b,c,d){b.td&&qi(b);b.aa=c;b.sd=z(b.aa,"keypress",b,d);b.le=z(b.aa,"keydown",b.a,d,b);b.td=z(b.aa,"keyup",b.c,d,b)}function qi(b){b.sd&&(Yc(b.sd),Yc(b.le),Yc(b.td),b.sd=null,b.le=null,b.td=null);b.aa=null;b.Ka=-1;b.Gb=-1}ji.prototype.M=function(){ji.S.M.call(this);qi(this)};function pi(b,c,d,e){zc.call(this,e);this.type="key";this.e=b;this.i=c}u(pi,zc);function ri(b,c){jd.call(this);var d=this.aa=b;(d=la(d)&&1==d.nodeType?this.aa:this.aa?this.aa.body:null)&&Jg(d,"direction");this.a=z(this.aa,Ib?"DOMMouseScroll":"mousewheel",this,c)}u(ri,jd);
+ri.prototype.handleEvent=function(b){var c=0,d=0,e=0;b=b.a;if("mousewheel"==b.type){d=1;if(Hb||Jb&&(Lb||Tb("532.0")))d=40;e=si(-b.wheelDelta,d);m(b.wheelDeltaX)?(c=si(-b.wheelDeltaX,d),d=si(-b.wheelDeltaY,d)):d=e}else e=b.detail,100<e?e=3:-100>e&&(e=-3),m(b.axis)&&b.axis===b.HORIZONTAL_AXIS?c=e:d=e;ja(this.c)&&Yb(c,-this.c,this.c);ja(this.b)&&(d=Yb(d,-this.b,this.b));c=new ti(e,b,0,d);this.dispatchEvent(c)};function si(b,c){return Jb&&(Kb||Mb)&&0!=b%c?b:b/c}
+ri.prototype.M=function(){ri.S.M.call(this);Yc(this.a);this.a=null};function ti(b,c,d,e){zc.call(this,c);this.type="mousewheel";this.detail=b;this.j=e}u(ti,zc);function ui(b,c,d){uc.call(this,b);this.a=c;b=m(d)?d:{};this.buttons=vi(b);this.pressure=wi(b,this.buttons);this.bubbles=x(b,"bubbles",!1);this.cancelable=x(b,"cancelable",!1);this.view=x(b,"view",null);this.detail=x(b,"detail",null);this.screenX=x(b,"screenX",0);this.screenY=x(b,"screenY",0);this.clientX=x(b,"clientX",0);this.clientY=x(b,"clientY",0);this.button=x(b,"button",0);this.relatedTarget=x(b,"relatedTarget",null);this.pointerId=x(b,"pointerId",0);this.width=x(b,"width",0);this.height=x(b,
+"height",0);this.pointerType=x(b,"pointerType","");this.isPrimary=x(b,"isPrimary",!1);c.preventDefault&&(this.preventDefault=function(){c.preventDefault()})}u(ui,uc);function vi(b){if(b.buttons||xi)b=b.buttons;else switch(b.which){case 1:b=1;break;case 2:b=4;break;case 3:b=2;break;default:b=0}return b}function wi(b,c){var d=0;b.pressure?d=b.pressure:d=c?.5:0;return d}var xi=!1;try{xi=1===(new MouseEvent("click",{buttons:1})).buttons}catch(yi){};function zi(b,c){this.a=b;this.e=c};function Ai(b){zi.call(this,b,{mousedown:this.yi,mousemove:this.zi,mouseup:this.Ci,mouseover:this.Bi,mouseout:this.Ai});this.c=b.c;this.b=[]}u(Ai,zi);function Bi(b,c){for(var d=b.b,e=c.clientX,f=c.clientY,g=0,h=d.length,k;g<h&&(k=d[g]);g++){var n=Math.abs(f-k[1]);if(25>=Math.abs(e-k[0])&&25>=n)return!0}return!1}function Ci(b){var c=Di(b,b.a),d=c.preventDefault;c.preventDefault=function(){b.preventDefault();d()};c.pointerId=1;c.isPrimary=!0;c.pointerType="mouse";return c}l=Ai.prototype;
+l.yi=function(b){if(!Bi(this,b)){(1).toString()in this.c&&this.cancel(b);var c=Ci(b);this.c[(1).toString()]=b;Ei(this.a,Fi,c,b)}};l.zi=function(b){if(!Bi(this,b)){var c=Ci(b);Ei(this.a,Gi,c,b)}};l.Ci=function(b){if(!Bi(this,b)){var c=x(this.c,(1).toString());c&&c.button===b.button&&(c=Ci(b),Ei(this.a,Hi,c,b),zb(this.c,(1).toString()))}};l.Bi=function(b){if(!Bi(this,b)){var c=Ci(b);Ii(this.a,c,b)}};l.Ai=function(b){if(!Bi(this,b)){var c=Ci(b);Ji(this.a,c,b)}};
+l.cancel=function(b){var c=Ci(b);this.a.cancel(c,b);zb(this.c,(1).toString())};function Ki(b){zi.call(this,b,{MSPointerDown:this.Hi,MSPointerMove:this.Ii,MSPointerUp:this.Li,MSPointerOut:this.Ji,MSPointerOver:this.Ki,MSPointerCancel:this.Gi,MSGotPointerCapture:this.Ei,MSLostPointerCapture:this.Fi});this.c=b.c;this.b=["","unavailable","touch","pen","mouse"]}u(Ki,zi);function Li(b,c){var d=c;ja(c.a.pointerType)&&(d=Di(c,c.a),d.pointerType=b.b[c.a.pointerType]);return d}l=Ki.prototype;l.Hi=function(b){this.c[b.a.pointerId]=b;var c=Li(this,b);Ei(this.a,Fi,c,b)};
+l.Ii=function(b){var c=Li(this,b);Ei(this.a,Gi,c,b)};l.Li=function(b){var c=Li(this,b);Ei(this.a,Hi,c,b);zb(this.c,b.a.pointerId)};l.Ji=function(b){var c=Li(this,b);Ji(this.a,c,b)};l.Ki=function(b){var c=Li(this,b);Ii(this.a,c,b)};l.Gi=function(b){var c=Li(this,b);this.a.cancel(c,b);zb(this.c,b.a.pointerId)};l.Fi=function(b){this.a.dispatchEvent(new ui("lostpointercapture",b,b.a))};l.Ei=function(b){this.a.dispatchEvent(new ui("gotpointercapture",b,b.a))};function Mi(b){zi.call(this,b,{pointerdown:this.zk,pointermove:this.Ak,pointerup:this.Dk,pointerout:this.Bk,pointerover:this.Ck,pointercancel:this.yk,gotpointercapture:this.Ph,lostpointercapture:this.xi})}u(Mi,zi);l=Mi.prototype;l.zk=function(b){Ni(this.a,b)};l.Ak=function(b){Ni(this.a,b)};l.Dk=function(b){Ni(this.a,b)};l.Bk=function(b){Ni(this.a,b)};l.Ck=function(b){Ni(this.a,b)};l.yk=function(b){Ni(this.a,b)};l.xi=function(b){Ni(this.a,b)};l.Ph=function(b){Ni(this.a,b)};function Oi(b,c){zi.call(this,b,{touchstart:this.vl,touchmove:this.ul,touchend:this.tl,touchcancel:this.sl});this.c=b.c;this.g=c;this.b=void 0;this.f=0;this.d=void 0}u(Oi,zi);l=Oi.prototype;l.Yf=function(){this.f=0;this.d=void 0};
+function Pi(b,c,d){c=Di(c,d);c.pointerId=d.identifier+2;c.bubbles=!0;c.cancelable=!0;c.detail=b.f;c.button=0;c.buttons=1;c.width=d.webkitRadiusX||d.radiusX||0;c.height=d.webkitRadiusY||d.radiusY||0;c.pressure=d.webkitForce||d.force||.5;c.isPrimary=b.b===d.identifier;c.pointerType="touch";c.clientX=d.clientX;c.clientY=d.clientY;c.screenX=d.screenX;c.screenY=d.screenY;return c}
+function Qi(b,c,d){function e(){c.preventDefault()}var f=Array.prototype.slice.call(c.a.changedTouches),g=f.length,h,k;for(h=0;h<g;++h)k=Pi(b,c,f[h]),k.preventDefault=e,d.call(b,c,k)}
+l.vl=function(b){var c=b.a.touches,d=sb(this.c),e=d.length;if(e>=c.length){var f=[],g,h,k;for(g=0;g<e;++g){h=d[g];k=this.c[h];var n;if(!(n=1==h))a:{n=c.length;for(var p=void 0,q=0;q<n;q++)if(p=c[q],p.identifier===h-2){n=!0;break a}n=!1}n||f.push(k.$b)}for(g=0;g<f.length;++g)this.be(b,f[g])}c=qb(this.c);if(0===c||1===c&&(1).toString()in this.c)this.b=b.a.changedTouches[0].identifier,m(this.d)&&ba.clearTimeout(this.d);Ri(this,b);this.f++;Qi(this,b,this.uk)};
+l.uk=function(b,c){this.c[c.pointerId]={target:c.target,$b:c,Jf:c.target};var d=this.a;c.bubbles=!0;Ei(d,Si,c,b);d=this.a;c.bubbles=!1;Ei(d,Ti,c,b);Ei(this.a,Fi,c,b)};l.ul=function(b){b.preventDefault();Qi(this,b,this.Di)};l.Di=function(b,c){var d=x(this.c,c.pointerId);if(d){var e=d.$b,f=d.Jf;Ei(this.a,Gi,c,b);e&&f!==c.target&&(e.relatedTarget=c.target,c.relatedTarget=f,e.target=f,c.target?(Ji(this.a,e,b),Ii(this.a,c,b)):(c.target=f,c.relatedTarget=null,this.be(b,c)));d.$b=c;d.Jf=c.target}};
+l.tl=function(b){Ri(this,b);Qi(this,b,this.wl)};l.wl=function(b,c){Ei(this.a,Hi,c,b);this.a.$b(c,b);var d=this.a;c.bubbles=!1;Ei(d,Ui,c,b);zb(this.c,c.pointerId);c.isPrimary&&(this.b=void 0,this.d=ba.setTimeout(sa(this.Yf,this),200))};l.sl=function(b){Qi(this,b,this.be)};l.be=function(b,c){this.a.cancel(c,b);this.a.$b(c,b);var d=this.a;c.bubbles=!1;Ei(d,Ui,c,b);zb(this.c,c.pointerId);c.isPrimary&&(this.b=void 0,this.d=ba.setTimeout(sa(this.Yf,this),200))};
+function Ri(b,c){var d=b.g.b,e=c.a.changedTouches[0];if(b.b===e.identifier){var f=[e.clientX,e.clientY];d.push(f);ba.setTimeout(function(){ab(d,f)},2500)}};function Vi(b){jd.call(this);this.aa=b;this.c={};this.b={};this.a=[];hg?Xi(this,new Mi(this)):ig?Xi(this,new Ki(this)):(b=new Ai(this),Xi(this,b),gg&&Xi(this,new Oi(this,b)));b=this.a.length;for(var c,d=0;d<b;d++)c=this.a[d],Yi(this,sb(c.e))}u(Vi,jd);function Xi(b,c){var d=sb(c.e);d&&(Ta(d,function(b){var d=c.e[b];d&&(this.b[b]=sa(d,c))},b),b.a.push(c))}Vi.prototype.d=function(b){var c=this.b[b.type];c&&c(b)};function Yi(b,c){Ta(c,function(b){z(this.aa,b,this.d,!1,this)},b)}
+function Zi(b,c){Ta(c,function(b){Xc(this.aa,b,this.d,!1,this)},b)}function Di(b,c){for(var d={},e,f=0,g=$i.length;f<g;f++)e=$i[f][0],d[e]=b[e]||c[e]||$i[f][1];return d}Vi.prototype.$b=function(b,c){b.bubbles=!0;Ei(this,aj,b,c)};Vi.prototype.cancel=function(b,c){Ei(this,bj,b,c)};function Ji(b,c,d){b.$b(c,d);c.target.contains(c.relatedTarget)||(c.bubbles=!1,Ei(b,Ui,c,d))}function Ii(b,c,d){c.bubbles=!0;Ei(b,Si,c,d);c.target.contains(c.relatedTarget)||(c.bubbles=!1,Ei(b,Ti,c,d))}
+function Ei(b,c,d,e){b.dispatchEvent(new ui(c,e,d))}function Ni(b,c){b.dispatchEvent(new ui(c.type,c,c.a))}Vi.prototype.M=function(){for(var b=this.a.length,c,d=0;d<b;d++)c=this.a[d],Zi(this,sb(c.e));Vi.S.M.call(this)};
+var Gi="pointermove",Fi="pointerdown",Hi="pointerup",Si="pointerover",aj="pointerout",Ti="pointerenter",Ui="pointerleave",bj="pointercancel",$i=[["bubbles",!1],["cancelable",!1],["view",null],["detail",null],["screenX",0],["screenY",0],["clientX",0],["clientY",0],["ctrlKey",!1],["altKey",!1],["shiftKey",!1],["metaKey",!1],["button",0],["relatedTarget",null],["buttons",0],["pointerId",0],["width",0],["height",0],["pressure",0],["tiltX",0],["tiltY",0],["pointerType",""],["hwTimestamp",0],["isPrimary",
+!1],["type",""],["target",null],["currentTarget",null],["which",0]];function cj(b,c,d,e){Zg.call(this,b,c,e);this.a=d;this.originalEvent=d.a;this.pixel=c.ad(this.originalEvent);this.coordinate=c.Ga(this.pixel)}u(cj,Zg);cj.prototype.preventDefault=function(){cj.S.preventDefault.call(this);this.a.preventDefault()};cj.prototype.lb=function(){cj.S.lb.call(this);this.a.lb()};function dj(b,c,d,e){cj.call(this,b,c,d.a,e);this.c=d}u(dj,cj);
+function ej(b){jd.call(this);this.c=b;this.e=0;this.o=!1;this.f=this.g=this.b=null;b=this.c.b;this.j=0;this.n={};this.d=new Vi(b);this.a=null;this.g=z(this.d,Fi,this.ki,!1,this);this.i=z(this.d,Gi,this.Tk,!1,this)}u(ej,jd);function fj(b,c){var d;d=new dj(gj,b.c,c);b.dispatchEvent(d);0!==b.e?(ba.clearTimeout(b.e),b.e=0,d=new dj(hj,b.c,c),b.dispatchEvent(d)):b.e=ba.setTimeout(sa(function(){this.e=0;var b=new dj(ij,this.c,c);this.dispatchEvent(b)},b),250)}
+function jj(b,c){c.type==kj||c.type==lj?delete b.n[c.pointerId]:c.type==mj&&(b.n[c.pointerId]=!0);b.j=qb(b.n)}l=ej.prototype;l.hf=function(b){jj(this,b);var c=new dj(kj,this.c,b);this.dispatchEvent(c);0===this.j&&(Ta(this.b,Yc),this.b=null,tc(this.a),this.a=null);!this.o&&0===b.button&&fj(this,this.f)};
+l.ki=function(b){jj(this,b);var c=new dj(mj,this.c,b);this.dispatchEvent(c);this.f=b;this.o=!1;null===this.b&&(this.a=new Vi(document),this.b=[z(this.a,nj,this.Zi,!1,this),z(this.a,kj,this.hf,!1,this),z(this.d,lj,this.hf,!1,this)])};l.Zi=function(b){if(b.clientX!=this.f.clientX||b.clientY!=this.f.clientY){this.o=!0;var c=new dj(oj,this.c,b);this.dispatchEvent(c)}b.preventDefault()};l.Tk=function(b){this.dispatchEvent(new dj(b.type,this.c,b))};
+l.M=function(){null!==this.i&&(Yc(this.i),this.i=null);null!==this.g&&(Yc(this.g),this.g=null);null!==this.b&&(Ta(this.b,Yc),this.b=null);null!==this.a&&(tc(this.a),this.a=null);null!==this.d&&(tc(this.d),this.d=null);ej.S.M.call(this)};var ij="singleclick",gj="click",hj="dblclick",oj="pointerdrag",nj="pointermove",mj="pointerdown",kj="pointerup",lj="pointercancel",pj={Tl:ij,Il:gj,Jl:hj,Ml:oj,Pl:nj,Ll:mj,Sl:kj,Rl:"pointerover",Ql:"pointerout",Nl:"pointerenter",Ol:"pointerleave",Kl:lj};function qj(b){md.call(this);this.g=Ee(b.projection);this.e=m(b.attributions)?b.attributions:null;this.r=b.logo;this.n=m(b.state)?b.state:"ready"}u(qj,md);l=qj.prototype;l.yd=ca;l.Y=function(){return this.e};l.W=function(){return this.r};l.Z=function(){return this.g};l.$=function(){return this.n};function rj(b,c){b.n=c;b.l()};function D(b){td.call(this);var c=Cb(b);c.brightness=m(b.brightness)?b.brightness:0;c.contrast=m(b.contrast)?b.contrast:1;c.hue=m(b.hue)?b.hue:0;c.opacity=m(b.opacity)?b.opacity:1;c.saturation=m(b.saturation)?b.saturation:1;c.visible=m(b.visible)?b.visible:!0;c.maxResolution=m(b.maxResolution)?b.maxResolution:Infinity;c.minResolution=m(b.minResolution)?b.minResolution:0;this.G(c)}u(D,td);D.prototype.d=function(){return this.get("brightness")};D.prototype.getBrightness=D.prototype.d;
+D.prototype.e=function(){return this.get("contrast")};D.prototype.getContrast=D.prototype.e;D.prototype.f=function(){return this.get("hue")};D.prototype.getHue=D.prototype.f;
+function sj(b){var c=b.d(),d=b.e(),e=b.f(),f=b.j(),g=b.n(),h=b.Ta(),k=b.b(),n=b.D(),p=b.g(),q=b.i();return{layer:b,brightness:m(c)?Yb(c,-1,1):0,contrast:m(d)?Math.max(d,0):1,hue:m(e)?e:0,opacity:m(f)?Yb(f,0,1):1,saturation:m(g)?Math.max(g,0):1,gc:h,visible:m(k)?!!k:!0,extent:n,maxResolution:m(p)?p:Infinity,minResolution:m(q)?Math.max(q,0):0}}D.prototype.D=function(){return this.get("extent")};D.prototype.getExtent=D.prototype.D;D.prototype.g=function(){return this.get("maxResolution")};
+D.prototype.getMaxResolution=D.prototype.g;D.prototype.i=function(){return this.get("minResolution")};D.prototype.getMinResolution=D.prototype.i;D.prototype.j=function(){return this.get("opacity")};D.prototype.getOpacity=D.prototype.j;D.prototype.n=function(){return this.get("saturation")};D.prototype.getSaturation=D.prototype.n;D.prototype.b=function(){return this.get("visible")};D.prototype.getVisible=D.prototype.b;D.prototype.t=function(b){this.set("brightness",b)};D.prototype.setBrightness=D.prototype.t;
+D.prototype.F=function(b){this.set("contrast",b)};D.prototype.setContrast=D.prototype.F;D.prototype.J=function(b){this.set("hue",b)};D.prototype.setHue=D.prototype.J;D.prototype.p=function(b){this.set("extent",b)};D.prototype.setExtent=D.prototype.p;D.prototype.Q=function(b){this.set("maxResolution",b)};D.prototype.setMaxResolution=D.prototype.Q;D.prototype.V=function(b){this.set("minResolution",b)};D.prototype.setMinResolution=D.prototype.V;D.prototype.q=function(b){this.set("opacity",b)};
+D.prototype.setOpacity=D.prototype.q;D.prototype.ba=function(b){this.set("saturation",b)};D.prototype.setSaturation=D.prototype.ba;D.prototype.ca=function(b){this.set("visible",b)};D.prototype.setVisible=D.prototype.ca;function E(b){var c=Cb(b);delete c.source;D.call(this,c);this.Ea=null;z(this,xd("source"),this.Yd,!1,this);this.ga(m(b.source)?b.source:null)}u(E,D);E.prototype.Da=function(b){b=m(b)?b:[];b.push(sj(this));return b};E.prototype.a=function(){var b=this.get("source");return m(b)?b:null};E.prototype.getSource=E.prototype.a;E.prototype.Ta=function(){var b=this.a();return null===b?"undefined":b.n};E.prototype.Zd=function(){this.l()};
+E.prototype.Yd=function(){null!==this.Ea&&(Yc(this.Ea),this.Ea=null);var b=this.a();null!==b&&(this.Ea=z(b,"change",this.Zd,!1,this));this.l()};E.prototype.ga=function(b){this.set("source",b)};E.prototype.setSource=E.prototype.ga;function tj(b,c,d,e,f){jd.call(this);this.f=f;this.extent=b;this.e=d;this.resolution=c;this.state=e}u(tj,jd);tj.prototype.D=function(){return this.extent};function uj(b,c){jd.call(this);this.a=b;this.state=c}u(uj,jd);function vj(b){b.dispatchEvent("change")}uj.prototype.mb=function(){return ma(this).toString()};uj.prototype.f=function(){return this.a};function wj(b){this.minZoom=m(b.minZoom)?b.minZoom:0;this.a=b.resolutions;this.maxZoom=this.a.length-1;this.d=m(b.origin)?b.origin:null;this.f=null;m(b.origins)&&(this.f=b.origins);this.c=null;m(b.tileSizes)&&(this.c=b.tileSizes);this.e=m(b.tileSize)?b.tileSize:null===this.c?256:void 0}var xj=[0,0,0];l=wj.prototype;l.Bb=function(){return ed};l.$c=function(b,c,d,e,f){f=yj(this,b,f);for(b=b[0]-1;b>=this.minZoom;){if(c.call(d,b,zj(this,f,b,e)))return!0;--b}return!1};l.ed=function(){return this.maxZoom};
+l.fd=function(){return this.minZoom};l.Lb=function(b){return null===this.d?this.f[b]:this.d};l.ka=function(b){return this.a[b]};l.Fd=function(){return this.a};l.kd=function(b,c,d){return b[0]<this.maxZoom?(d=yj(this,b,d),zj(this,d,b[0]+1,c)):null};function Aj(b,c,d,e){Bj(b,c[0],c[1],d,!1,xj);var f=xj[1],g=xj[2];Bj(b,c[2],c[3],d,!0,xj);return pf(f,xj[1],g,xj[2],e)}function zj(b,c,d,e){return Aj(b,c,b.ka(d),e)}
+function Cj(b,c){var d=b.Lb(c[0]),e=b.ka(c[0]),f=b.sa(c[0]);return[d[0]+(c[1]+.5)*f*e,d[1]+(c[2]+.5)*f*e]}function yj(b,c,d){var e=b.Lb(c[0]),f=b.ka(c[0]);b=b.sa(c[0]);var g=e[0]+c[1]*b*f;c=e[1]+c[2]*b*f;return Xd(g,c,g+b*f,c+b*f,d)}l.Vb=function(b,c,d){return Bj(this,b[0],b[1],c,!1,d)};function Bj(b,c,d,e,f,g){var h=ec(b.a,e,0),k=e/b.ka(h),n=b.Lb(h);b=b.sa(h);c=k*(c-n[0])/(e*b);d=k*(d-n[1])/(e*b);f?(c=Math.ceil(c)-1,d=Math.ceil(d)-1):(c=Math.floor(c),d=Math.floor(d));return kf(h,c,d,g)}
+l.Fc=function(b,c,d){return Bj(this,b[0],b[1],this.ka(c),!1,d)};l.sa=function(b){return m(this.e)?this.e:this.c[b]};function Dj(b,c,d){c=m(c)?c:42;d=m(d)?d:256;b=Math.max(re(b)/d,oe(b)/d);c+=1;d=Array(c);for(var e=0;e<c;++e)d[e]=b/Math.pow(2,e);return d}function Ej(b){b=Ee(b);var c=b.D();null===c&&(b=180*Ae.degrees/b.ie(),c=Xd(-b,-b,b,b));return c};function Fj(b){qj.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state});this.t=m(b.opaque)?b.opaque:!1;this.F=m(b.tilePixelRatio)?b.tilePixelRatio:1;this.tileGrid=m(b.tileGrid)?b.tileGrid:null}u(Fj,qj);l=Fj.prototype;l.zd=cd;l.fe=function(b,c,d,e){var f=!0,g,h,k,n;for(k=e.a;k<=e.d;++k)for(n=e.b;n<=e.c;++n)h=this.hb(d,k,n),b[d]&&b[d][h]||(g=c(d,k,n),null===g?f=!1:(b[d]||(b[d]={}),b[d][h]=g));return f};l.bd=function(){return 0};l.hb=lf;l.za=function(){return this.tileGrid};
+function Gj(b,c){var d;if(null===b.tileGrid){if(d=c.e,null===d){d=Ej(c);var e=m(void 0)?void 0:256,f=m(void 0)?void 0:"bottom-left",g=Dj(d,void 0,e);d=new wj({origin:le(d,f),resolutions:g,tileSize:e});c.e=d}}else d=b.tileGrid;return d}l.Gc=function(b,c,d){return Gj(this,d).sa(b)*this.F};l.He=ca;function Hj(b,c){pc.call(this);this.b=b;this.a=c}u(Hj,pc);Hj.prototype.Zb=ca;Hj.prototype.o=function(b){2===b.target.state&&Ij(this)};function Ij(b){var c=b.a;c.b()&&"ready"==c.Ta()&&b.b.e.render()}function Jj(b,c){c.zd()&&b.postRenderFunctions.push(ta(function(b,c,f){c=ma(b).toString();b.se(f.usedTiles[c])},c))}function Kj(b,c){if(null!=c){var d,e,f;e=0;for(f=c.length;e<f;++e)d=c[e],b[ma(d).toString()]=d}}function Lj(b,c){var d=c.r;m(d)&&(ia(d)?b.logos[d]="":la(d)&&(b.logos[d.src]=d.href))}
+function Mj(b,c,d,e){c=ma(c).toString();d=d.toString();c in b?d in b[c]?(b=b[c][d],e.a<b.a&&(b.a=e.a),e.d>b.d&&(b.d=e.d),e.b<b.b&&(b.b=e.b),e.c>b.c&&(b.c=e.c)):b[c][d]=e:(b[c]={},b[c][d]=e)}function Nj(b,c,d,e){return function(f,g,h){f=c.Fb(f,g,h,d,e);return b(f)?f:null}}function Oj(b,c,d){return[c*(Math.round(b[0]/c)+d[0]%2/2),c*(Math.round(b[1]/c)+d[1]%2/2)]}
+function Pj(b,c,d,e,f,g,h,k,n,p){var q=ma(c).toString();q in b.wantedTiles||(b.wantedTiles[q]={});var r=b.wantedTiles[q];b=b.tileQueue;var s=d.minZoom,v,y,C,F,G,w;m(k)||(k=0);for(w=h;w>=s;--w)for(y=zj(d,g,w,y),C=d.ka(w),F=y.a;F<=y.d;++F)for(G=y.b;G<=y.c;++G)h-w<=k?(v=c.Fb(w,F,G,e,f),0==v.state&&(r[nf(v.a)]=!0,v.mb()in b.b||Qj(b,[v,q,Cj(d,v.a),C])),m(n)&&n.call(p,v)):c.He(w,F,G)};function Rj(b){this.p=b.opacity;this.q=b.rotateWithView;this.i=b.rotation;this.n=b.scale;this.r=b.snapToPixel}l=Rj.prototype;l.Ad=function(){return this.p};l.hd=function(){return this.q};l.Bd=function(){return this.i};l.Cd=function(){return this.n};l.jd=function(){return this.r};l.Dd=function(b){this.i=b};l.Ed=function(b){this.n=b};function Sj(b){b=m(b)?b:{};this.e=m(b.anchor)?b.anchor:[.5,.5];this.d=null;this.c=m(b.anchorOrigin)?b.anchorOrigin:"top-left";this.g=m(b.anchorXUnits)?b.anchorXUnits:"fraction";this.o=m(b.anchorYUnits)?b.anchorYUnits:"fraction";var c=m(b.crossOrigin)?b.crossOrigin:null,d=m(b.img)?b.img:null,e=b.src;m(e)&&0!==e.length||null===d||(e=d.src);var f=m(b.src)?0:2,g=Tj.Ja(),h=g.get(e,c);null===h&&(h=new Uj(d,e,c,f),g.set(e,c,h));this.a=h;this.t=m(b.offset)?b.offset:[0,0];this.b=m(b.offsetOrigin)?b.offsetOrigin:
+"top-left";this.f=null;this.j=m(b.size)?b.size:null;Rj.call(this,{opacity:m(b.opacity)?b.opacity:1,rotation:m(b.rotation)?b.rotation:0,scale:m(b.scale)?b.scale:1,snapToPixel:m(b.snapToPixel)?b.snapToPixel:!0,rotateWithView:m(b.rotateWithView)?b.rotateWithView:!1})}u(Sj,Rj);l=Sj.prototype;
+l.tb=function(){if(null!==this.d)return this.d;var b=this.e,c=this.ab();if("fraction"==this.g||"fraction"==this.o){if(null===c)return null;b=this.e.slice();"fraction"==this.g&&(b[0]*=c[0]);"fraction"==this.o&&(b[1]*=c[1])}if("top-left"!=this.c){if(null===c)return null;b===this.e&&(b=this.e.slice());if("top-right"==this.c||"bottom-right"==this.c)b[0]=-b[0]+c[0];if("bottom-left"==this.c||"bottom-right"==this.c)b[1]=-b[1]+c[1]}return this.d=b};l.yb=function(){return this.a.a};l.cd=function(){return this.a.c};
+l.ue=function(){return this.a.b};l.te=function(){var b=this.a;if(null===b.e)if(b.o){var c=b.c[0],d=b.c[1],e=Tf(c,d);e.fillRect(0,0,c,d);b.e=e.canvas}else b.e=b.a;return b.e};l.zb=function(){if(null!==this.f)return this.f;var b=this.t;if("top-left"!=this.b){var c=this.ab(),d=this.a.c;if(null===c||null===d)return null;b=b.slice();if("top-right"==this.b||"bottom-right"==this.b)b[0]=d[0]-c[0]-b[0];if("bottom-left"==this.b||"bottom-right"==this.b)b[1]=d[1]-c[1]-b[1]}return this.f=b};l.Pj=function(){return this.a.f};
+l.ab=function(){return null===this.j?this.a.c:this.j};l.ne=function(b,c){return z(this.a,"change",b,!1,c)};l.load=function(){this.a.load()};l.Ge=function(b,c){Xc(this.a,"change",b,!1,c)};function Uj(b,c,d,e){jd.call(this);this.e=null;this.a=null===b?new Image:b;null!==d&&(this.a.crossOrigin=d);this.d=null;this.b=e;this.c=null;this.f=c;this.o=!1}u(Uj,jd);Uj.prototype.g=function(){this.b=3;Ta(this.d,Yc);this.d=null;this.dispatchEvent("change")};
+Uj.prototype.i=function(){this.b=2;this.c=[this.a.width,this.a.height];Ta(this.d,Yc);this.d=null;var b=Tf(1,1);b.drawImage(this.a,0,0);try{b.getImageData(0,0,1,1)}catch(c){this.o=!0}this.dispatchEvent("change")};Uj.prototype.load=function(){if(0==this.b){this.b=1;this.d=[Wc(this.a,"error",this.g,!1,this),Wc(this.a,"load",this.i,!1,this)];try{this.a.src=this.f}catch(b){this.g()}}};function Tj(){this.a={};this.c=0}da(Tj);Tj.prototype.clear=function(){this.a={};this.c=0};
+Tj.prototype.get=function(b,c){var d=c+":"+b;return d in this.a?this.a[d]:null};Tj.prototype.set=function(b,c,d){this.a[c+":"+b]=d;++this.c};function Vj(b,c,d,e,f,g,h,k){Od(b);0===c&&0===d||Qd(b,c,d);1==e&&1==f||Rd(b,e,f);0!==g&&Sd(b,g);0===h&&0===k||Qd(b,h,k);return b}function Wj(b,c){return b[0]==c[0]&&b[1]==c[1]&&b[4]==c[4]&&b[5]==c[5]&&b[12]==c[12]&&b[13]==c[13]}function Xj(b,c,d){var e=b[1],f=b[5],g=b[13],h=c[0];c=c[1];d[0]=b[0]*h+b[4]*c+b[12];d[1]=e*h+f*c+g;return d};function Yj(b,c){pc.call(this);this.e=c;this.g=null;this.b={}}u(Yj,pc);
+function Zj(b){var c=b.viewState,d=b.coordinateToPixelMatrix;Vj(d,b.size[0]/2,b.size[1]/2,1/c.resolution,-1/c.resolution,-c.rotation,-c.center[0],-c.center[1]);b=b.pixelToCoordinateMatrix;var c=d[0],e=d[1],f=d[2],g=d[3],h=d[4],k=d[5],n=d[6],p=d[7],q=d[8],r=d[9],s=d[10],v=d[11],y=d[12],C=d[13],F=d[14],d=d[15],G=c*k-e*h,w=c*n-f*h,U=c*p-g*h,N=e*n-f*k,Y=e*p-g*k,T=f*p-g*n,qa=q*C-r*y,vb=q*F-s*y,Ka=q*d-v*y,ac=r*F-s*C,Sb=r*d-v*C,La=s*d-v*F,Pa=G*La-w*Sb+U*ac+N*Ka-Y*vb+T*qa;0!=Pa&&(Pa=1/Pa,b[0]=(k*La-n*Sb+
+p*ac)*Pa,b[1]=(-e*La+f*Sb-g*ac)*Pa,b[2]=(C*T-F*Y+d*N)*Pa,b[3]=(-r*T+s*Y-v*N)*Pa,b[4]=(-h*La+n*Ka-p*vb)*Pa,b[5]=(c*La-f*Ka+g*vb)*Pa,b[6]=(-y*T+F*U-d*w)*Pa,b[7]=(q*T-s*U+v*w)*Pa,b[8]=(h*Sb-k*Ka+p*qa)*Pa,b[9]=(-c*Sb+e*Ka-g*qa)*Pa,b[10]=(y*Y-C*U+d*G)*Pa,b[11]=(-q*Y+r*U-v*G)*Pa,b[12]=(-h*ac+k*vb-n*qa)*Pa,b[13]=(c*ac-e*vb+f*qa)*Pa,b[14]=(-y*N+C*w-F*G)*Pa,b[15]=(q*N-r*w+s*G)*Pa)}Yj.prototype.Yc=function(b){return new Hj(this,b)};Yj.prototype.M=function(){ob(this.b,tc);Yj.S.M.call(this)};
+function ak(){var b=Tj.Ja();if(32<b.c){var c=0,d,e;for(d in b.a){e=b.a[d];var f;if(f=0===(c++&3))Ec(e)?e=ld(e,void 0,void 0):(e=Sc(e),e=!!e&&Mc(e,void 0,void 0)),f=!e;f&&(delete b.a[d],--b.c)}}}
+function bk(b,c,d,e,f,g,h){var k,n=d.viewState,p=n.resolution,n=n.rotation;if(null!==b.g){var q={};if(k=ck(b.g,p,n,c,{},function(b){var c=ma(b).toString();if(!(c in q))return q[c]=!0,e.call(f,b,null)}))return k}var n=b.e.Eb().Da(),r;for(r=n.length-1;0<=r;--r){k=n[r];var s=k.layer;if(k.visible&&p>=k.minResolution&&p<k.maxResolution&&g.call(h,s)&&(k=dk(b,s).Zb(c,d,e,f)))return k}}function dk(b,c){var d=ma(c).toString();if(d in b.b)return b.b[d];var e=b.Yc(c);return b.b[d]=e}Yj.prototype.Ld=ca;
+Yj.prototype.t=function(b,c){for(var d in this.b)if(!(null!==c&&d in c.layerStates)){var e=this.b[d];delete this.b[d];tc(e)}};function ek(b,c){for(var d in b.b)if(!(d in c.layerStates)){c.postRenderFunctions.push(sa(b.t,b));break}};function fk(b,c){this.f=b;this.e=c;this.a=[];this.c=[];this.b={}}fk.prototype.clear=function(){this.a.length=0;this.c.length=0;yb(this.b)};function gk(b){var c=b.a,d=b.c,e=c[0];1==c.length?(c.length=0,d.length=0):(c[0]=c.pop(),d[0]=d.pop(),hk(b,0));c=b.e(e);delete b.b[c];return e}function Qj(b,c){var d=b.f(c);Infinity!=d&&(b.a.push(c),b.c.push(d),b.b[b.e(c)]=!0,ik(b,0,b.a.length-1))}fk.prototype.Ub=function(){return this.a.length};fk.prototype.ia=function(){return 0===this.a.length};
+function hk(b,c){for(var d=b.a,e=b.c,f=d.length,g=d[c],h=e[c],k=c;c<f>>1;){var n=2*c+1,p=2*c+2,n=p<f&&e[p]<e[n]?p:n;d[c]=d[n];e[c]=e[n];c=n}d[c]=g;e[c]=h;ik(b,k,c)}function ik(b,c,d){var e=b.a;b=b.c;for(var f=e[d],g=b[d];d>c;){var h=d-1>>1;if(b[h]>g)e[d]=e[h],b[d]=b[h],d=h;else break}e[d]=f;b[d]=g}function jk(b){var c=b.f,d=b.a,e=b.c,f=0,g=d.length,h,k,n;for(k=0;k<g;++k)h=d[k],n=c(h),Infinity==n?delete b.b[b.e(h)]:(e[f]=n,d[f++]=h);d.length=f;e.length=f;for(c=(b.a.length>>1)-1;0<=c;c--)hk(b,c)};function kk(b,c){fk.call(this,function(c){return b.apply(null,c)},function(b){return b[0].mb()});this.o=c;this.d=0}u(kk,fk);kk.prototype.g=function(){--this.d;this.o()};function lk(b,c,d){this.d=b;this.b=c;this.f=d;this.a=[];this.c=this.e=0}lk.prototype.update=function(b,c){this.a.push(b,c,ua())};function mk(b,c){var d=b.d,e=b.c,f=b.b-e,g=nk(b);return gf({source:c,duration:g,easing:function(b){return e*(Math.exp(d*b*g)-1)/f}})}function nk(b){return Math.log(b.b/b.c)/b.d};function ok(b){td.call(this);this.n=null;this.b(!0);this.handleEvent=b.handleEvent}u(ok,td);ok.prototype.a=function(){return this.get("active")};ok.prototype.getActive=ok.prototype.a;ok.prototype.b=function(b){this.set("active",b)};ok.prototype.setActive=ok.prototype.b;ok.prototype.setMap=function(b){this.n=b};function pk(b,c,d,e,f){if(null!=d){var g=c.e(),h=c.a();m(g)&&m(h)&&m(f)&&0<f&&(b.Ua(hf({rotation:g,duration:f,easing:cf})),m(e)&&b.Ua(gf({source:h,duration:f,easing:cf})));c.rotate(d,e)}}
+function qk(b,c,d,e,f){var g=c.b();d=c.constrainResolution(g,d,0);rk(b,c,d,e,f)}function rk(b,c,d,e,f){if(null!=d){var g=c.b(),h=c.a();m(g)&&m(h)&&m(f)&&0<f&&(b.Ua(jf({resolution:g,duration:f,easing:cf})),m(e)&&b.Ua(gf({source:h,duration:f,easing:cf})));if(null!=e){var k;b=c.a();f=c.b();m(b)&&m(f)&&(k=[e[0]-d*(e[0]-b[0])/f,e[1]-d*(e[1]-b[1])/f]);c.Oa(k)}c.d(d)}};function sk(b){b=m(b)?b:{};this.d=m(b.delta)?b.delta:1;ok.call(this,{handleEvent:tk});this.e=m(b.duration)?b.duration:250}u(sk,ok);function tk(b){var c=!1,d=b.a;if(b.type==hj){var c=b.map,e=b.coordinate,d=d.d?-this.d:this.d,f=c.a();qk(c,f,d,e,this.e);b.preventDefault();c=!0}return!c};function uk(b){b=b.a;return b.c&&!b.g&&b.d}function vk(b){return"mousemove"==b.originalEvent.type}function wk(b){return b.type==ij}function xk(b){b=b.a;return!b.c&&!b.g&&!b.d}function yk(b){b=b.a;return!b.c&&!b.g&&b.d}function zk(b){b=b.a.target.tagName;return"INPUT"!==b&&"SELECT"!==b&&"TEXTAREA"!==b}function Ak(b){return 1==b.c.pointerId};function Bk(b){b=m(b)?b:{};ok.call(this,{handleEvent:m(b.handleEvent)?b.handleEvent:Ck});this.Da=m(b.handleDownEvent)?b.handleDownEvent:cd;this.oa=m(b.handleDragEvent)?b.handleDragEvent:ca;this.Ea=m(b.handleMoveEvent)?b.handleMoveEvent:ca;this.qa=m(b.handleUpEvent)?b.handleUpEvent:cd;this.p=!1;this.t={};this.e=[]}u(Bk,ok);function Dk(b){for(var c=b.length,d=0,e=0,f=0;f<c;f++)d+=b[f].clientX,e+=b[f].clientY;return[d/c,e/c]}
+function Ck(b){if(!(b instanceof dj))return!0;var c=!1,d=b.type;if(d===mj||d===oj||d===kj)d=b.c,b.type==kj?delete this.t[d.pointerId]:b.type==mj?this.t[d.pointerId]=d:d.pointerId in this.t&&(this.t[d.pointerId]=d),this.e=rb(this.t);this.p&&(b.type==oj?this.oa(b):b.type==kj&&(this.p=this.qa(b)));b.type==mj?(this.p=b=this.Da(b),c=this.q(b)):b.type==nj&&this.Ea(b);return!c}Bk.prototype.q=ed;function Ek(b){Bk.call(this,{handleDownEvent:Fk,handleDragEvent:Gk,handleUpEvent:Hk});b=m(b)?b:{};this.d=b.kinetic;this.f=this.g=null;this.j=m(b.condition)?b.condition:xk;this.i=!1}u(Ek,Bk);function Gk(b){var c=Dk(this.e);this.d&&this.d.update(c[0],c[1]);if(null!==this.f){var d=this.f[0]-c[0],e=c[1]-this.f[1];b=b.map;var f=b.a(),g=$e(f),e=d=[d,e],h=g.resolution;e[0]*=h;e[1]*=h;Dd(d,g.rotation);yd(d,g.center);d=f.i(d);b.render();f.Oa(d)}this.f=c}
+function Hk(b){b=b.map;var c=b.a();if(0===this.e.length){var d;if(d=!this.i&&this.d)if(d=this.d,6>d.a.length)d=!1;else{var e=ua()-d.f,f=d.a.length-3;if(d.a[f+2]<e)d=!1;else{for(var g=f-3;0<g&&d.a[g+2]>e;)g-=3;var e=d.a[f+2]-d.a[g+2],h=d.a[f]-d.a[g],f=d.a[f+1]-d.a[g+1];d.e=Math.atan2(f,h);d.c=Math.sqrt(h*h+f*f)/e;d=d.c>d.b}}d&&(d=this.d,d=(d.b-d.c)/d.d,f=this.d.e,g=c.a(),this.g=mk(this.d,g),b.Ua(this.g),g=b.f(g),d=b.Ga([g[0]-d*Math.cos(f),g[1]-d*Math.sin(f)]),d=c.i(d),c.Oa(d));bf(c,-1);b.render();
+return!1}this.f=null;return!0}function Fk(b){if(0<this.e.length&&this.j(b)){var c=b.map,d=c.a();this.f=null;this.p||bf(d,1);c.render();null!==this.g&&ab(c.F,this.g)&&(d.Oa(b.frameState.viewState.center),this.g=null);this.d&&(b=this.d,b.a.length=0,b.e=0,b.c=0);this.i=1<this.e.length;return!0}return!1}Ek.prototype.q=cd;function Ik(b){b=m(b)?b:{};Bk.call(this,{handleDownEvent:Jk,handleDragEvent:Kk,handleUpEvent:Lk});this.f=m(b.condition)?b.condition:uk;this.d=void 0}u(Ik,Bk);function Kk(b){if(Ak(b)){var c=b.map,d=c.e();b=b.pixel;d=Math.atan2(d[1]/2-b[1],b[0]-d[0]/2);if(m(this.d)){b=d-this.d;var e=c.a(),f=$e(e);c.render();pk(c,e,f.rotation-b)}this.d=d}}function Lk(b){if(!Ak(b))return!0;b=b.map;var c=b.a();bf(c,-1);var d=$e(c).rotation,d=c.constrainRotation(d,0);pk(b,c,d,void 0,250);return!1}
+function Jk(b){return Ak(b)&&Cc(b.a)&&this.f(b)?(b=b.map,bf(b.a(),1),b.render(),this.d=void 0,!0):!1}Ik.prototype.q=cd;function Mk(){md.call(this);this.extent=void 0;this.g=-1;this.o={};this.j=this.i=0}u(Mk,md);Mk.prototype.f=function(b,c){var d=m(c)?c:[NaN,NaN];this.Va(b[0],b[1],d,Infinity);return d};Mk.prototype.Jb=cd;Mk.prototype.e=function(b,c){this.ma(Ve(b,c));return this};function Nk(b,c,d,e,f,g){var h=f[0],k=f[1],n=f[4],p=f[5],q=f[12];f=f[13];for(var r=m(g)?g:[],s=0;c<d;c+=e){var v=b[c],y=b[c+1];r[s++]=h*v+n*y+q;r[s++]=k*v+p*y+f}m(g)&&r.length!=s&&(r.length=s);return r};function Ok(){Mk.call(this);this.a="XY";this.s=2;this.k=null}u(Ok,Mk);function Pk(b){if("XY"==b)return 2;if("XYZ"==b||"XYM"==b)return 3;if("XYZM"==b)return 4}l=Ok.prototype;l.Jb=cd;l.D=function(b){if(this.g!=this.c){var c=this.k,d=this.k.length,e=this.s,f=Xd(Infinity,Infinity,-Infinity,-Infinity,this.extent);this.extent=fe(f,c,0,d,e);this.g=this.c}return te(this.extent,b)};l.vb=function(){return this.k.slice(0,this.s)};l.wb=function(){return this.k.slice(this.k.length-this.s)};l.xb=function(){return this.a};
+l.ke=function(b){this.j!=this.c&&(yb(this.o),this.i=0,this.j=this.c);if(0>b||0!==this.i&&b<=this.i)return this;var c=b.toString();if(this.o.hasOwnProperty(c))return this.o[c];var d=this.mc(b);if(d.k.length<this.k.length)return this.o[c]=d;this.i=b;return this};l.mc=function(){return this};function Qk(b,c,d){b.s=Pk(c);b.a=c;b.k=d}
+function Rk(b,c,d,e){if(m(c))d=Pk(c);else{for(c=0;c<e;++c){if(0===d.length){b.a="XY";b.s=2;return}d=d[0]}d=d.length;c=2==d?"XY":3==d?"XYZ":4==d?"XYZM":void 0}b.a=c;b.s=d}l.ma=function(b){null!==this.k&&(b(this.k,this.k,this.s),this.l())};l.Aa=function(b,c){var d=this.k;if(null!==d){var e=d.length,f=this.s,g=m(d)?d:[],h=0,k,n;for(k=0;k<e;k+=f)for(g[h++]=d[k]+b,g[h++]=d[k+1]+c,n=k+2;n<k+f;++n)g[h++]=d[n];m(d)&&g.length!=h&&(g.length=h);this.l()}};function Sk(b,c,d,e){for(var f=0,g=b[d-e],h=b[d-e+1];c<d;c+=e)var k=b[c],n=b[c+1],f=f+(h*k-g*n),g=k,h=n;return f/2}function Tk(b,c,d,e){var f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],f=f+Sk(b,c,k,e);c=k}return f};function Uk(b,c,d,e,f,g){var h=f-d,k=g-e;if(0!==h||0!==k){var n=((b-d)*h+(c-e)*k)/(h*h+k*k);1<n?(d=f,e=g):0<n&&(d+=h*n,e+=k*n)}return Vk(b,c,d,e)}function Vk(b,c,d,e){b=d-b;c=e-c;return b*b+c*c};function Wk(b,c,d,e,f,g,h){var k=b[c],n=b[c+1],p=b[d]-k,q=b[d+1]-n;if(0!==p||0!==q)if(g=((f-k)*p+(g-n)*q)/(p*p+q*q),1<g)c=d;else if(0<g){for(f=0;f<e;++f)h[f]=$b(b[c+f],b[d+f],g);h.length=e;return}for(f=0;f<e;++f)h[f]=b[c+f];h.length=e}function Xk(b,c,d,e,f){var g=b[c],h=b[c+1];for(c+=e;c<d;c+=e){var k=b[c],n=b[c+1],g=Vk(g,h,k,n);g>f&&(f=g);g=k;h=n}return f}function Yk(b,c,d,e,f){var g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];f=Xk(b,c,k,e,f);c=k}return f}
+function Zk(b,c,d,e,f,g,h,k,n,p,q){if(c==d)return p;var r;if(0===f){r=Vk(h,k,b[c],b[c+1]);if(r<p){for(q=0;q<e;++q)n[q]=b[c+q];n.length=e;return r}return p}for(var s=m(q)?q:[NaN,NaN],v=c+e;v<d;)if(Wk(b,v-e,v,e,h,k,s),r=Vk(h,k,s[0],s[1]),r<p){p=r;for(q=0;q<e;++q)n[q]=s[q];n.length=e;v+=e}else v+=e*Math.max((Math.sqrt(r)-Math.sqrt(p))/f|0,1);if(g&&(Wk(b,d-e,c,e,h,k,s),r=Vk(h,k,s[0],s[1]),r<p)){p=r;for(q=0;q<e;++q)n[q]=s[q];n.length=e}return p}
+function $k(b,c,d,e,f,g,h,k,n,p,q){q=m(q)?q:[NaN,NaN];var r,s;r=0;for(s=d.length;r<s;++r){var v=d[r];p=Zk(b,c,v,e,f,g,h,k,n,p,q);c=v}return p};function al(b,c){var d=0,e,f;e=0;for(f=c.length;e<f;++e)b[d++]=c[e];return d}function bl(b,c,d,e){var f,g;f=0;for(g=d.length;f<g;++f){var h=d[f],k;for(k=0;k<e;++k)b[c++]=h[k]}return c}function cl(b,c,d,e,f){f=m(f)?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h)c=bl(b,c,d[h],e),f[g++]=c;f.length=g;return f};function dl(b,c,d,e,f){f=m(f)?f:[];for(var g=0;c<d;c+=e)f[g++]=b.slice(c,c+e);f.length=g;return f}function el(b,c,d,e,f){f=m(f)?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h){var n=d[h];f[g++]=dl(b,c,n,e,f[g]);c=n}f.length=g;return f};function fl(b,c,d,e,f,g,h){var k=(d-c)/e;if(3>k){for(;c<d;c+=e)g[h++]=b[c],g[h++]=b[c+1];return h}var n=Array(k);n[0]=1;n[k-1]=1;d=[c,d-e];for(var p=0,q;0<d.length;){var r=d.pop(),s=d.pop(),v=0,y=b[s],C=b[s+1],F=b[r],G=b[r+1];for(q=s+e;q<r;q+=e){var w=Uk(b[q],b[q+1],y,C,F,G);w>v&&(p=q,v=w)}v>f&&(n[(p-c)/e]=1,s+e<p&&d.push(s,p),p+e<r&&d.push(p,r))}for(q=0;q<k;++q)n[q]&&(g[h++]=b[c+q*e],g[h++]=b[c+q*e+1]);return h}
+function gl(b,c,d,e,f,g,h,k){var n,p;n=0;for(p=d.length;n<p;++n){var q=d[n];a:{var r=b,s=q,v=e,y=f,C=g;if(c!=s){var F=y*Math.round(r[c]/y),G=y*Math.round(r[c+1]/y);c+=v;C[h++]=F;C[h++]=G;var w=void 0,U=void 0;do if(w=y*Math.round(r[c]/y),U=y*Math.round(r[c+1]/y),c+=v,c==s){C[h++]=w;C[h++]=U;break a}while(w==F&&U==G);for(;c<s;){var N,Y;N=y*Math.round(r[c]/y);Y=y*Math.round(r[c+1]/y);c+=v;if(N!=w||Y!=U){var T=w-F,qa=U-G,vb=N-F,Ka=Y-G;T*Ka==qa*vb&&(0>T&&vb<T||T==vb||0<T&&vb>T)&&(0>qa&&Ka<qa||qa==Ka||
+0<qa&&Ka>qa)||(C[h++]=w,C[h++]=U,F=w,G=U);w=N;U=Y}}C[h++]=w;C[h++]=U}}k.push(h);c=q}return h};function hl(b,c){Ok.call(this);this.b=this.n=-1;this.U(b,c)}u(hl,Ok);l=hl.prototype;l.clone=function(){var b=new hl(null);il(b,this.a,this.k.slice());return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.b!=this.c&&(this.n=Math.sqrt(Xk(this.k,0,this.k.length,this.s,0)),this.b=this.c);return Zk(this.k,0,this.k.length,this.s,this.n,!0,b,c,d,e)};l.nj=function(){return Sk(this.k,0,this.k.length,this.s)};l.K=function(){return dl(this.k,0,this.k.length,this.s)};
+l.mc=function(b){var c=[];c.length=fl(this.k,0,this.k.length,this.s,b,c,0);b=new hl(null);il(b,"XY",c);return b};l.H=function(){return"LinearRing"};l.U=function(b,c){null===b?il(this,"XY",null):(Rk(this,c,b,1),null===this.k&&(this.k=[]),this.k.length=bl(this.k,0,b,this.s),this.l())};function il(b,c,d){Qk(b,c,d);b.l()};function jl(b,c){Ok.call(this);this.U(b,c)}u(jl,Ok);l=jl.prototype;l.clone=function(){var b=new jl(null);kl(b,this.a,this.k.slice());return b};l.Va=function(b,c,d,e){var f=this.k;b=Vk(b,c,f[0],f[1]);if(b<e){e=this.s;for(c=0;c<e;++c)d[c]=f[c];d.length=e;return b}return e};l.K=function(){return null===this.k?[]:this.k.slice()};l.D=function(b){this.g!=this.c&&(this.extent=ce(this.k,this.extent),this.g=this.c);return te(this.extent,b)};l.H=function(){return"Point"};
+l.ha=function(b){return ae(b,this.k[0],this.k[1])};l.U=function(b,c){null===b?kl(this,"XY",null):(Rk(this,c,b,0),null===this.k&&(this.k=[]),this.k.length=al(this.k,b),this.l())};function kl(b,c,d){Qk(b,c,d);b.l()};function ll(b,c,d,e,f){return!ge(f,function(f){return!ml(b,c,d,e,f[0],f[1])})}function ml(b,c,d,e,f,g){for(var h=!1,k=b[d-e],n=b[d-e+1];c<d;c+=e){var p=b[c],q=b[c+1];n>g!=q>g&&f<(p-k)*(g-n)/(q-n)+k&&(h=!h);k=p;n=q}return h}function nl(b,c,d,e,f,g){if(0===d.length||!ml(b,c,d[0],e,f,g))return!1;var h;c=1;for(h=d.length;c<h;++c)if(ml(b,d[c-1],d[c],e,f,g))return!1;return!0};function pl(b,c,d,e,f,g,h){var k,n,p,q,r,s=f[g+1],v=[],y=d[0];p=b[y-e];r=b[y-e+1];for(k=c;k<y;k+=e){q=b[k];n=b[k+1];if(s<=r&&n<=s||r<=s&&s<=n)p=(s-r)/(n-r)*(q-p)+p,v.push(p);p=q;r=n}y=NaN;r=-Infinity;v.sort();p=v[0];k=1;for(n=v.length;k<n;++k){q=v[k];var C=Math.abs(q-p);C>r&&(p=(p+q)/2,nl(b,c,d,e,p,s)&&(y=p,r=C));p=q}isNaN(y)&&(y=f[g]);return m(h)?(h.push(y,s),h):[y,s]};function ql(b,c,d,e,f){for(var g=[b[c],b[c+1]],h=[],k;c+e<d;c+=e){h[0]=b[c+e];h[1]=b[c+e+1];if(k=f(g,h))return k;g[0]=h[0];g[1]=h[1]}return!1};function rl(b,c,d,e,f){var g=fe(Vd(),b,c,d,e);return qe(f,g)?$d(f,g)||g[0]>=f[0]&&g[2]<=f[2]||g[1]>=f[1]&&g[3]<=f[3]?!0:ql(b,c,d,e,function(b,c){var d=!1,e=be(f,b),g=be(f,c);if(1===e||1===g)d=!0;else{var r=f[0],s=f[1],v=f[2],y=f[3],C=c[0],F=c[1],G=(F-b[1])/(C-b[0]);g&2&&!(e&2)?(s=C-(F-y)/G,d=s>=r&&s<=v):g&4&&!(e&4)?(r=F-(C-v)*G,d=r>=s&&r<=y):g&8&&!(e&8)?(s=C-(F-s)/G,d=s>=r&&s<=v):g&16&&!(e&16)&&(r=F-(C-r)*G,d=r>=s&&r<=y)}return d}):!1}
+function sl(b,c,d,e,f){var g=d[0];if(!(rl(b,c,g,e,f)||ml(b,c,g,e,f[0],f[1])||ml(b,c,g,e,f[0],f[3])||ml(b,c,g,e,f[2],f[1])||ml(b,c,g,e,f[2],f[3])))return!1;if(1===d.length)return!0;c=1;for(g=d.length;c<g;++c)if(ll(b,d[c-1],d[c],e,f))return!1;return!0};function tl(b,c,d,e){for(var f=0,g=b[d-e],h=b[d-e+1];c<d;c+=e)var k=b[c],n=b[c+1],f=f+(k-g)*(n+h),g=k,h=n;return 0<f}function ul(b,c,d){var e=0,f,g;f=0;for(g=c.length;f<g;++f){var h=c[f],e=tl(b,e,h,d);if(0===f?!e:e)return!1;e=h}return!0}function vl(b,c,d,e){var f,g;f=0;for(g=d.length;f<g;++f){var h=d[f],k=tl(b,c,h,e);if(0===f?!k:k)for(var k=b,n=h,p=e;c<n-p;){var q;for(q=0;q<p;++q){var r=k[c+q];k[c+q]=k[n-p+q];k[n-p+q]=r}c+=p;n-=p}c=h}return c};function H(b,c){Ok.call(this);this.b=[];this.p=-1;this.q=null;this.F=this.r=this.t=-1;this.n=null;this.U(b,c)}u(H,Ok);l=H.prototype;l.Ug=function(b){null===this.k?this.k=b.k.slice():db(this.k,b.k);this.b.push(this.k.length);this.l()};l.clone=function(){var b=new H(null);wl(b,this.a,this.k.slice(),this.b.slice());return b};
+l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.r!=this.c&&(this.t=Math.sqrt(Yk(this.k,0,this.b,this.s,0)),this.r=this.c);return $k(this.k,0,this.b,this.s,this.t,!0,b,c,d,e)};l.Jb=function(b,c){return nl(xl(this),0,this.b,this.s,b,c)};l.qj=function(){return Tk(xl(this),0,this.b,this.s)};l.K=function(){return el(this.k,0,this.b,this.s)};function yl(b){if(b.p!=b.c){var c=ke(b.D());b.q=pl(xl(b),0,b.b,b.s,c,0);b.p=b.c}return b.q}l.ph=function(){return new jl(yl(this))};l.vh=function(){return this.b.length};
+l.uh=function(b){if(0>b||this.b.length<=b)return null;var c=new hl(null);il(c,this.a,this.k.slice(0===b?0:this.b[b-1],this.b[b]));return c};l.dd=function(){var b=this.a,c=this.k,d=this.b,e=[],f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],n=new hl(null);il(n,b,c.slice(f,k));e.push(n);f=k}return e};function xl(b){if(b.F!=b.c){var c=b.k;ul(c,b.b,b.s)?b.n=c:(b.n=c.slice(),b.n.length=vl(b.n,0,b.b,b.s));b.F=b.c}return b.n}
+l.mc=function(b){var c=[],d=[];c.length=gl(this.k,0,this.b,this.s,Math.sqrt(b),c,0,d);b=new H(null);wl(b,"XY",c,d);return b};l.H=function(){return"Polygon"};l.ha=function(b){return sl(xl(this),0,this.b,this.s,b)};l.U=function(b,c){if(null===b)wl(this,"XY",null,this.b);else{Rk(this,c,b,2);null===this.k&&(this.k=[]);var d=cl(this.k,0,b,this.s,this.b);this.k.length=0===d.length?0:d[d.length-1];this.l()}};function wl(b,c,d,e){Qk(b,c,d);b.b=e;b.l()}
+function zl(b,c,d,e){var f=m(e)?e:32;e=[];var g;for(g=0;g<f;++g)db(e,b.offset(c,d,2*Math.PI*g/f));e.push(e[0],e[1]);b=new H(null);wl(b,"XY",e,[e.length]);return b};function Al(b,c,d,e,f,g,h){uc.call(this,b,c);this.vectorContext=d;this.a=e;this.frameState=f;this.context=g;this.glContext=h}u(Al,uc);function Bl(b){this.b=this.c=this.e=this.d=this.a=null;this.f=b}u(Bl,pc);function Cl(b){var c=b.e,d=b.c;b=Va([c,[c[0],d[1]],d,[d[0],c[1]]],b.a.Ga,b.a);b[4]=b[0].slice();return new H([b])}Bl.prototype.M=function(){this.setMap(null)};Bl.prototype.g=function(b){var c=this.b,d=this.f;b.vectorContext.ic(Infinity,function(b){b.wa(d.e,d.b);b.xa(d.c);b.Sb(c,null)})};Bl.prototype.N=function(){return this.b};function Dl(b){null===b.a||null===b.e||null===b.c||b.a.render()}
+Bl.prototype.setMap=function(b){null!==this.d&&(Yc(this.d),this.d=null,this.a.render(),this.a=null);this.a=b;null!==this.a&&(this.d=z(b,"postcompose",this.g,!1,this),Dl(this))};function El(b,c){uc.call(this,b);this.coordinate=c}u(El,uc);function Fl(b){Bk.call(this,{handleDownEvent:Hl,handleDragEvent:Il,handleUpEvent:Jl});b=m(b)?b:{};this.f=new Bl(m(b.style)?b.style:null);this.d=null;this.i=m(b.condition)?b.condition:dd}u(Fl,Bk);function Il(b){if(Ak(b)){var c=this.f;b=b.pixel;c.e=this.d;c.c=b;c.b=Cl(c);Dl(c)}}Fl.prototype.N=function(){return this.f.N()};Fl.prototype.g=ca;
+function Jl(b){if(!Ak(b))return!0;this.f.setMap(null);var c=b.pixel[0]-this.d[0],d=b.pixel[1]-this.d[1];64<=c*c+d*d&&(this.g(b),this.dispatchEvent(new El("boxend",b.coordinate)));return!1}function Hl(b){if(Ak(b)&&Cc(b.a)&&this.i(b)){this.d=b.pixel;this.f.setMap(b.map);var c=this.f,d=this.d;c.e=this.d;c.c=d;c.b=Cl(c);Dl(c);this.dispatchEvent(new El("boxstart",b.coordinate));return!0}return!1};function Kl(){this.c=-1};function Ll(){this.c=-1;this.c=64;this.a=Array(4);this.e=Array(this.c);this.d=this.b=0;this.a[0]=1732584193;this.a[1]=4023233417;this.a[2]=2562383102;this.a[3]=271733878;this.d=this.b=0}u(Ll,Kl);
+function Ml(b,c,d){d||(d=0);var e=Array(16);if(ia(c))for(var f=0;16>f;++f)e[f]=c.charCodeAt(d++)|c.charCodeAt(d++)<<8|c.charCodeAt(d++)<<16|c.charCodeAt(d++)<<24;else for(f=0;16>f;++f)e[f]=c[d++]|c[d++]<<8|c[d++]<<16|c[d++]<<24;c=b.a[0];d=b.a[1];var f=b.a[2],g=b.a[3],h=0,h=c+(g^d&(f^g))+e[0]+3614090360&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[1]+3905402710&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+(d^g&(c^d))+e[2]+606105819&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^
+c))+e[3]+3250441966&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(g^d&(f^g))+e[4]+4118548399&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[5]+1200080426&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+(d^g&(c^d))+e[6]+2821735955&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^c))+e[7]+4249261313&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(g^d&(f^g))+e[8]+1770035416&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[9]+2336552879&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+
+(d^g&(c^d))+e[10]+4294925233&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^c))+e[11]+2304563134&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(g^d&(f^g))+e[12]+1804603682&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[13]+4254626195&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+(d^g&(c^d))+e[14]+2792965006&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^c))+e[15]+1236535329&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(f^g&(d^f))+e[1]+4129170786&4294967295;c=d+(h<<5&4294967295|
+h>>>27);h=g+(d^f&(c^d))+e[6]+3225465664&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[11]+643717713&4294967295;f=g+(h<<14&4294967295|h>>>18);h=d+(g^c&(f^g))+e[0]+3921069994&4294967295;d=f+(h<<20&4294967295|h>>>12);h=c+(f^g&(d^f))+e[5]+3593408605&4294967295;c=d+(h<<5&4294967295|h>>>27);h=g+(d^f&(c^d))+e[10]+38016083&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[15]+3634488961&4294967295;f=g+(h<<14&4294967295|h>>>18);h=d+(g^c&(f^g))+e[4]+3889429448&4294967295;d=f+(h<<20&4294967295|
+h>>>12);h=c+(f^g&(d^f))+e[9]+568446438&4294967295;c=d+(h<<5&4294967295|h>>>27);h=g+(d^f&(c^d))+e[14]+3275163606&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[3]+4107603335&4294967295;f=g+(h<<14&4294967295|h>>>18);h=d+(g^c&(f^g))+e[8]+1163531501&4294967295;d=f+(h<<20&4294967295|h>>>12);h=c+(f^g&(d^f))+e[13]+2850285829&4294967295;c=d+(h<<5&4294967295|h>>>27);h=g+(d^f&(c^d))+e[2]+4243563512&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[7]+1735328473&4294967295;f=g+(h<<14&4294967295|
+h>>>18);h=d+(g^c&(f^g))+e[12]+2368359562&4294967295;d=f+(h<<20&4294967295|h>>>12);h=c+(d^f^g)+e[5]+4294588738&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[8]+2272392833&4294967295;g=c+(h<<11&4294967295|h>>>21);h=f+(g^c^d)+e[11]+1839030562&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[14]+4259657740&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(d^f^g)+e[1]+2763975236&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[4]+1272893353&4294967295;g=c+(h<<11&4294967295|h>>>21);h=f+(g^
+c^d)+e[7]+4139469664&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[10]+3200236656&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(d^f^g)+e[13]+681279174&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[0]+3936430074&4294967295;g=c+(h<<11&4294967295|h>>>21);h=f+(g^c^d)+e[3]+3572445317&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[6]+76029189&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(d^f^g)+e[9]+3654602809&4294967295;c=d+(h<<4&4294967295|h>>>28);h=g+(c^d^f)+e[12]+3873151461&4294967295;
+g=c+(h<<11&4294967295|h>>>21);h=f+(g^c^d)+e[15]+530742520&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[2]+3299628645&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(f^(d|~g))+e[0]+4096336452&4294967295;c=d+(h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[7]+1126891415&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[14]+2878612391&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[5]+4237533241&4294967295;d=f+(h<<21&4294967295|h>>>11);h=c+(f^(d|~g))+e[12]+1700485571&4294967295;c=d+
+(h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[3]+2399980690&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[10]+4293915773&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[1]+2240044497&4294967295;d=f+(h<<21&4294967295|h>>>11);h=c+(f^(d|~g))+e[8]+1873313359&4294967295;c=d+(h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[15]+4264355552&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[6]+2734768916&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[13]+1309151649&4294967295;
+d=f+(h<<21&4294967295|h>>>11);h=c+(f^(d|~g))+e[4]+4149444226&4294967295;c=d+(h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[11]+3174756917&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[2]+718787259&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[9]+3951481745&4294967295;b.a[0]=b.a[0]+c&4294967295;b.a[1]=b.a[1]+(f+(h<<21&4294967295|h>>>11))&4294967295;b.a[2]=b.a[2]+f&4294967295;b.a[3]=b.a[3]+g&4294967295}
+Ll.prototype.update=function(b,c){m(c)||(c=b.length);for(var d=c-this.c,e=this.e,f=this.b,g=0;g<c;){if(0==f)for(;g<=d;)Ml(this,b,g),g+=this.c;if(ia(b))for(;g<c;){if(e[f++]=b.charCodeAt(g++),f==this.c){Ml(this,e);f=0;break}}else for(;g<c;)if(e[f++]=b[g++],f==this.c){Ml(this,e);f=0;break}}this.b=f;this.d+=c};function Nl(b){b=m(b)?b:{};this.a=m(b.color)?b.color:null;this.d=b.lineCap;this.b=m(b.lineDash)?b.lineDash:null;this.e=b.lineJoin;this.f=b.miterLimit;this.c=b.width;this.g=void 0}l=Nl.prototype;l.Vj=function(){return this.a};l.rh=function(){return this.d};l.Wj=function(){return this.b};l.sh=function(){return this.e};l.xh=function(){return this.f};l.Xj=function(){return this.c};l.Yj=function(b){this.a=b;this.g=void 0};l.el=function(b){this.d=b;this.g=void 0};l.Zj=function(b){this.b=b;this.g=void 0};
+l.fl=function(b){this.e=b;this.g=void 0};l.gl=function(b){this.f=b;this.g=void 0};l.ol=function(b){this.c=b;this.g=void 0};
+l.ub=function(){if(!m(this.g)){var b="s"+(null===this.a?"-":wg(this.a))+","+(m(this.d)?this.d.toString():"-")+","+(null===this.b?"-":this.b.toString())+","+(m(this.e)?this.e:"-")+","+(m(this.f)?this.f.toString():"-")+","+(m(this.c)?this.c.toString():"-"),c=new Ll;c.update(b);var d=Array((56>c.b?c.c:2*c.c)-c.b);d[0]=128;for(b=1;b<d.length-8;++b)d[b]=0;for(var e=8*c.d,b=d.length-8;b<d.length;++b)d[b]=e&255,e/=256;c.update(d);d=Array(16);for(b=e=0;4>b;++b)for(var f=0;32>f;f+=8)d[e++]=c.a[b]>>>f&255;
+if(8192>d.length)c=String.fromCharCode.apply(null,d);else for(c="",b=0;b<d.length;b+=8192)c+=String.fromCharCode.apply(null,fb(d,b,b+8192));this.g=c}return this.g};var Ol=[0,0,0,1],Pl=[],Ql=[0,0,0,1];function Rl(b){b=m(b)?b:{};this.a=m(b.color)?b.color:null;this.c=void 0}Rl.prototype.b=function(){return this.a};Rl.prototype.d=function(b){this.a=b;this.c=void 0};Rl.prototype.ub=function(){m(this.c)||(this.c="f"+(null===this.a?"-":wg(this.a)));return this.c};function Sl(b){b=m(b)?b:{};this.f=this.a=this.e=null;this.d=m(b.fill)?b.fill:null;this.c=m(b.stroke)?b.stroke:null;this.b=b.radius;this.j=[0,0];this.o=this.t=this.g=null;var c=b.atlasManager,d,e=null,f,g=0;null!==this.c&&(f=wg(this.c.a),g=this.c.c,m(g)||(g=1),e=this.c.b,cg||(e=null));var h=2*(this.b+g)+1;f={strokeStyle:f,Nc:g,size:h,lineDash:e};m(c)?(h=Math.round(h),(e=null===this.d)&&(d=sa(this.Bf,this,f)),g=this.ub(),f=c.add(g,h,h,sa(this.Cf,this,f),d),this.a=f.image,this.j=[f.offsetX,f.offsetY],
+d=f.image.width,this.f=e?f.kf:this.a):(this.a=If("CANVAS"),this.a.height=h,this.a.width=h,d=h=this.a.width,c=this.a.getContext("2d"),this.Cf(f,c,0,0),null===this.d?(c=this.f=If("CANVAS"),c.height=f.size,c.width=f.size,c=c.getContext("2d"),this.Bf(f,c,0,0)):this.f=this.a);this.g=[h/2,h/2];this.t=[h,h];this.o=[d,d];Rj.call(this,{opacity:1,rotateWithView:!1,rotation:0,scale:1,snapToPixel:m(b.snapToPixel)?b.snapToPixel:!0})}u(Sl,Rj);l=Sl.prototype;l.tb=function(){return this.g};l.Mj=function(){return this.d};
+l.te=function(){return this.f};l.yb=function(){return this.a};l.ue=function(){return 2};l.cd=function(){return this.o};l.zb=function(){return this.j};l.Nj=function(){return this.b};l.ab=function(){return this.t};l.Oj=function(){return this.c};l.ne=ca;l.load=ca;l.Ge=ca;
+l.Cf=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(b.size/2,b.size/2,this.b,0,2*Math.PI,!0);null!==this.d&&(c.fillStyle=wg(this.d.a),c.fill());null!==this.c&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()};
+l.Bf=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(b.size/2,b.size/2,this.b,0,2*Math.PI,!0);c.fillStyle=Ol;c.fill();null!==this.c&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()};l.ub=function(){var b=null===this.c?"-":this.c.ub(),c=null===this.d?"-":this.d.ub();if(null===this.e||b!=this.e[1]||c!=this.e[2]||this.b!=this.e[3])this.e=["c"+b+c+(m(this.b)?this.b.toString():"-"),b,c,this.b];return this.e[0]};function Tl(b){b=m(b)?b:{};this.g=null;this.d=Ul;m(b.geometry)&&this.Ff(b.geometry);this.e=m(b.fill)?b.fill:null;this.f=m(b.image)?b.image:null;this.b=m(b.stroke)?b.stroke:null;this.c=m(b.text)?b.text:null;this.a=b.zIndex}l=Tl.prototype;l.N=function(){return this.g};l.lh=function(){return this.d};l.$j=function(){return this.e};l.ak=function(){return this.f};l.bk=function(){return this.b};l.ck=function(){return this.c};l.Nh=function(){return this.a};
+l.Ff=function(b){ka(b)?this.d=b:ia(b)?this.d=function(c){return c.get(b)}:null===b?this.d=Ul:m(b)&&(this.d=function(){return b});this.g=b};l.ql=function(b){this.a=b};function Vl(b){ka(b)||(b=ga(b)?b:[b],b=bd(b));return b}function Wl(){var b=new Rl({color:"rgba(255,255,255,0.4)"}),c=new Nl({color:"#3399CC",width:1.25}),d=[new Tl({image:new Sl({fill:b,stroke:c,radius:5}),fill:b,stroke:c})];Wl=function(){return d};return d}
+function Xl(){var b={},c=[255,255,255,1],d=[0,153,255,1];b.Polygon=[new Tl({fill:new Rl({color:[255,255,255,.5]})})];b.MultiPolygon=b.Polygon;b.LineString=[new Tl({stroke:new Nl({color:c,width:5})}),new Tl({stroke:new Nl({color:d,width:3})})];b.MultiLineString=b.LineString;b.Point=[new Tl({image:new Sl({radius:6,fill:new Rl({color:d}),stroke:new Nl({color:c,width:1.5})}),zIndex:Infinity})];b.MultiPoint=b.Point;b.GeometryCollection=b.Polygon.concat(b.Point);return b}function Ul(b){return b.N()};function Yl(b){var c=m(b)?b:{};b=m(c.condition)?c.condition:yk;c=m(c.style)?c.style:new Tl({stroke:new Nl({color:[0,0,255,1]})});Fl.call(this,{condition:b,style:c})}u(Yl,Fl);Yl.prototype.g=function(){var b=this.n,c=b.a(),d=this.N().D(),e=ke(d),f=b.e(),d=c.n(d,f),d=c.constrainResolution(d,0,void 0);rk(b,c,d,e,200)};function Zl(b){ok.call(this,{handleEvent:$l});b=m(b)?b:{};this.d=m(b.condition)?b.condition:id(xk,zk);this.e=m(b.pixelDelta)?b.pixelDelta:128}u(Zl,ok);
+function $l(b){var c=!1;if("key"==b.type){var d=b.a.e;if(this.d(b)&&(40==d||37==d||39==d||38==d)){var e=b.map,c=e.a(),f=$e(c),g=f.resolution*this.e,h=0,k=0;40==d?k=-g:37==d?h=-g:39==d?h=g:k=g;d=[h,k];Dd(d,f.rotation);f=c.a();m(f)&&(m(100)&&e.Ua(gf({source:f,duration:100,easing:ef})),e=c.i([f[0]+d[0],f[1]+d[1]]),c.Oa(e));b.preventDefault();c=!0}}return!c};function am(b){ok.call(this,{handleEvent:bm});b=m(b)?b:{};this.e=m(b.condition)?b.condition:zk;this.d=m(b.delta)?b.delta:1;this.f=m(b.duration)?b.duration:100}u(am,ok);function bm(b){var c=!1;if("key"==b.type){var d=b.a.i;if(this.e(b)&&(43==d||45==d)){c=b.map;d=43==d?this.d:-this.d;c.render();var e=c.a();qk(c,e,d,void 0,this.f);b.preventDefault();c=!0}}return!c};function cm(b){ok.call(this,{handleEvent:dm});b=m(b)?b:{};this.d=0;this.j=m(b.duration)?b.duration:250;this.f=null;this.g=this.e=void 0}u(cm,ok);function dm(b){var c=!1;if("mousewheel"==b.type){var c=b.map,d=b.a;this.f=b.coordinate;this.d+=d.j;m(this.e)||(this.e=ua());d=Math.max(80-(ua()-this.e),0);ba.clearTimeout(this.g);this.g=ba.setTimeout(sa(this.i,this,c),d);b.preventDefault();c=!0}return!c}
+cm.prototype.i=function(b){var c=Yb(this.d,-1,1),d=b.a();b.render();qk(b,d,-c,this.f,this.j);this.d=0;this.f=null;this.g=this.e=void 0};function em(b){Bk.call(this,{handleDownEvent:fm,handleDragEvent:gm,handleUpEvent:hm});b=m(b)?b:{};this.f=null;this.g=void 0;this.d=!1;this.i=0;this.j=m(b.threshold)?b.threshold:.3}u(em,Bk);
+function gm(b){var c=0,d=this.e[0],e=this.e[1],d=Math.atan2(e.clientY-d.clientY,e.clientX-d.clientX);m(this.g)&&(c=d-this.g,this.i+=c,!this.d&&Math.abs(this.i)>this.j&&(this.d=!0));this.g=d;b=b.map;d=Og(b.b);e=Dk(this.e);e[0]-=d.x;e[1]-=d.y;this.f=b.Ga(e);this.d&&(d=b.a(),e=$e(d),b.render(),pk(b,d,e.rotation+c,this.f))}function hm(b){if(2>this.e.length){b=b.map;var c=b.a();bf(c,-1);if(this.d){var d=$e(c).rotation,e=this.f,d=c.constrainRotation(d,0);pk(b,c,d,e,250)}return!1}return!0}
+function fm(b){return 2<=this.e.length?(b=b.map,this.f=null,this.g=void 0,this.d=!1,this.i=0,this.p||bf(b.a(),1),b.render(),!0):!1}em.prototype.q=cd;function im(b){Bk.call(this,{handleDownEvent:jm,handleDragEvent:km,handleUpEvent:lm});b=m(b)?b:{};this.f=null;this.i=m(b.duration)?b.duration:400;this.d=void 0;this.g=1}u(im,Bk);function km(b){var c=1,d=this.e[0],e=this.e[1],f=d.clientX-e.clientX,d=d.clientY-e.clientY,f=Math.sqrt(f*f+d*d);m(this.d)&&(c=this.d/f);this.d=f;1!=c&&(this.g=c);b=b.map;var f=b.a(),d=$e(f),e=Og(b.b),g=Dk(this.e);g[0]-=e.x;g[1]-=e.y;this.f=b.Ga(g);b.render();rk(b,f,d.resolution*c,this.f)}
+function lm(b){if(2>this.e.length){b=b.map;var c=b.a();bf(c,-1);var d=$e(c).resolution,e=this.f,f=this.i,d=c.constrainResolution(d,0,this.g-1);rk(b,c,d,e,f);return!1}return!0}function jm(b){return 2<=this.e.length?(b=b.map,this.f=null,this.d=void 0,this.g=1,this.p||bf(b.a(),1),b.render(),!0):!1}im.prototype.q=cd;function mm(b){b=m(b)?b:{};var c=new B,d=new lk(-.005,.05,100);(m(b.altShiftDragRotate)?b.altShiftDragRotate:1)&&c.push(new Ik);(m(b.doubleClickZoom)?b.doubleClickZoom:1)&&c.push(new sk({delta:b.zoomDelta,duration:b.zoomDuration}));(m(b.dragPan)?b.dragPan:1)&&c.push(new Ek({kinetic:d}));(m(b.pinchRotate)?b.pinchRotate:1)&&c.push(new em);(m(b.pinchZoom)?b.pinchZoom:1)&&c.push(new im({duration:b.zoomDuration}));if(m(b.keyboard)?b.keyboard:1)c.push(new Zl),c.push(new am({delta:b.zoomDelta,duration:b.zoomDuration}));
+(m(b.mouseWheelZoom)?b.mouseWheelZoom:1)&&c.push(new cm({duration:b.zoomDuration}));(m(b.shiftDragZoom)?b.shiftDragZoom:1)&&c.push(new Yl);return c};function I(b){var c=m(b)?b:{};b=Cb(c);delete b.layers;c=c.layers;D.call(this,b);this.a=null;z(this,xd("layers"),this.ei,!1,this);null!=c?ga(c)&&(c=new B(cb(c))):c=new B;this.r(c)}u(I,D);l=I.prototype;l.ff=function(){this.b()&&this.l()};
+l.ei=function(){null!==this.a&&(Ta(rb(this.a),Yc),this.a=null);var b=this.Yb();if(null!=b){this.a={add:z(b,"add",this.di,!1,this),remove:z(b,"remove",this.fi,!1,this)};var b=b.a,c,d,e;c=0;for(d=b.length;c<d;c++)e=b[c],this.a[ma(e).toString()]=z(e,["propertychange","change"],this.ff,!1,this)}this.l()};l.di=function(b){b=b.element;this.a[ma(b).toString()]=z(b,["propertychange","change"],this.ff,!1,this);this.l()};l.fi=function(b){b=ma(b.element).toString();Yc(this.a[b]);delete this.a[b];this.l()};
+l.Yb=function(){return this.get("layers")};I.prototype.getLayers=I.prototype.Yb;I.prototype.r=function(b){this.set("layers",b)};I.prototype.setLayers=I.prototype.r;
+I.prototype.Da=function(b){var c=m(b)?b:[],d=c.length;this.Yb().forEach(function(b){b.Da(c)});b=sj(this);var e,f;for(e=c.length;d<e;d++)f=c[d],f.brightness=Yb(f.brightness+b.brightness,-1,1),f.contrast*=b.contrast,f.hue+=b.hue,f.opacity*=b.opacity,f.saturation*=b.saturation,f.visible=f.visible&&b.visible,f.maxResolution=Math.min(f.maxResolution,b.maxResolution),f.minResolution=Math.max(f.minResolution,b.minResolution),m(b.extent)&&(f.extent=m(f.extent)?pe(f.extent,b.extent):b.extent);return c};
+I.prototype.Ta=function(){return"ready"};function nm(b){Be.call(this,{code:b,units:"m",extent:om,global:!0,worldExtent:pm})}u(nm,Be);nm.prototype.je=function(b,c){var d=c[1]/6378137;return b/((Math.exp(d)+Math.exp(-d))/2)};var qm=6378137*Math.PI,om=[-qm,-qm,qm,qm],pm=[-180,-85,180,85],Me=Va("EPSG:3857 EPSG:102100 EPSG:102113 EPSG:900913 urn:ogc:def:crs:EPSG:6.18:3:3857 urn:ogc:def:crs:EPSG::3857 http://www.opengis.net/gml/srs/epsg.xml#3857".split(" "),function(b){return new nm(b)});
+function Ne(b,c,d){var e=b.length;d=1<d?d:2;m(c)||(2<d?c=b.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=6378137*Math.PI*b[f]/180,c[f+1]=6378137*Math.log(Math.tan(Math.PI*(b[f+1]+90)/360));return c}function Oe(b,c,d){var e=b.length;d=1<d?d:2;m(c)||(2<d?c=b.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=180*b[f]/(6378137*Math.PI),c[f+1]=360*Math.atan(Math.exp(b[f+1]/6378137))/Math.PI-90;return c};function rm(b,c){Be.call(this,{code:b,units:"degrees",extent:sm,axisOrientation:c,global:!0,worldExtent:sm})}u(rm,Be);rm.prototype.je=function(b){return b};var sm=[-180,-90,180,90],Pe=[new rm("CRS:84"),new rm("EPSG:4326","neu"),new rm("urn:ogc:def:crs:EPSG::4326","neu"),new rm("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new rm("urn:ogc:def:crs:OGC:1.3:CRS84"),new rm("urn:ogc:def:crs:OGC:2:84"),new rm("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new rm("urn:x-ogc:def:crs:EPSG:4326","neu")];function tm(){He(Me);He(Pe);Le()};function J(b){E.call(this,m(b)?b:{})}u(J,E);function K(b){E.call(this,m(b)?b:{})}u(K,E);K.prototype.r=function(){return this.get("preload")};K.prototype.getPreload=K.prototype.r;K.prototype.oa=function(b){this.set("preload",b)};K.prototype.setPreload=K.prototype.oa;K.prototype.ea=function(){return this.get("useInterimTilesOnError")};K.prototype.getUseInterimTilesOnError=K.prototype.ea;K.prototype.qa=function(b){this.set("useInterimTilesOnError",b)};K.prototype.setUseInterimTilesOnError=K.prototype.qa;function L(b){b=m(b)?b:{};var c=Cb(b);delete c.style;E.call(this,c);this.Ab=m(b.renderBuffer)?b.renderBuffer:100;this.eb=null;this.r=void 0;this.oa(b.style)}u(L,E);L.prototype.Uc=function(){return this.eb};L.prototype.Vc=function(){return this.r};L.prototype.oa=function(b){this.eb=m(b)?b:Wl;this.r=null===b?void 0:Vl(this.eb);this.l()};function um(b,c,d,e,f){this.p={};this.b=b;this.r=c;this.e=d;this.F=e;this.eb=f;this.f=this.a=this.c=this.Ra=this.ga=this.la=null;this.ea=this.Sa=this.j=this.V=this.Q=this.J=0;this.Da=!1;this.g=this.oa=0;this.Ea=!1;this.ba=0;this.d="";this.i=this.t=this.Ta=this.qa=0;this.ca=this.n=this.o=null;this.q=[];this.ec=Kd()}
+function vm(b,c,d){if(null!==b.f){c=Nk(c,0,d,2,b.F,b.q);d=b.b;var e=b.ec,f=d.globalAlpha;1!=b.j&&(d.globalAlpha=f*b.j);var g=b.oa;b.Da&&(g+=b.eb);var h,k;h=0;for(k=c.length;h<k;h+=2){var n=c[h]-b.J,p=c[h+1]-b.Q;b.Ea&&(n=n+.5|0,p=p+.5|0);if(0!==g||1!=b.g){var q=n+b.J,r=p+b.Q;Vj(e,q,r,b.g,b.g,g,-q,-r);d.setTransform(e[0],e[1],e[4],e[5],e[12],e[13])}d.drawImage(b.f,b.Sa,b.ea,b.ba,b.V,n,p,b.ba,b.V)}0===g&&1==b.g||d.setTransform(1,0,0,1,0,0);1!=b.j&&(d.globalAlpha=f)}}
+function wm(b,c,d,e){var f=0;if(null!==b.ca&&""!==b.d){null===b.o||xm(b,b.o);null===b.n||ym(b,b.n);var g=b.ca,h=b.b,k=b.Ra;null===k?(h.font=g.font,h.textAlign=g.textAlign,h.textBaseline=g.textBaseline,b.Ra={font:g.font,textAlign:g.textAlign,textBaseline:g.textBaseline}):(k.font!=g.font&&(k.font=h.font=g.font),k.textAlign!=g.textAlign&&(k.textAlign=h.textAlign=g.textAlign),k.textBaseline!=g.textBaseline&&(k.textBaseline=h.textBaseline=g.textBaseline));c=Nk(c,f,d,e,b.F,b.q);for(g=b.b;f<d;f+=e){h=c[f]+
+b.qa;k=c[f+1]+b.Ta;if(0!==b.t||1!=b.i){var n=Vj(b.ec,h,k,b.i,b.i,b.t,-h,-k);g.setTransform(n[0],n[1],n[4],n[5],n[12],n[13])}null===b.n||g.strokeText(b.d,h,k);null===b.o||g.fillText(b.d,h,k)}0===b.t&&1==b.i||g.setTransform(1,0,0,1,0,0)}}function zm(b,c,d,e,f,g){var h=b.b;b=Nk(c,d,e,f,b.F,b.q);h.moveTo(b[0],b[1]);for(c=2;c<b.length;c+=2)h.lineTo(b[c],b[c+1]);g&&h.lineTo(b[0],b[1]);return e}function Am(b,c,d,e,f){var g=b.b,h,k;h=0;for(k=e.length;h<k;++h)d=zm(b,c,d,e[h],f,!0),g.closePath();return d}
+l=um.prototype;l.ic=function(b,c){var d=b.toString(),e=this.p[d];m(e)?e.push(c):this.p[d]=[c]};l.jc=function(b){if(qe(this.e,b.D())){if(null!==this.c||null!==this.a){null===this.c||xm(this,this.c);null===this.a||ym(this,this.a);var c;c=b.k;c=null===c?null:Nk(c,0,c.length,b.s,this.F,this.q);var d=c[2]-c[0],e=c[3]-c[1],d=Math.sqrt(d*d+e*e),e=this.b;e.beginPath();e.arc(c[0],c[1],d,0,2*Math.PI);null===this.c||e.fill();null===this.a||e.stroke()}""!==this.d&&wm(this,b.qe(),2,2)}};
+l.ee=function(b,c){var d=(0,c.d)(b);if(null!=d&&qe(this.e,d.D())){var e=c.a;m(e)||(e=0);this.ic(e,function(b){b.wa(c.e,c.b);b.cb(c.f);b.xa(c.c);Bm[d.H()].call(b,d,null)})}};l.Zc=function(b,c){var d=b.d,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e];Bm[g.H()].call(this,g,c)}};l.rb=function(b){var c=b.k;b=b.s;null===this.f||vm(this,c,c.length);""!==this.d&&wm(this,c,c.length,b)};l.qb=function(b){var c=b.k;b=b.s;null===this.f||vm(this,c,c.length);""!==this.d&&wm(this,c,c.length,b)};
+l.Cb=function(b){if(qe(this.e,b.D())){if(null!==this.a){ym(this,this.a);var c=this.b,d=b.k;c.beginPath();zm(this,d,0,d.length,b.s,!1);c.stroke()}""!==this.d&&(b=Cm(b),wm(this,b,2,2))}};l.kc=function(b){var c=b.D();if(qe(this.e,c)){if(null!==this.a){ym(this,this.a);var c=this.b,d=b.k,e=0,f=b.b,g=b.s;c.beginPath();var h,k;h=0;for(k=f.length;h<k;++h)e=zm(this,d,e,f[h],g,!1);c.stroke()}""!==this.d&&(b=Dm(b),wm(this,b,b.length,2))}};
+l.Sb=function(b){if(qe(this.e,b.D())){if(null!==this.a||null!==this.c){null===this.c||xm(this,this.c);null===this.a||ym(this,this.a);var c=this.b;c.beginPath();Am(this,xl(b),0,b.b,b.s);null===this.c||c.fill();null===this.a||c.stroke()}""!==this.d&&(b=yl(b),wm(this,b,2,2))}};
+l.lc=function(b){if(qe(this.e,b.D())){if(null!==this.a||null!==this.c){null===this.c||xm(this,this.c);null===this.a||ym(this,this.a);var c=this.b,d=Em(b),e=0,f=b.b,g=b.s,h,k;h=0;for(k=f.length;h<k;++h){var n=f[h];c.beginPath();e=Am(this,d,e,n,g);null===this.c||c.fill();null===this.a||c.stroke()}}""!==this.d&&(b=Fm(b),wm(this,b,b.length,2))}};function Gm(b){var c=Va(sb(b.p),Number);gb(c);var d,e,f,g,h;d=0;for(e=c.length;d<e;++d)for(f=b.p[c[d].toString()],g=0,h=f.length;g<h;++g)f[g](b)}
+function xm(b,c){var d=b.b,e=b.la;null===e?(d.fillStyle=c.fillStyle,b.la={fillStyle:c.fillStyle}):e.fillStyle!=c.fillStyle&&(e.fillStyle=d.fillStyle=c.fillStyle)}
+function ym(b,c){var d=b.b,e=b.ga;null===e?(d.lineCap=c.lineCap,cg&&d.setLineDash(c.lineDash),d.lineJoin=c.lineJoin,d.lineWidth=c.lineWidth,d.miterLimit=c.miterLimit,d.strokeStyle=c.strokeStyle,b.ga={lineCap:c.lineCap,lineDash:c.lineDash,lineJoin:c.lineJoin,lineWidth:c.lineWidth,miterLimit:c.miterLimit,strokeStyle:c.strokeStyle}):(e.lineCap!=c.lineCap&&(e.lineCap=d.lineCap=c.lineCap),cg&&!ib(e.lineDash,c.lineDash)&&d.setLineDash(e.lineDash=c.lineDash),e.lineJoin!=c.lineJoin&&(e.lineJoin=d.lineJoin=
+c.lineJoin),e.lineWidth!=c.lineWidth&&(e.lineWidth=d.lineWidth=c.lineWidth),e.miterLimit!=c.miterLimit&&(e.miterLimit=d.miterLimit=c.miterLimit),e.strokeStyle!=c.strokeStyle&&(e.strokeStyle=d.strokeStyle=c.strokeStyle))}
+l.wa=function(b,c){if(null===b)this.c=null;else{var d=b.a;this.c={fillStyle:wg(null===d?Ol:d)}}if(null===c)this.a=null;else{var d=c.a,e=c.d,f=c.b,g=c.e,h=c.c,k=c.f;this.a={lineCap:m(e)?e:"round",lineDash:null!=f?f:Pl,lineJoin:m(g)?g:"round",lineWidth:this.r*(m(h)?h:1),miterLimit:m(k)?k:10,strokeStyle:wg(null===d?Ql:d)}}};
+l.cb=function(b){if(null===b)this.f=null;else{var c=b.tb(),d=b.yb(1),e=b.zb(),f=b.ab();this.J=c[0];this.Q=c[1];this.V=f[1];this.f=d;this.j=b.p;this.Sa=e[0];this.ea=e[1];this.Da=b.q;this.oa=b.i;this.g=b.n;this.Ea=b.r;this.ba=f[0]}};
+l.xa=function(b){if(null===b)this.d="";else{var c=b.a;null===c?this.o=null:(c=c.a,this.o={fillStyle:wg(null===c?Ol:c)});var d=b.f;if(null===d)this.n=null;else{var c=d.a,e=d.d,f=d.b,g=d.e,h=d.c,d=d.f;this.n={lineCap:m(e)?e:"round",lineDash:null!=f?f:Pl,lineJoin:m(g)?g:"round",lineWidth:m(h)?h:1,miterLimit:m(d)?d:10,strokeStyle:wg(null===c?Ql:c)}}var c=b.d,e=b.i,f=b.n,g=b.e,h=b.c,d=b.b,k=b.g;b=b.o;this.ca={font:m(c)?c:"10px sans-serif",textAlign:m(k)?k:"center",textBaseline:m(b)?b:"middle"};this.d=
+m(d)?d:"";this.qa=m(e)?this.r*e:0;this.Ta=m(f)?this.r*f:0;this.t=m(g)?g:0;this.i=this.r*(m(h)?h:1)}};var Bm={Point:um.prototype.rb,LineString:um.prototype.Cb,Polygon:um.prototype.Sb,MultiPoint:um.prototype.qb,MultiLineString:um.prototype.kc,MultiPolygon:um.prototype.lc,GeometryCollection:um.prototype.Zc,Circle:um.prototype.jc};var Hm=["Polygon","LineString","Image","Text"];function Im(b,c,d){this.ga=b;this.V=c;this.d=0;this.resolution=d;this.J=this.F=null;this.c=[];this.coordinates=[];this.ca=Kd();this.a=[];this.ba=[];this.la=Kd()}
+function Jm(b,c,d,e,f,g){var h=b.coordinates.length,k=b.he(),n=[c[d],c[d+1]],p=[NaN,NaN],q=!0,r,s,v;for(r=d+f;r<e;r+=f)p[0]=c[r],p[1]=c[r+1],v=be(k,p),v!==s?(q&&(b.coordinates[h++]=n[0],b.coordinates[h++]=n[1]),b.coordinates[h++]=p[0],b.coordinates[h++]=p[1],q=!1):1===v?(b.coordinates[h++]=p[0],b.coordinates[h++]=p[1],q=!1):q=!0,n[0]=p[0],n[1]=p[1],s=v;r===d+f&&(b.coordinates[h++]=n[0],b.coordinates[h++]=n[1]);g&&(b.coordinates[h++]=c[d],b.coordinates[h++]=c[d+1]);return h}
+function Km(b,c){b.F=[0,c,0];b.c.push(b.F);b.J=[0,c,0];b.a.push(b.J)}
+function Lm(b,c,d,e,f,g,h,k){var n;Wj(e,b.ca)?n=b.ba:(n=Nk(b.coordinates,0,b.coordinates.length,2,e,b.ba),Nd(b.ca,e));e=0;var p=h.length,q=0,r;for(b=b.la;e<p;){var s=h[e],v,y,C,F;switch(s[0]){case 0:q=s[1];q=ma(q).toString();m(x(g,q))?e=s[2]:++e;break;case 1:c.beginPath();++e;break;case 2:q=s[1];r=n[q];var G=n[q+1],w=n[q+2]-r,q=n[q+3]-G;c.arc(r,G,Math.sqrt(w*w+q*q),0,2*Math.PI,!0);++e;break;case 3:c.closePath();++e;break;case 4:q=s[1];r=s[2];v=s[3];C=s[4]*d;var U=s[5]*d,N=s[6];y=s[7];var Y=s[8],T=
+s[9],G=s[11],w=s[12],qa=s[13],vb=s[14];for(s[10]&&(G+=f);q<r;q+=2){s=n[q]-C;F=n[q+1]-U;qa&&(s=s+.5|0,F=F+.5|0);if(1!=w||0!==G){var Ka=s+C,ac=F+U;Vj(b,Ka,ac,w,w,G,-Ka,-ac);c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13])}Ka=c.globalAlpha;1!=y&&(c.globalAlpha=Ka*y);c.drawImage(v,Y,T,vb,N,s,F,vb*d,N*d);1!=y&&(c.globalAlpha=Ka);1==w&&0===G||c.setTransform(1,0,0,1,0,0)}++e;break;case 5:q=s[1];r=s[2];C=s[3];U=s[4]*d;N=s[5]*d;G=s[6];w=s[7]*d;v=s[8];for(y=s[9];q<r;q+=2){s=n[q]+U;F=n[q+1]+N;if(1!=w||0!==G)Vj(b,
+s,F,w,w,G,-s,-F),c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13]);y&&c.strokeText(C,s,F);v&&c.fillText(C,s,F);1==w&&0===G||c.setTransform(1,0,0,1,0,0)}++e;break;case 6:if(m(k)&&(q=s[1],q=k(q)))return q;++e;break;case 7:c.fill();++e;break;case 8:q=s[1];r=s[2];c.moveTo(n[q],n[q+1]);for(q+=2;q<r;q+=2)c.lineTo(n[q],n[q+1]);++e;break;case 9:c.fillStyle=s[1];++e;break;case 10:q=m(s[7])?s[7]:!0;r=s[2];c.strokeStyle=s[1];c.lineWidth=q?r*d:r;c.lineCap=s[3];c.lineJoin=s[4];c.miterLimit=s[5];cg&&c.setLineDash(s[6]);
+++e;break;case 11:c.font=s[1];c.textAlign=s[2];c.textBaseline=s[3];++e;break;case 12:c.stroke();++e;break;default:++e}}}Im.prototype.Hc=function(b,c,d,e,f){Lm(this,b,c,d,e,f,this.c,void 0)};function Mm(b){var c=b.a;c.reverse();var d,e=c.length,f,g,h=-1;for(d=0;d<e;++d)if(f=c[d],g=f[0],6==g)h=d;else if(0==g){f[2]=d;f=b.a;for(g=d;h<g;){var k=f[h];f[h]=f[g];f[g]=k;++h;--g}h=-1}}function Nm(b,c){b.F[2]=b.c.length;b.F=null;b.J[2]=b.a.length;b.J=null;var d=[6,c];b.c.push(d);b.a.push(d)}
+Im.prototype.Kb=ca;Im.prototype.he=function(){return this.V};function Om(b,c,d){Im.call(this,b,c,d);this.g=this.Q=null;this.t=this.r=this.q=this.p=this.j=this.n=this.i=this.o=this.f=this.e=this.b=void 0}u(Om,Im);
+Om.prototype.rb=function(b,c){if(null!==this.g){Km(this,c);var d=b.k,e=this.coordinates.length,d=Jm(this,d,0,d.length,b.s,!1);this.c.push([4,e,d,this.g,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);this.a.push([4,e,d,this.Q,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);Nm(this,c)}};
+Om.prototype.qb=function(b,c){if(null!==this.g){Km(this,c);var d=b.k,e=this.coordinates.length,d=Jm(this,d,0,d.length,b.s,!1);this.c.push([4,e,d,this.g,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);this.a.push([4,e,d,this.Q,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);Nm(this,c)}};Om.prototype.Kb=function(){Mm(this);this.e=this.b=void 0;this.g=this.Q=null;this.t=this.r=this.p=this.j=this.n=this.i=this.o=this.q=this.f=void 0};
+Om.prototype.cb=function(b){var c=b.tb(),d=b.ab(),e=b.te(1),f=b.yb(1),g=b.zb();this.b=c[0];this.e=c[1];this.Q=e;this.g=f;this.f=d[1];this.o=b.p;this.i=g[0];this.n=g[1];this.j=b.q;this.p=b.i;this.q=b.n;this.r=b.r;this.t=d[0]};function Pm(b,c,d){Im.call(this,b,c,d);this.b={Cc:void 0,xc:void 0,yc:null,zc:void 0,Ac:void 0,Bc:void 0,me:0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}u(Pm,Im);
+function Qm(b,c,d,e,f){var g=b.coordinates.length;c=Jm(b,c,d,e,f,!1);g=[8,g,c];b.c.push(g);b.a.push(g);return e}l=Pm.prototype;l.he=function(){var b=this.V;this.d&&(b=Yd(b,this.resolution*(this.d+1)/2));return b};
+function Rm(b){var c=b.b,d=c.strokeStyle,e=c.lineCap,f=c.lineDash,g=c.lineJoin,h=c.lineWidth,k=c.miterLimit;c.Cc==d&&c.xc==e&&ib(c.yc,f)&&c.zc==g&&c.Ac==h&&c.Bc==k||(c.me!=b.coordinates.length&&(b.c.push([12]),c.me=b.coordinates.length),b.c.push([10,d,h,e,g,k,f],[1]),c.Cc=d,c.xc=e,c.yc=f,c.zc=g,c.Ac=h,c.Bc=k)}
+l.Cb=function(b,c){var d=this.b,e=d.lineWidth;m(d.strokeStyle)&&m(e)&&(Rm(this),Km(this,c),this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]),d=b.k,Qm(this,d,0,d.length,b.s),this.a.push([12]),Nm(this,c))};
+l.kc=function(b,c){var d=this.b,e=d.lineWidth;if(m(d.strokeStyle)&&m(e)){Rm(this);Km(this,c);this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]);var d=b.b,e=b.k,f=b.s,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Qm(this,e,g,d[h],f);this.a.push([12]);Nm(this,c)}};l.Kb=function(){this.b.me!=this.coordinates.length&&this.c.push([12]);Mm(this);this.b=null};
+l.wa=function(b,c){var d=c.a;this.b.strokeStyle=wg(null===d?Ql:d);d=c.d;this.b.lineCap=m(d)?d:"round";d=c.b;this.b.lineDash=null===d?Pl:d;d=c.e;this.b.lineJoin=m(d)?d:"round";d=c.c;this.b.lineWidth=m(d)?d:1;d=c.f;this.b.miterLimit=m(d)?d:10;this.d=Math.max(this.d,this.b.lineWidth)};
+function Sm(b,c,d){Im.call(this,b,c,d);this.b={Ve:void 0,Cc:void 0,xc:void 0,yc:null,zc:void 0,Ac:void 0,Bc:void 0,fillStyle:void 0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}u(Sm,Im);
+function Tm(b,c,d,e,f){var g=b.b,h=[1];b.c.push(h);b.a.push(h);var k,h=0;for(k=e.length;h<k;++h){var n=e[h],p=b.coordinates.length;d=Jm(b,c,d,n,f,!0);d=[8,p,d];p=[3];b.c.push(d,p);b.a.push(d,p);d=n}c=[7];b.a.push(c);m(g.fillStyle)&&b.c.push(c);m(g.strokeStyle)&&(g=[12],b.c.push(g),b.a.push(g));return d}l=Sm.prototype;
+l.jc=function(b,c){var d=this.b,e=d.strokeStyle;if(m(d.fillStyle)||m(e)){Um(this);Km(this,c);this.a.push([9,wg(Ol)]);m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var f=b.k,e=this.coordinates.length;Jm(this,f,0,f.length,b.s,!1);f=[1];e=[2,e];this.c.push(f,e);this.a.push(f,e);e=[7];this.a.push(e);m(d.fillStyle)&&this.c.push(e);m(d.strokeStyle)&&(d=[12],this.c.push(d),this.a.push(d));Nm(this,c)}};
+l.Sb=function(b,c){var d=this.b,e=d.strokeStyle;if(m(d.fillStyle)||m(e))Um(this),Km(this,c),this.a.push([9,wg(Ol)]),m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]),d=b.b,e=xl(b),Tm(this,e,0,d,b.s),Nm(this,c)};
+l.lc=function(b,c){var d=this.b,e=d.strokeStyle;if(m(d.fillStyle)||m(e)){Um(this);Km(this,c);this.a.push([9,wg(Ol)]);m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var d=b.b,e=Em(b),f=b.s,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Tm(this,e,g,d[h],f);Nm(this,c)}};l.Kb=function(){Mm(this);this.b=null;var b=this.ga;if(0!==b){var c=this.coordinates,d,e;d=0;for(e=c.length;d<e;++d)c[d]=b*Math.round(c[d]/b)}};
+l.he=function(){var b=this.V;this.d&&(b=Yd(b,this.resolution*(this.d+1)/2));return b};
+l.wa=function(b,c){var d=this.b;if(null===b)d.fillStyle=void 0;else{var e=b.a;d.fillStyle=wg(null===e?Ol:e)}null===c?(d.strokeStyle=void 0,d.lineCap=void 0,d.lineDash=null,d.lineJoin=void 0,d.lineWidth=void 0,d.miterLimit=void 0):(e=c.a,d.strokeStyle=wg(null===e?Ql:e),e=c.d,d.lineCap=m(e)?e:"round",e=c.b,d.lineDash=null===e?Pl:e.slice(),e=c.e,d.lineJoin=m(e)?e:"round",e=c.c,d.lineWidth=m(e)?e:1,e=c.f,d.miterLimit=m(e)?e:10,this.d=Math.max(this.d,d.lineWidth))};
+function Um(b){var c=b.b,d=c.fillStyle,e=c.strokeStyle,f=c.lineCap,g=c.lineDash,h=c.lineJoin,k=c.lineWidth,n=c.miterLimit;m(d)&&c.Ve!=d&&(b.c.push([9,d]),c.Ve=c.fillStyle);!m(e)||c.Cc==e&&c.xc==f&&c.yc==g&&c.zc==h&&c.Ac==k&&c.Bc==n||(b.c.push([10,e,k,f,h,n,g]),c.Cc=e,c.xc=f,c.yc=g,c.zc=h,c.Ac=k,c.Bc=n)}function Vm(b,c,d){Im.call(this,b,c,d);this.r=this.q=this.p=null;this.g="";this.j=this.n=this.i=this.o=0;this.f=this.e=this.b=null}u(Vm,Im);
+Vm.prototype.sb=function(b,c,d,e,f,g){if(""!==this.g&&null!==this.f&&(null!==this.b||null!==this.e)){if(null!==this.b){f=this.b;var h=this.p;if(null===h||h.fillStyle!=f.fillStyle){var k=[9,f.fillStyle];this.c.push(k);this.a.push(k);null===h?this.p={fillStyle:f.fillStyle}:h.fillStyle=f.fillStyle}}null!==this.e&&(f=this.e,h=this.q,null===h||h.lineCap!=f.lineCap||h.lineDash!=f.lineDash||h.lineJoin!=f.lineJoin||h.lineWidth!=f.lineWidth||h.miterLimit!=f.miterLimit||h.strokeStyle!=f.strokeStyle)&&(k=[10,
+f.strokeStyle,f.lineWidth,f.lineCap,f.lineJoin,f.miterLimit,f.lineDash,!1],this.c.push(k),this.a.push(k),null===h?this.q={lineCap:f.lineCap,lineDash:f.lineDash,lineJoin:f.lineJoin,lineWidth:f.lineWidth,miterLimit:f.miterLimit,strokeStyle:f.strokeStyle}:(h.lineCap=f.lineCap,h.lineDash=f.lineDash,h.lineJoin=f.lineJoin,h.lineWidth=f.lineWidth,h.miterLimit=f.miterLimit,h.strokeStyle=f.strokeStyle));f=this.f;h=this.r;if(null===h||h.font!=f.font||h.textAlign!=f.textAlign||h.textBaseline!=f.textBaseline)k=
+[11,f.font,f.textAlign,f.textBaseline],this.c.push(k),this.a.push(k),null===h?this.r={font:f.font,textAlign:f.textAlign,textBaseline:f.textBaseline}:(h.font=f.font,h.textAlign=f.textAlign,h.textBaseline=f.textBaseline);Km(this,g);f=this.coordinates.length;b=Jm(this,b,c,d,e,!1);b=[5,f,b,this.g,this.o,this.i,this.n,this.j,null!==this.b,null!==this.e];this.c.push(b);this.a.push(b);Nm(this,g)}};
+Vm.prototype.xa=function(b){if(null===b)this.g="";else{var c=b.a;null===c?this.b=null:(c=c.a,c=wg(null===c?Ol:c),null===this.b?this.b={fillStyle:c}:this.b.fillStyle=c);var d=b.f;if(null===d)this.e=null;else{var c=d.a,e=d.d,f=d.b,g=d.e,h=d.c,d=d.f,e=m(e)?e:"round",f=null!=f?f.slice():Pl,g=m(g)?g:"round",h=m(h)?h:1,d=m(d)?d:10,c=wg(null===c?Ql:c);if(null===this.e)this.e={lineCap:e,lineDash:f,lineJoin:g,lineWidth:h,miterLimit:d,strokeStyle:c};else{var k=this.e;k.lineCap=e;k.lineDash=f;k.lineJoin=g;k.lineWidth=
+h;k.miterLimit=d;k.strokeStyle=c}}var n=b.d,c=b.i,e=b.n,f=b.e,h=b.c,d=b.b,g=b.g,k=b.o;b=m(n)?n:"10px sans-serif";g=m(g)?g:"center";k=m(k)?k:"middle";null===this.f?this.f={font:b,textAlign:g,textBaseline:k}:(n=this.f,n.font=b,n.textAlign=g,n.textBaseline=k);this.g=m(d)?d:"";this.o=m(c)?c:0;this.i=m(e)?e:0;this.n=m(f)?f:0;this.j=m(h)?h:1}};function Wm(b,c,d){this.g=b;this.b=c;this.f=d;this.c={};this.d=Tf(1,1);this.e=Kd()}function Xm(b){for(var c in b.c){var d=b.c[c],e;for(e in d)d[e].Kb()}}
+function ck(b,c,d,e,f,g){var h=b.e;Vj(h,.5,.5,1/c,-1/c,-d,-e[0],-e[1]);var k=b.d;k.clearRect(0,0,1,1);return Ym(b,k,h,d,f,function(b){if(0<k.getImageData(0,0,1,1).data[3]){if(b=g(b))return b;k.clearRect(0,0,1,1)}})}Wm.prototype.a=function(b,c){var d=m(b)?b.toString():"0",e=this.c[d];m(e)||(e={},this.c[d]=e);d=e[c];m(d)||(d=new Zm[c](this.g,this.b,this.f),e[c]=d);return d};Wm.prototype.ia=function(){return xb(this.c)};
+function $m(b,c,d,e,f,g){var h=Va(sb(b.c),Number);gb(h);var k=b.b,n=k[0],p=k[1],q=k[2],k=k[3],n=[n,p,n,k,q,k,q,p];Nk(n,0,8,2,e,n);c.save();c.beginPath();c.moveTo(n[0],n[1]);c.lineTo(n[2],n[3]);c.lineTo(n[4],n[5]);c.lineTo(n[6],n[7]);c.closePath();c.clip();for(var r,s,n=0,p=h.length;n<p;++n)for(r=b.c[h[n].toString()],q=0,k=Hm.length;q<k;++q)s=r[Hm[q]],m(s)&&s.Hc(c,d,e,f,g);c.restore()}
+function Ym(b,c,d,e,f,g){var h=Va(sb(b.c),Number);gb(h,function(b,c){return c-b});var k,n,p,q,r;k=0;for(n=h.length;k<n;++k)for(q=b.c[h[k].toString()],p=Hm.length-1;0<=p;--p)if(r=q[Hm[p]],m(r)&&(r=Lm(r,c,1,d,e,f,r.a,g)))return r}var Zm={Image:Om,LineString:Pm,Polygon:Sm,Text:Vm};function an(b,c){Hj.call(this,b,c);this.F=Kd()}u(an,Hj);
+an.prototype.j=function(b,c,d){bn(this,"precompose",d,b,void 0);var e=this.p();if(null!==e){var f=c.extent,g=m(f);if(g){var h=me(f),k=je(f),n=ie(f),f=he(f);Xj(b.coordinateToPixelMatrix,h,h);Xj(b.coordinateToPixelMatrix,k,k);Xj(b.coordinateToPixelMatrix,n,n);Xj(b.coordinateToPixelMatrix,f,f);d.save();d.beginPath();d.moveTo(h[0],h[1]);d.lineTo(k[0],k[1]);d.lineTo(n[0],n[1]);d.lineTo(f[0],f[1]);d.clip()}h=this.n();k=d.globalAlpha;d.globalAlpha=c.opacity;0===b.viewState.rotation?(c=h[13],n=e.width*h[0],
+f=e.height*h[5],d.drawImage(e,0,0,+e.width,+e.height,Math.round(h[12]),Math.round(c),Math.round(n),Math.round(f))):(d.setTransform(h[0],h[1],h[4],h[5],h[12],h[13]),d.drawImage(e,0,0),d.setTransform(1,0,0,1,0,0));d.globalAlpha=k;g&&d.restore()}bn(this,"postcompose",d,b,void 0)};function bn(b,c,d,e,f){var g=b.a;ld(g,c)&&(b=m(f)?f:cn(b,e),b=new um(d,e.pixelRatio,e.extent,b,e.viewState.rotation),g.dispatchEvent(new Al(c,g,b,null,e,d,null)),Gm(b))}
+function cn(b,c){var d=c.viewState,e=c.pixelRatio;return Vj(b.F,e*c.size[0]/2,e*c.size[1]/2,e/d.resolution,-e/d.resolution,-d.rotation,-d.center[0],-d.center[1])}var dn=function(){var b=null,c=null;return function(d){if(null===b){b=Tf(1,1);c=b.createImageData(1,1);var e=c.data;e[0]=42;e[1]=84;e[2]=126;e[3]=255}var e=b.canvas,f=d[0]<=e.width&&d[1]<=e.height;f||(e.width=d[0],e.height=d[1],e=d[0]-1,d=d[1]-1,b.putImageData(c,e,d),d=b.getImageData(e,d,1,1),f=ib(c.data,d.data));return f}}();function en(b,c){an.call(this,b,c);this.c=null;this.d=Kd()}u(en,an);en.prototype.Zb=function(b,c,d,e){var f=this.a;return f.a().yd(c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};en.prototype.p=function(){return null===this.c?null:this.c.c()};en.prototype.n=function(){return this.d};
+en.prototype.i=function(b,c){var d=b.pixelRatio,e=b.viewState,f=e.center,g=e.resolution,h=e.rotation,k,n=this.a.a(),p=b.viewHints;k=b.extent;m(c.extent)&&(k=pe(k,c.extent));p[0]||p[1]||se(k)||(e=e.projection,p=n.g,null===p||(e=p),k=n.rc(k,g,d,e),null!==k&&(e=k.state,0==e?(Wc(k,"change",this.o,!1,this),k.load()):2==e&&(this.c=k)));if(null!==this.c){k=this.c;var e=k.D(),p=k.resolution,q=k.e,g=d*p/(g*q);Vj(this.d,d*b.size[0]/2,d*b.size[1]/2,g,g,h,q*(e[0]-f[0])/p,q*(f[1]-e[3])/p);Kj(b.attributions,k.f);
+Lj(b,n)}return!0};function fn(b,c){an.call(this,b,c);this.c=this.e=null;this.g=!1;this.q=null;this.r=Kd();this.t=NaN;this.f=this.d=null}u(fn,an);fn.prototype.p=function(){return this.e};fn.prototype.n=function(){return this.r};
+fn.prototype.i=function(b,c){var d=b.pixelRatio,e=b.viewState,f=e.projection,g=this.a,h=g.a(),k=Gj(h,f),n=h.bd(),p=ec(k.a,e.resolution,0),q=h.Gc(p,b.pixelRatio,f),r=k.ka(p),s=r/(q/k.sa(p)),v=e.center,y;r==e.resolution?(v=Oj(v,r,b.size),y=ne(v,r,e.rotation,b.size)):y=b.extent;m(c.extent)&&(y=pe(y,c.extent));if(se(y))return!1;var C=Aj(k,y,r),F=q*(C.d-C.a+1),G=q*(C.c-C.b+1),w,U;null===this.e?(U=Tf(F,G),this.e=U.canvas,this.c=[F,G],this.q=U,this.g=!dn(this.c)):(w=this.e,U=this.q,this.c[0]<F||this.c[1]<
+G||this.g&&(this.c[0]>F||this.c[1]>G)?(w.width=F,w.height=G,this.c=[F,G],this.g=!dn(this.c),this.d=null):(F=this.c[0],G=this.c[1],p==this.t&&rf(this.d,C)||(this.d=null)));var N,Y;null===this.d?(F/=q,G/=q,N=C.a-Math.floor((F-(C.d-C.a+1))/2),Y=C.b-Math.floor((G-(C.c-C.b+1))/2),this.t=p,this.d=new of(N,N+F-1,Y,Y+G-1),this.f=Array(F*G),G=this.d):(G=this.d,F=G.d-G.a+1);w={};w[p]={};var T=[],qa=sa(h.fe,h,w,Nj(function(b){return null!==b&&2==b.state},h,d,f)),vb=g.ea();m(vb)||(vb=!0);var Ka=Vd(),ac=new of(0,
+0,0,0),Sb,La,Pa;for(Y=C.a;Y<=C.d;++Y)for(Pa=C.b;Pa<=C.c;++Pa)La=h.Fb(p,Y,Pa,d,f),N=La.state,2==N||4==N||3==N&&!vb?w[p][nf(La.a)]=La:(Sb=k.$c(La.a,qa,null,ac,Ka),Sb||(T.push(La),Sb=k.kd(La.a,ac,Ka),null===Sb||qa(p+1,Sb)));qa=0;for(Sb=T.length;qa<Sb;++qa)La=T[qa],Y=q*(La.a[1]-G.a),Pa=q*(G.c-La.a[2]),U.clearRect(Y,Pa,q,q);T=Va(sb(w),Number);gb(T);var Ud=h.t,qd=me(yj(k,[p,G.a,G.c],Ka)),fd,ve,Wi,Eh,Bf,Gl,qa=0;for(Sb=T.length;qa<Sb;++qa)if(fd=T[qa],q=h.Gc(fd,d,f),Eh=w[fd],fd==p)for(Wi in Eh)La=Eh[Wi],ve=
+(La.a[2]-G.b)*F+(La.a[1]-G.a),this.f[ve]!=La&&(Y=q*(La.a[1]-G.a),Pa=q*(G.c-La.a[2]),N=La.state,4!=N&&(3!=N||vb)&&Ud||U.clearRect(Y,Pa,q,q),2==N&&U.drawImage(La.Na(),n,n,q,q,Y,Pa,q,q),this.f[ve]=La);else for(Wi in fd=k.ka(fd)/r,Eh)for(La=Eh[Wi],ve=yj(k,La.a,Ka),Y=(ve[0]-qd[0])/s,Pa=(qd[1]-ve[3])/s,Gl=fd*q,Bf=fd*q,N=La.state,4!=N&&Ud||U.clearRect(Y,Pa,Gl,Bf),2==N&&U.drawImage(La.Na(),n,n,q,q,Y,Pa,Gl,Bf),La=zj(k,ve,p,ac),N=Math.max(La.a,G.a),Pa=Math.min(La.d,G.d),Y=Math.max(La.b,G.b),La=Math.min(La.c,
+G.c);N<=Pa;++N)for(Bf=Y;Bf<=La;++Bf)ve=(Bf-G.b)*F+(N-G.a),this.f[ve]=void 0;Mj(b.usedTiles,h,p,C);Pj(b,h,k,d,f,y,p,g.r());Jj(b,h);Lj(b,h);Vj(this.r,d*b.size[0]/2,d*b.size[1]/2,d*s/e.resolution,d*s/e.resolution,e.rotation,(qd[0]-v[0])/s,(v[1]-qd[1])/s);return!0};function gn(b,c,d){Ok.call(this);this.ag(b,m(c)?c:0,d)}u(gn,Ok);l=gn.prototype;l.clone=function(){var b=new gn(null);Qk(b,this.a,this.k.slice());b.l();return b};l.Va=function(b,c,d,e){var f=this.k;b-=f[0];var g=c-f[1];c=b*b+g*g;if(c<e){if(0===c)for(e=0;e<this.s;++e)d[e]=f[e];else for(e=this.vf()/Math.sqrt(c),d[0]=f[0]+e*b,d[1]=f[1]+e*g,e=2;e<this.s;++e)d[e]=f[e];d.length=this.s;return c}return e};l.Jb=function(b,c){var d=this.k,e=b-d[0],d=c-d[1];return e*e+d*d<=hn(this)};
+l.qe=function(){return this.k.slice(0,this.s)};l.D=function(b){if(this.g!=this.c){var c=this.k,d=c[this.s]-c[0];this.extent=Xd(c[0]-d,c[1]-d,c[0]+d,c[1]+d,this.extent);this.g=this.c}return te(this.extent,b)};l.vf=function(){return Math.sqrt(hn(this))};function hn(b){var c=b.k[b.s]-b.k[0];b=b.k[b.s+1]-b.k[1];return c*c+b*b}l.H=function(){return"Circle"};l.kj=function(b){var c=this.s,d=b.slice();d[c]=d[0]+(this.k[c]-this.k[0]);var e;for(e=1;e<c;++e)d[c+e]=b[e];Qk(this,this.a,d);this.l()};
+l.ag=function(b,c,d){if(null===b)Qk(this,"XY",null);else{Rk(this,d,b,0);null===this.k&&(this.k=[]);d=this.k;b=al(d,b);d[b++]=d[0]+c;var e;c=1;for(e=this.s;c<e;++c)d[b++]=d[c];d.length=b}this.l()};l.jl=function(b){this.k[this.s]=this.k[0]+b;this.l()};function jn(b){Mk.call(this);this.d=m(b)?b:null;kn(this)}u(jn,Mk);function ln(b){var c=[],d,e;d=0;for(e=b.length;d<e;++d)c.push(b[d].clone());return c}function mn(b){var c,d;if(null!==b.d)for(c=0,d=b.d.length;c<d;++c)Xc(b.d[c],"change",b.l,!1,b)}function kn(b){var c,d;if(null!==b.d)for(c=0,d=b.d.length;c<d;++c)z(b.d[c],"change",b.l,!1,b)}l=jn.prototype;l.clone=function(){var b=new jn(null);b.bg(this.d);return b};
+l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;var f=this.d,g,h;g=0;for(h=f.length;g<h;++g)e=f[g].Va(b,c,d,e);return e};l.Jb=function(b,c){var d=this.d,e,f;e=0;for(f=d.length;e<f;++e)if(d[e].Jb(b,c))return!0;return!1};l.D=function(b){if(this.g!=this.c){var c=Xd(Infinity,Infinity,-Infinity,-Infinity,this.extent),d=this.d,e,f;e=0;for(f=d.length;e<f;++e)ee(c,d[e].D());this.extent=c;this.g=this.c}return te(this.extent,b)};l.af=function(){return ln(this.d)};
+l.ke=function(b){this.j!=this.c&&(yb(this.o),this.i=0,this.j=this.c);if(0>b||0!==this.i&&b<this.i)return this;var c=b.toString();if(this.o.hasOwnProperty(c))return this.o[c];var d=[],e=this.d,f=!1,g,h;g=0;for(h=e.length;g<h;++g){var k=e[g],n=k.ke(b);d.push(n);n!==k&&(f=!0)}if(f)return b=new jn(null),mn(b),b.d=d,kn(b),b.l(),this.o[c]=b;this.i=b;return this};l.H=function(){return"GeometryCollection"};l.ha=function(b){var c=this.d,d,e;d=0;for(e=c.length;d<e;++d)if(c[d].ha(b))return!0;return!1};
+l.ia=function(){return 0==this.d.length};l.bg=function(b){b=ln(b);mn(this);this.d=b;kn(this);this.l()};l.ma=function(b){var c=this.d,d,e;d=0;for(e=c.length;d<e;++d)c[d].ma(b);this.l()};l.Aa=function(b,c){var d=this.d,e,f;e=0;for(f=d.length;e<f;++e)d[e].Aa(b,c);this.l()};l.M=function(){mn(this);jn.S.M.call(this)};function nn(b,c,d,e,f){var g=NaN,h=NaN,k=(d-c)/e;if(0!==k)if(1==k)g=b[c],h=b[c+1];else if(2==k)g=.5*b[c]+.5*b[c+e],h=.5*b[c+1]+.5*b[c+e+1];else{var h=b[c],k=b[c+1],n=0,g=[0],p;for(p=c+e;p<d;p+=e){var q=b[p],r=b[p+1],n=n+Math.sqrt((q-h)*(q-h)+(r-k)*(r-k));g.push(n);h=q;k=r}d=.5*n;for(var s,h=hb,k=0,n=g.length;k<n;)p=k+n>>1,q=h(d,g[p]),0<q?k=p+1:(n=p,s=!q);s=s?k:~k;0>s?(d=(d-g[-s-2])/(g[-s-1]-g[-s-2]),c+=(-s-2)*e,g=$b(b[c],b[c+e],d),h=$b(b[c+1],b[c+e+1],d)):(g=b[c+s*e],h=b[c+s*e+1])}return null!=f?
+(f[0]=g,f[1]=h,f):[g,h]}function on(b,c,d,e,f,g){if(d==c)return null;if(f<b[c+e-1])return g?(d=b.slice(c,c+e),d[e-1]=f,d):null;if(b[d-1]<f)return g?(d=b.slice(d-e,d),d[e-1]=f,d):null;if(f==b[c+e-1])return b.slice(c,c+e);c/=e;for(d/=e;c<d;)g=c+d>>1,f<b[(g+1)*e-1]?d=g:c=g+1;d=b[c*e-1];if(f==d)return b.slice((c-1)*e,(c-1)*e+e);g=(f-d)/(b[(c+1)*e-1]-d);d=[];var h;for(h=0;h<e-1;++h)d.push($b(b[(c-1)*e+h],b[c*e+h],g));d.push(f);return d}
+function pn(b,c,d,e,f,g){var h=0;if(g)return on(b,h,c[c.length-1],d,e,f);if(e<b[d-1])return f?(b=b.slice(0,d),b[d-1]=e,b):null;if(b[b.length-1]<e)return f?(b=b.slice(b.length-d),b[d-1]=e,b):null;f=0;for(g=c.length;f<g;++f){var k=c[f];if(h!=k){if(e<b[h+d-1])break;if(e<=b[k-1])return on(b,h,k,d,e,!1);h=k}}return null};function M(b,c){Ok.call(this);this.b=null;this.p=this.q=this.n=-1;this.U(b,c)}u(M,Ok);l=M.prototype;l.Sg=function(b){null===this.k?this.k=b.slice():db(this.k,b);this.l()};l.clone=function(){var b=new M(null);qn(b,this.a,this.k.slice());return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.p!=this.c&&(this.q=Math.sqrt(Xk(this.k,0,this.k.length,this.s,0)),this.p=this.c);return Zk(this.k,0,this.k.length,this.s,this.q,!1,b,c,d,e)};
+l.lj=function(b,c){return"XYM"!=this.a&&"XYZM"!=this.a?null:on(this.k,0,this.k.length,this.s,b,m(c)?c:!1)};l.K=function(){return dl(this.k,0,this.k.length,this.s)};l.mj=function(){var b=this.k,c=this.s,d=b[0],e=b[1],f=0,g;for(g=0+c;g<this.k.length;g+=c)var h=b[g],k=b[g+1],f=f+Math.sqrt((h-d)*(h-d)+(k-e)*(k-e)),d=h,e=k;return f};function Cm(b){b.n!=b.c&&(b.b=nn(b.k,0,b.k.length,b.s,b.b),b.n=b.c);return b.b}
+l.mc=function(b){var c=[];c.length=fl(this.k,0,this.k.length,this.s,b,c,0);b=new M(null);qn(b,"XY",c);return b};l.H=function(){return"LineString"};l.ha=function(b){return rl(this.k,0,this.k.length,this.s,b)};l.U=function(b,c){null===b?qn(this,"XY",null):(Rk(this,c,b,1),null===this.k&&(this.k=[]),this.k.length=bl(this.k,0,b,this.s),this.l())};function qn(b,c,d){Qk(b,c,d);b.l()};function rn(b,c){Ok.call(this);this.b=[];this.n=this.p=-1;this.U(b,c)}u(rn,Ok);l=rn.prototype;l.Tg=function(b){null===this.k?this.k=b.k.slice():db(this.k,b.k.slice());this.b.push(this.k.length);this.l()};l.clone=function(){var b=new rn(null);sn(b,this.a,this.k.slice(),this.b.slice());return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.n!=this.c&&(this.p=Math.sqrt(Yk(this.k,0,this.b,this.s,0)),this.n=this.c);return $k(this.k,0,this.b,this.s,this.p,!1,b,c,d,e)};
+l.oj=function(b,c,d){return"XYM"!=this.a&&"XYZM"!=this.a||0===this.k.length?null:pn(this.k,this.b,this.s,b,m(c)?c:!1,m(d)?d:!1)};l.K=function(){return el(this.k,0,this.b,this.s)};l.th=function(b){if(0>b||this.b.length<=b)return null;var c=new M(null);qn(c,this.a,this.k.slice(0===b?0:this.b[b-1],this.b[b]));return c};l.Ec=function(){var b=this.k,c=this.b,d=this.a,e=[],f=0,g,h;g=0;for(h=c.length;g<h;++g){var k=c[g],n=new M(null);qn(n,d,b.slice(f,k));e.push(n);f=k}return e};
+function Dm(b){var c=[],d=b.k,e=0,f=b.b;b=b.s;var g,h;g=0;for(h=f.length;g<h;++g){var k=f[g],e=nn(d,e,k,b);db(c,e);e=k}return c}l.mc=function(b){var c=[],d=[],e=this.k,f=this.b,g=this.s,h=0,k=0,n,p;n=0;for(p=f.length;n<p;++n){var q=f[n],k=fl(e,h,q,g,b,c,k);d.push(k);h=q}c.length=k;b=new rn(null);sn(b,"XY",c,d);return b};l.H=function(){return"MultiLineString"};l.ha=function(b){a:{var c=this.k,d=this.b,e=this.s,f=0,g,h;g=0;for(h=d.length;g<h;++g){if(rl(c,f,d[g],e,b)){b=!0;break a}f=d[g]}b=!1}return b};
+l.U=function(b,c){if(null===b)sn(this,"XY",null,this.b);else{Rk(this,c,b,2);null===this.k&&(this.k=[]);var d=cl(this.k,0,b,this.s,this.b);this.k.length=0===d.length?0:d[d.length-1];this.l()}};function sn(b,c,d,e){Qk(b,c,d);b.b=e;b.l()}function tn(b,c){var d="XY",e=[],f=[],g,h;g=0;for(h=c.length;g<h;++g){var k=c[g];0===g&&(d=k.a);db(e,k.k);f.push(e.length)}sn(b,d,e,f)};function un(b,c){Ok.call(this);this.U(b,c)}u(un,Ok);l=un.prototype;l.Vg=function(b){null===this.k?this.k=b.k.slice():db(this.k,b.k);this.l()};l.clone=function(){var b=new un(null);Qk(b,this.a,this.k.slice());b.l();return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;var f=this.k,g=this.s,h,k,n;h=0;for(k=f.length;h<k;h+=g)if(n=Vk(b,c,f[h],f[h+1]),n<e){e=n;for(n=0;n<g;++n)d[n]=f[h+n];d.length=g}return e};l.K=function(){return dl(this.k,0,this.k.length,this.s)};
+l.Ch=function(b){var c=null===this.k?0:this.k.length/this.s;if(0>b||c<=b)return null;c=new jl(null);kl(c,this.a,this.k.slice(b*this.s,(b+1)*this.s));return c};l.xd=function(){var b=this.k,c=this.a,d=this.s,e=[],f,g;f=0;for(g=b.length;f<g;f+=d){var h=new jl(null);kl(h,c,b.slice(f,f+d));e.push(h)}return e};l.H=function(){return"MultiPoint"};l.ha=function(b){var c=this.k,d=this.s,e,f,g,h;e=0;for(f=c.length;e<f;e+=d)if(g=c[e],h=c[e+1],ae(b,g,h))return!0;return!1};
+l.U=function(b,c){null===b?Qk(this,"XY",null):(Rk(this,c,b,1),null===this.k&&(this.k=[]),this.k.length=bl(this.k,0,b,this.s));this.l()};function vn(b,c){Ok.call(this);this.b=[];this.p=-1;this.q=null;this.F=this.r=this.t=-1;this.n=null;this.U(b,c)}u(vn,Ok);l=vn.prototype;l.Wg=function(b){if(null===this.k)this.k=b.k.slice(),b=b.b.slice(),this.b.push();else{var c=this.k.length;db(this.k,b.k);b=b.b.slice();var d,e;d=0;for(e=b.length;d<e;++d)b[d]+=c}this.b.push(b);this.l()};l.clone=function(){var b=new vn(null);wn(b,this.a,this.k.slice(),this.b.slice());return b};
+l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;if(this.r!=this.c){var f=this.b,g=0,h=0,k,n;k=0;for(n=f.length;k<n;++k)var p=f[k],h=Yk(this.k,g,p,this.s,h),g=p[p.length-1];this.t=Math.sqrt(h);this.r=this.c}f=Em(this);g=this.b;h=this.s;k=this.t;n=0;var p=m(void 0)?void 0:[NaN,NaN],q,r;q=0;for(r=g.length;q<r;++q){var s=g[q];e=$k(f,n,s,h,k,!0,b,c,d,e,p);n=s[s.length-1]}return e};
+l.Jb=function(b,c){var d;a:{d=Em(this);var e=this.b,f=0;if(0!==e.length){var g,h;g=0;for(h=e.length;g<h;++g){var k=e[g];if(nl(d,f,k,this.s,b,c)){d=!0;break a}f=k[k.length-1]}}d=!1}return d};l.pj=function(){var b=Em(this),c=this.b,d=0,e=0,f,g;f=0;for(g=c.length;f<g;++f)var h=c[f],e=e+Tk(b,d,h,this.s),d=h[h.length-1];return e};l.K=function(){var b=this.k,c=this.b,d=this.s,e=0,f=m(void 0)?void 0:[],g=0,h,k;h=0;for(k=c.length;h<k;++h){var n=c[h];f[g++]=el(b,e,n,d,f[g]);e=n[n.length-1]}f.length=g;return f};
+function Fm(b){if(b.p!=b.c){var c=b.k,d=b.b,e=b.s,f=0,g=[],h,k,n=Vd();h=0;for(k=d.length;h<k;++h){var p=d[h],n=fe(Xd(Infinity,Infinity,-Infinity,-Infinity,void 0),c,f,p[0],e);g.push((n[0]+n[2])/2,(n[1]+n[3])/2);f=p[p.length-1]}c=Em(b);d=b.b;e=b.s;f=0;h=[];k=0;for(n=d.length;k<n;++k)p=d[k],h=pl(c,f,p,e,g,2*k,h),f=p[p.length-1];b.q=h;b.p=b.c}return b.q}l.qh=function(){var b=new un(null),c=Fm(this).slice();Qk(b,"XY",c);b.l();return b};
+function Em(b){if(b.F!=b.c){var c=b.k,d;a:{d=b.b;var e,f;e=0;for(f=d.length;e<f;++e)if(!ul(c,d[e],b.s)){d=!1;break a}d=!0}if(d)b.n=c;else{b.n=c.slice();d=c=b.n;e=b.b;f=b.s;var g=0,h,k;h=0;for(k=e.length;h<k;++h)g=vl(d,g,e[h],f);c.length=g}b.F=b.c}return b.n}l.mc=function(b){var c=[],d=[],e=this.k,f=this.b,g=this.s;b=Math.sqrt(b);var h=0,k=0,n,p;n=0;for(p=f.length;n<p;++n){var q=f[n],r=[],k=gl(e,h,q,g,b,c,k,r);d.push(r);h=q[q.length-1]}c.length=k;e=new vn(null);wn(e,"XY",c,d);return e};
+l.Dh=function(b){if(0>b||this.b.length<=b)return null;var c;0===b?c=0:(c=this.b[b-1],c=c[c.length-1]);b=this.b[b].slice();var d=b[b.length-1];if(0!==c){var e,f;e=0;for(f=b.length;e<f;++e)b[e]-=c}e=new H(null);wl(e,this.a,this.k.slice(c,d),b);return e};l.gd=function(){var b=this.a,c=this.k,d=this.b,e=[],f=0,g,h,k,n;g=0;for(h=d.length;g<h;++g){var p=d[g].slice(),q=p[p.length-1];if(0!==f)for(k=0,n=p.length;k<n;++k)p[k]-=f;k=new H(null);wl(k,b,c.slice(f,q),p);e.push(k);f=q}return e};l.H=function(){return"MultiPolygon"};
+l.ha=function(b){a:{var c=Em(this),d=this.b,e=this.s,f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];if(sl(c,f,k,e,b)){b=!0;break a}f=k[k.length-1]}b=!1}return b};l.U=function(b,c){if(null===b)wn(this,"XY",null,this.b);else{Rk(this,c,b,3);null===this.k&&(this.k=[]);var d=this.k,e=this.s,f=this.b,g=0,f=m(f)?f:[],h=0,k,n;k=0;for(n=b.length;k<n;++k)g=cl(d,g,b[k],e,f[h]),f[h++]=g,g=g[g.length-1];f.length=h;0===f.length?this.k.length=0:(d=f[f.length-1],this.k.length=0===d.length?0:d[d.length-1]);this.l()}};
+function wn(b,c,d,e){Qk(b,c,d);b.b=e;b.l()}function xn(b,c){var d="XY",e=[],f=[],g,h,k;g=0;for(h=c.length;g<h;++g){var n=c[g];0===g&&(d=n.a);var p=e.length;k=n.b;var q,r;q=0;for(r=k.length;q<r;++q)k[q]+=p;db(e,n.k);f.push(k)}wn(b,d,e,f)};function yn(b,c){return ma(b)-ma(c)}function zn(b,c){var d=.5*b/c;return d*d}function An(b,c,d,e,f,g){var h=!1,k,n;k=d.f;null===k?Bn(b,c,d,e):(n=k.ue(),2==n||3==n?(k.Ge(f,g),2==n&&Bn(b,c,d,e)):(0==n&&k.load(),k.ne(f,g),h=!0));return h}function Bn(b,c,d,e){var f=(0,d.d)(c);null!=f&&(e=f.ke(e),(0,Cn[e.H()])(b,e,d,c))}
+var Cn={Point:function(b,c,d,e){var f=d.f;if(null!==f){var g=b.a(d.a,"Image");g.cb(f);g.rb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(c.K(),0,2,2,c,e))},LineString:function(b,c,d,e){var f=d.b;if(null!==f){var g=b.a(d.a,"LineString");g.wa(null,f);g.Cb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(Cm(c),0,2,2,c,e))},Polygon:function(b,c,d,e){var f=d.e,g=d.b;if(null!==f||null!==g){var h=b.a(d.a,"Polygon");h.wa(f,g);h.Sb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(yl(c),0,2,
+2,c,e))},MultiPoint:function(b,c,d,e){var f=d.f;if(null!==f){var g=b.a(d.a,"Image");g.cb(f);g.qb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),d=c.k,b.sb(d,0,d.length,c.s,c,e))},MultiLineString:function(b,c,d,e){var f=d.b;if(null!==f){var g=b.a(d.a,"LineString");g.wa(null,f);g.kc(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),d=Dm(c),b.sb(d,0,d.length,2,c,e))},MultiPolygon:function(b,c,d,e){var f=d.e,g=d.b;if(null!==g||null!==f){var h=b.a(d.a,"Polygon");h.wa(f,g);h.lc(c,e)}f=d.c;null!==f&&(b=b.a(d.a,
+"Text"),b.xa(f),d=Fm(c),b.sb(d,0,d.length,2,c,e))},GeometryCollection:function(b,c,d,e){c=c.d;var f,g;f=0;for(g=c.length;f<g;++f)(0,Cn[c[f].H()])(b,c[f],d,e)},Circle:function(b,c,d,e){var f=d.e,g=d.b;if(null!==f||null!==g){var h=b.a(d.a,"Polygon");h.wa(f,g);h.jc(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(c.qe(),0,2,2,c,e))}};function Dn(b,c){an.call(this,b,c);this.d=!1;this.r=-1;this.q=NaN;this.f=Vd();this.c=this.g=null;this.e=Tf()}u(Dn,an);
+Dn.prototype.j=function(b,c,d){var e=cn(this,b);bn(this,"precompose",d,b,e);var f=this.c;if(null!==f&&!f.ia()){var g;ld(this.a,"render")?(this.e.canvas.width=d.canvas.width,this.e.canvas.height=d.canvas.height,g=this.e):g=d;var h=g.globalAlpha;g.globalAlpha=c.opacity;$m(f,g,b.pixelRatio,e,b.viewState.rotation,b.skippedFeatureUids);g!=d&&(bn(this,"render",g,b,e),d.drawImage(g.canvas,0,0));g.globalAlpha=h}bn(this,"postcompose",d,b,e)};
+Dn.prototype.Zb=function(b,c,d,e){if(null!==this.c){var f=this.a,g={};return ck(this.c,c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){var c=ma(b).toString();if(!(c in g))return g[c]=!0,d.call(e,b,f)})}};Dn.prototype.t=function(){Ij(this)};
+Dn.prototype.i=function(b){function c(b){var c;m(b.a)?c=b.a.call(b,k):m(d.r)&&(c=(0,d.r)(b,k));if(null!=c){if(null!=c){var e,f,g=!1;e=0;for(f=c.length;e<f;++e)g=An(q,b,c[e],zn(k,n),this.t,this)||g;b=g}else b=!1;this.d=this.d||b}}var d=this.a,e=d.a();Kj(b.attributions,e.e);Lj(b,e);if(!this.d&&(b.viewHints[0]||b.viewHints[1]))return!0;var f=b.extent,g=b.viewState,h=g.projection,k=g.resolution,n=b.pixelRatio;b=d.c;var p=d.Ab,g=d.get("renderOrder");m(g)||(g=yn);f=Yd(f,p*k);if(!this.d&&this.q==k&&this.r==
+b&&this.g==g&&$d(this.f,f))return!0;tc(this.c);this.c=null;this.d=!1;var q=new Wm(.5*k/n,f,k);e.Hb(f,k,h);if(null===g)e.Db(f,k,c,this);else{var r=[];e.Db(f,k,function(b){r.push(b)},this);gb(r,g);Ta(r,c,this)}Xm(q);this.q=k;this.r=b;this.g=g;this.f=f;this.c=q;return!0};function En(b,c){Yj.call(this,0,c);this.o=Tf();this.a=this.o.canvas;this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Mf(b,this.a,0);this.c=!0;this.i=Kd()}u(En,Yj);En.prototype.Yc=function(b){return b instanceof J?new en(this,b):b instanceof K?new fn(this,b):b instanceof L?new Dn(this,b):null};
+function Fn(b,c,d){var e=b.e,f=b.o;if(ld(e,c)){var g=d.extent,h=d.pixelRatio,k=d.viewState,n=k.resolution,p=k.rotation;Vj(b.i,b.a.width/2,b.a.height/2,h/n,-h/n,-p,-k.center[0],-k.center[1]);k=new Wm(.5*n/h,g,n);g=new um(f,h,g,b.i,p);e.dispatchEvent(new Al(c,e,g,k,d,f,null));Xm(k);k.ia()||$m(k,f,h,b.i,p,{});Gm(g);b.g=k}}En.prototype.H=function(){return"canvas"};
+En.prototype.Ld=function(b){if(null===b)this.c&&(Sg(this.a,!1),this.c=!1);else{var c=this.o,d=b.size[0]*b.pixelRatio,e=b.size[1]*b.pixelRatio;this.a.width!=d||this.a.height!=e?(this.a.width=d,this.a.height=e):c.clearRect(0,0,this.a.width,this.a.height);Zj(b);Fn(this,"precompose",b);var d=b.layerStatesArray,e=b.viewState.resolution,f,g,h,k;f=0;for(g=d.length;f<g;++f)k=d[f],h=k.layer,h=dk(this,h),k.visible&&e>=k.minResolution&&e<k.maxResolution&&"ready"==k.gc&&h.i(b,k)&&h.j(b,k,c);Fn(this,"postcompose",
+b);this.c||(Sg(this.a,!0),this.c=!0);ek(this,b);b.postRenderFunctions.push(ak)}};function Gn(b,c,d){Hj.call(this,b,c);this.target=d}u(Gn,Hj);Gn.prototype.e=ca;Gn.prototype.i=ca;function Hn(b,c){var d=If("DIV");d.style.position="absolute";Gn.call(this,b,c,d);this.c=null;this.d=Md()}u(Hn,Gn);Hn.prototype.Zb=function(b,c,d,e){var f=this.a;return f.a().yd(c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};Hn.prototype.e=function(){Kf(this.target);this.c=null};
+Hn.prototype.f=function(b,c){var d=b.viewState,e=d.center,f=d.resolution,g=d.rotation,h=this.c,k=this.a.a(),n=b.viewHints,p=b.extent;m(c.extent)&&(p=pe(p,c.extent));n[0]||n[1]||se(p)||(d=d.projection,n=k.g,null===n||(d=n),p=k.rc(p,f,b.pixelRatio,d),null!==p&&(d=p.state,0==d?(Wc(p,"change",this.o,!1,this),p.load()):2==d&&(h=p)));null!==h&&(d=h.D(),n=h.resolution,p=Kd(),Vj(p,b.size[0]/2,b.size[1]/2,n/f,n/f,g,(d[0]-e[0])/n,(e[1]-d[3])/n),h!=this.c&&(e=h.c(this),e.style.maxWidth="none",e.style.position=
+"absolute",Kf(this.target),this.target.appendChild(e),this.c=h),Wj(p,this.d)||(Xf(this.target,p),Nd(this.d,p)),Kj(b.attributions,h.f),Lj(b,k));return!0};function In(b,c){var d=If("DIV");d.style.position="absolute";Gn.call(this,b,c,d);this.d=!0;this.n=1;this.g=0;this.c={}}u(In,Gn);In.prototype.e=function(){Kf(this.target);this.g=0};
+In.prototype.f=function(b,c){if(!c.visible)return this.d&&(Sg(this.target,!1),this.d=!1),!0;var d=b.pixelRatio,e=b.viewState,f=e.projection,g=this.a,h=g.a(),k=Gj(h,f),n=h.bd(),p=ec(k.a,e.resolution,0),q=k.ka(p),r=e.center,s;q==e.resolution?(r=Oj(r,q,b.size),s=ne(r,q,e.rotation,b.size)):s=b.extent;m(c.extent)&&(s=pe(s,c.extent));var q=Aj(k,s,q),v={};v[p]={};var y=sa(h.fe,h,v,Nj(function(b){return null!==b&&2==b.state},h,d,f)),C=g.ea();m(C)||(C=!0);var F=Vd(),G=new of(0,0,0,0),w,U,N,Y;for(N=q.a;N<=
+q.d;++N)for(Y=q.b;Y<=q.c;++Y)w=h.Fb(p,N,Y,d,f),U=w.state,2==U?v[p][nf(w.a)]=w:4==U||3==U&&!C||(U=k.$c(w.a,y,null,G,F),U||(w=k.kd(w.a,G,F),null===w||y(p+1,w)));var T;if(this.g!=h.c){for(T in this.c)C=this.c[+T],Nf(C.target);this.c={};this.g=h.c}F=Va(sb(v),Number);gb(F);var y={},qa;N=0;for(Y=F.length;N<Y;++N){T=F[N];T in this.c?C=this.c[T]:(C=k.Fc(r,T),C=new Jn(k,C),y[T]=!0,this.c[T]=C);T=v[T];for(qa in T)Kn(C,T[qa],n);Ln(C)}n=Va(sb(this.c),Number);gb(n);N=Kd();qa=0;for(F=n.length;qa<F;++qa)if(T=n[qa],
+C=this.c[T],T in v)if(w=C.g,Y=C.f,Vj(N,b.size[0]/2,b.size[1]/2,w/e.resolution,w/e.resolution,e.rotation,(Y[0]-r[0])/w,(r[1]-Y[1])/w),Mn(C,N),T in y){for(--T;0<=T;--T)if(T in this.c){Lf(C.target,this.c[T].target);break}0>T&&Mf(this.target,C.target,0)}else b.viewHints[0]||b.viewHints[1]||Nn(C,s,G);else Nf(C.target),delete this.c[T];c.opacity!=this.n&&(this.n=this.target.style.opacity=c.opacity);c.visible&&!this.d&&(Sg(this.target,!0),this.d=!0);Mj(b.usedTiles,h,p,q);Pj(b,h,k,d,f,s,p,g.r());Jj(b,h);
+Lj(b,h);return!0};function Jn(b,c){this.target=If("DIV");this.target.style.position="absolute";this.target.style.width="100%";this.target.style.height="100%";this.d=b;this.b=c;this.f=me(yj(b,c));this.g=b.ka(c[0]);this.c={};this.a=null;this.e=Md()}
+function Kn(b,c,d){var e=c.a,f=e[0],g=e[1],h=e[2],e=nf(e);if(!(e in b.c)){var f=b.d.sa(f),k=c.Na(b),n=k.style;n.maxWidth="none";var p,q;0<d?(p=If("DIV"),q=p.style,q.overflow="hidden",q.width=f+"px",q.height=f+"px",n.position="absolute",n.left=-d+"px",n.top=-d+"px",n.width=f+2*d+"px",n.height=f+2*d+"px",p.appendChild(k)):(n.width=f+"px",n.height=f+"px",p=k,q=n);q.position="absolute";q.left=(g-b.b[1])*f+"px";q.top=(b.b[2]-h)*f+"px";null===b.a&&(b.a=document.createDocumentFragment());b.a.appendChild(p);
+b.c[e]=c}}function Ln(b){null!==b.a&&(b.target.appendChild(b.a),b.a=null)}function Nn(b,c,d){var e=zj(b.d,c,b.b[0],d);c=[];for(var f in b.c)d=b.c[f],e.contains(d.a)||c.push(d);var g,e=0;for(g=c.length;e<g;++e)d=c[e],f=nf(d.a),Nf(d.Na(b)),delete b.c[f]}function Mn(b,c){Wj(c,b.e)||(Xf(b.target,c),Nd(b.e,c))};function On(b,c){this.g=Tf();var d=this.g.canvas;d.style.maxWidth="none";d.style.position="absolute";Gn.call(this,b,c,d);this.d=!1;this.q=-1;this.p=NaN;this.n=Vd();this.c=this.j=null;this.r=Kd()}u(On,Gn);
+On.prototype.i=function(b,c){var d=b.viewState,e=d.rotation,f=b.pixelRatio,d=Vj(this.r,f*b.size[0]/2,f*b.size[1]/2,f/d.resolution,-f/d.resolution,-d.rotation,-d.center[0],-d.center[1]),g=this.g;g.canvas.width=b.size[0];g.canvas.height=b.size[1];Pn(this,"precompose",b,d);var h=this.c;null===h||h.ia()||(g.globalAlpha=c.opacity,$m(h,g,f,d,e,b.skippedFeatureUids),Pn(this,"render",b,d));Pn(this,"postcompose",b,d)};
+function Pn(b,c,d,e){var f=b.g;b=b.a;ld(b,c)&&(e=new um(f,d.pixelRatio,d.extent,e,d.viewState.rotation),b.dispatchEvent(new Al(c,b,e,null,d,f,null)),Gm(e))}On.prototype.Zb=function(b,c,d,e){if(null!==this.c){var f=this.a,g={};return ck(this.c,c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){var c=ma(b).toString();if(!(c in g))return g[c]=!0,d.call(e,b,f)})}};On.prototype.t=function(){Ij(this)};
+On.prototype.f=function(b){function c(b){var c;m(b.a)?c=b.a.call(b,k):m(d.r)&&(c=(0,d.r)(b,k));if(null!=c){if(null!=c){var e,f,g=!1;e=0;for(f=c.length;e<f;++e)g=An(q,b,c[e],zn(k,n),this.t,this)||g;b=g}else b=!1;this.d=this.d||b}}var d=this.a,e=d.a();Kj(b.attributions,e.e);Lj(b,e);if(!this.d&&(b.viewHints[0]||b.viewHints[1]))return!0;var f=b.extent,g=b.viewState,h=g.projection,k=g.resolution,n=b.pixelRatio;b=d.c;var p=d.Ab,g=d.get("renderOrder");m(g)||(g=yn);f=Yd(f,p*k);if(!this.d&&this.p==k&&this.q==
+b&&this.j==g&&$d(this.n,f))return!0;tc(this.c);this.c=null;this.d=!1;var q=new Wm(.5*k/n,f,k);e.Hb(f,k,h);if(null===g)e.Db(f,k,c,this);else{var r=[];e.Db(f,k,function(b){r.push(b)},this);gb(r,g);Ta(r,c,this)}Xm(q);this.p=k;this.q=b;this.j=g;this.n=f;this.c=q;return!0};function Qn(b,c){Yj.call(this,0,c);this.c=null;this.c=Tf();var d=this.c.canvas;d.style.position="absolute";d.style.width="100%";d.style.height="100%";d.className="ol-unselectable";Mf(b,d,0);this.i=Kd();this.a=If("DIV");this.a.className="ol-unselectable";d=this.a.style;d.position="absolute";d.width="100%";d.height="100%";z(this.a,"touchstart",wc);Mf(b,this.a,0);this.o=!0}u(Qn,Yj);Qn.prototype.M=function(){Nf(this.a);Qn.S.M.call(this)};
+Qn.prototype.Yc=function(b){if(b instanceof J)b=new Hn(this,b);else if(b instanceof K)b=new In(this,b);else if(b instanceof L)b=new On(this,b);else return null;return b};
+function Rn(b,c,d){var e=b.e;if(ld(e,c)){var f=d.extent,g=d.pixelRatio,h=d.viewState,k=h.resolution,n=h.rotation,p=b.c,q=p.canvas;Vj(b.i,q.width/2,q.height/2,g/h.resolution,-g/h.resolution,-h.rotation,-h.center[0],-h.center[1]);h=new um(p,g,f,b.i,n);f=new Wm(.5*k/g,f,k);e.dispatchEvent(new Al(c,e,h,f,d,p,null));Xm(f);f.ia()||$m(f,p,g,b.i,n,{});Gm(h);b.g=f}}Qn.prototype.H=function(){return"dom"};
+Qn.prototype.Ld=function(b){if(null===b)this.o&&(Sg(this.a,!1),this.o=!1);else{var c;c=function(b,c){Mf(this.a,b,c)};var d=this.e;if(ld(d,"precompose")||ld(d,"postcompose"))d=this.c.canvas,d.width=b.size[0],d.height=b.size[1];Rn(this,"precompose",b);var d=b.layerStatesArray,e,f,g,h;e=0;for(f=d.length;e<f;++e)h=d[e],g=h.layer,g=dk(this,g),c.call(this,g.target,e),"ready"==h.gc?g.f(b,h)&&g.i(b,h):g.e();c=b.layerStates;for(var k in this.b)k in c||(g=this.b[k],Nf(g.target));this.o||(Sg(this.a,!0),this.o=
+!0);Zj(b);ek(this,b);b.postRenderFunctions.push(ak);Rn(this,"postcompose",b)}};function Sn(b){this.a=b}function Tn(b){this.a=b}u(Tn,Sn);Tn.prototype.H=function(){return 35632};function Un(b){this.a=b}u(Un,Sn);Un.prototype.H=function(){return 35633};function Vn(){this.a="precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}"}u(Vn,Tn);da(Vn);
+function Wn(){this.a="varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}"}u(Wn,Un);da(Wn);
+function Xn(b,c){this.n=b.getUniformLocation(c,"k");this.o=b.getUniformLocation(c,"j");this.i=b.getUniformLocation(c,"i");this.f=b.getUniformLocation(c,"l");this.g=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"e");this.c=b.getAttribLocation(c,"f");this.d=b.getAttribLocation(c,"c");this.b=b.getAttribLocation(c,"g");this.e=b.getAttribLocation(c,"d")};function Yn(){this.a="precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}"}u(Yn,Tn);da(Yn);
+function Zn(){this.a="varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}"}u(Zn,Un);da(Zn);
+function $n(b,c){this.o=b.getUniformLocation(c,"j");this.i=b.getUniformLocation(c,"i");this.f=b.getUniformLocation(c,"k");this.g=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"e");this.c=b.getAttribLocation(c,"f");this.d=b.getAttribLocation(c,"c");this.b=b.getAttribLocation(c,"g");this.e=b.getAttribLocation(c,"d")};function ao(b){this.a=m(b)?b:[];this.c=m(void 0)?void 0:35044};function bo(b,c){this.n=this.i=void 0;this.Sa=new yg;this.e=ke(c);this.o=[];this.q=void 0;this.b=[];this.t=this.r=void 0;this.c=[];this.p=this.j=this.d=null;this.F=void 0;this.ga=Md();this.Ra=Md();this.Q=this.J=void 0;this.ea=Md();this.ca=this.ba=this.V=void 0;this.f=[];this.a=[];this.g=null;this.la=void 0}function co(b,c){var d=b.g,e=b.d,f=b.f,g=c.a;return function(){if(!g.isContextLost()){var b,k;b=0;for(k=f.length;b<k;++b)g.deleteTexture(f[b])}eo(c,d);eo(c,e)}}
+function fo(b,c,d,e){var f=b.i,g=b.n,h=b.q,k=b.r,n=b.t,p=b.F,q=b.J,r=b.Q,s=b.V?1:0,v=b.ba,y=b.ca,C=b.la,F=Math.cos(v),v=Math.sin(v),G=b.c.length,w=b.a.length,U,N,Y,T,qa,vb;for(U=0;U<d;U+=e)qa=c[U]-b.e[0],vb=c[U+1]-b.e[1],N=w/8,Y=-y*f,T=-y*(h-g),b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=q/n,b.a[w++]=(r+h)/k,b.a[w++]=p,b.a[w++]=s,Y=y*(C-f),T=-y*(h-g),b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=(q+C)/n,b.a[w++]=(r+h)/k,b.a[w++]=p,b.a[w++]=s,Y=y*(C-f),T=
+y*g,b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=(q+C)/n,b.a[w++]=r/k,b.a[w++]=p,b.a[w++]=s,Y=-y*f,T=y*g,b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=q/n,b.a[w++]=r/k,b.a[w++]=p,b.a[w++]=s,b.c[G++]=N,b.c[G++]=N+1,b.c[G++]=N+2,b.c[G++]=N,b.c[G++]=N+2,b.c[G++]=N+3}l=bo.prototype;l.qb=function(b){var c=b.k;fo(this,c,c.length,b.s)};l.rb=function(b){var c=b.k;fo(this,c,c.length,b.s)};
+l.Kb=function(b){var c=b.a;this.o.push(this.c.length);this.g=new ao(this.a);go(b,34962,this.g);this.d=new ao(this.c);go(b,34963,this.d);var d,e,f={},g,h=this.b.length;for(g=0;g<h;++g)d=this.b[g],e=ma(d).toString(),e in f?b=x(f,e):(b=c.createTexture(),c.bindTexture(3553,b),c.texParameteri(3553,10242,33071),c.texParameteri(3553,10243,33071),c.texParameteri(3553,10241,9729),c.texParameteri(3553,10240,9729),c.texImage2D(3553,0,6408,6408,5121,d),f[e]=b),this.f[g]=b;this.q=this.n=this.i=void 0;this.b=null;
+this.t=this.r=void 0;this.c=null;this.ca=this.ba=this.V=this.Q=this.J=this.F=void 0;this.a=null;this.la=void 0};
+l.Hc=function(b,c,d,e,f,g,h,k,n,p,q){g=b.a;go(b,34962,this.g);go(b,34963,this.d);var r=k||1!=n||p||1!=q,s,v;r?(s=Vn.Ja(),v=Wn.Ja()):(s=Yn.Ja(),v=Zn.Ja());v=ho(b,s,v);r?null===this.j?this.j=s=new Xn(g,v):s=this.j:null===this.p?this.p=s=new $n(g,v):s=this.p;b.Gd(v);g.enableVertexAttribArray(s.d);g.vertexAttribPointer(s.d,2,5126,!1,32,0);g.enableVertexAttribArray(s.a);g.vertexAttribPointer(s.a,2,5126,!1,32,8);g.enableVertexAttribArray(s.e);g.vertexAttribPointer(s.e,2,5126,!1,32,16);g.enableVertexAttribArray(s.c);
+g.vertexAttribPointer(s.c,1,5126,!1,32,24);g.enableVertexAttribArray(s.b);g.vertexAttribPointer(s.b,1,5126,!1,32,28);v=this.ea;Vj(v,0,0,2/(d*f[0]),2/(d*f[1]),-e,-(c[0]-this.e[0]),-(c[1]-this.e[1]));c=this.Ra;d=2/f[0];f=2/f[1];Od(c);c[0]=d;c[5]=f;c[10]=1;c[15]=1;f=this.ga;Od(f);0!==e&&Sd(f,-e);g.uniformMatrix4fv(s.g,!1,v);g.uniformMatrix4fv(s.i,!1,c);g.uniformMatrix4fv(s.o,!1,f);g.uniform1f(s.f,h);r&&g.uniformMatrix4fv(s.n,!1,zg(this.Sa,k,n,p,q));e=0;h=this.f.length;for(k=0;e<h;++e)g.bindTexture(3553,
+this.f[e]),n=this.o[e],g.drawElements(4,n-k,b.e?5125:5123,k*(b.e?4:2)),k=n;g.disableVertexAttribArray(s.d);g.disableVertexAttribArray(s.a);g.disableVertexAttribArray(s.e);g.disableVertexAttribArray(s.c);g.disableVertexAttribArray(s.b)};
+l.cb=function(b){var c=b.tb(),d=b.yb(1),e=b.cd(),f=b.p,g=b.zb(),h=b.q,k=b.i,n=b.ab();b=b.n;0===this.b.length?this.b.push(d):ma(this.b[this.b.length-1])!=ma(d)&&(this.o.push(this.c.length),this.b.push(d));this.i=c[0];this.n=c[1];this.q=n[1];this.r=e[1];this.t=e[0];this.F=f;this.J=g[0];this.Q=g[1];this.ba=k;this.V=h;this.ca=b;this.la=n[0]};function io(b,c){this.b=c;this.d=b;this.c={}}function jo(b,c){var d=[],e;for(e in b.c)d.push(co(b.c[e],c));return hd.apply(null,d)}
+function ko(b,c){for(var d in b.c)b.c[d].Kb(c)}io.prototype.a=function(b,c){var d=this.c[c];m(d)||(d=new lo[c](this.d,this.b),this.c[c]=d);return d};io.prototype.ia=function(){return xb(this.c)};function mo(b,c,d,e,f,g,h,k,n,p,q,r,s){var v,y,C;v=0;for(y=Hm.length;v<y&&(C=b.c[Hm[v]],!m(C)||!(C=C.Hc(c,d,e,f,g,h,k,n,p,q,r,s)));++v);}var lo={Image:bo};function no(b,c,d,e,f,g,h){this.b=b;this.e=c;this.a=g;this.f=h;this.i=f;this.o=e;this.g=d;this.d=null;this.c={}}l=no.prototype;l.ic=function(b,c){var d=b.toString(),e=this.c[d];m(e)?e.push(c):this.c[d]=[c]};l.jc=function(){};l.ee=function(b,c){var d=(0,c.d)(b);if(null!=d&&qe(this.a,d.D())){var e=c.a;m(e)||(e=0);this.ic(e,function(b){b.wa(c.e,c.b);b.cb(c.f);b.xa(c.c);var e=oo[d.H()];e&&e.call(b,d,null)})}};
+l.Zc=function(b,c){var d=b.d,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e],h=oo[g.H()];h&&h.call(this,g,c)}};l.rb=function(b,c){var d=this.b,e=(new io(1,this.a)).a(0,"Image");e.cb(this.d);e.rb(b,c);e.Kb(d);e.Hc(this.b,this.e,this.g,this.o,this.i,this.a,this.f,1,0,1,0,1,{});co(e,d)()};l.Cb=function(){};l.kc=function(){};l.qb=function(b,c){var d=this.b,e=(new io(1,this.a)).a(0,"Image");e.cb(this.d);e.qb(b,c);e.Kb(d);e.Hc(this.b,this.e,this.g,this.o,this.i,this.a,this.f,1,0,1,0,1,{});co(e,d)()};l.lc=function(){};
+l.Sb=function(){};l.sb=function(){};l.wa=function(){};l.cb=function(b){this.d=b};l.xa=function(){};var oo={Point:no.prototype.rb,MultiPoint:no.prototype.qb,GeometryCollection:no.prototype.Zc};function po(){this.a="precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}"}u(po,Tn);da(po);function qo(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}u(qo,Un);da(qo);
+function ro(b,c){this.g=b.getUniformLocation(c,"f");this.b=b.getUniformLocation(c,"g");this.d=b.getUniformLocation(c,"e");this.f=b.getUniformLocation(c,"d");this.e=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"b");this.c=b.getAttribLocation(c,"c")};function so(){this.a="precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}"}u(so,Tn);da(so);function to(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}u(to,Un);da(to);
+function uo(b,c){this.b=b.getUniformLocation(c,"f");this.d=b.getUniformLocation(c,"e");this.f=b.getUniformLocation(c,"d");this.e=b.getUniformLocation(c,"g");this.a=b.getAttribLocation(c,"b");this.c=b.getAttribLocation(c,"c")};function vo(b,c){Hj.call(this,b,c);this.J=new ao([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.e=this.Qa=null;this.f=void 0;this.q=Kd();this.F=Md();this.Q=new yg;this.i=this.g=null}u(vo,Hj);
+function wo(b,c,d){var e=b.b.d;if(m(b.f)&&b.f==d)e.bindFramebuffer(36160,b.e);else{c.postRenderFunctions.push(ta(function(b,c,d){b.isContextLost()||(b.deleteFramebuffer(c),b.deleteTexture(d))},e,b.e,b.Qa));c=e.createTexture();e.bindTexture(3553,c);e.texImage2D(3553,0,6408,d,d,0,6408,5121,null);e.texParameteri(3553,10240,9729);e.texParameteri(3553,10241,9729);var f=e.createFramebuffer();e.bindFramebuffer(36160,f);e.framebufferTexture2D(36160,36064,3553,c,0);b.Qa=c;b.e=f;b.f=d}}
+vo.prototype.xf=function(b,c,d){xo(this,"precompose",d,b);go(d,34962,this.J);var e=d.a,f=c.brightness||1!=c.contrast||c.hue||1!=c.saturation,g,h;f?(g=po.Ja(),h=qo.Ja()):(g=so.Ja(),h=to.Ja());g=ho(d,g,h);f?null===this.g?this.g=h=new ro(e,g):h=this.g:null===this.i?this.i=h=new uo(e,g):h=this.i;d.Gd(g)&&(e.enableVertexAttribArray(h.a),e.vertexAttribPointer(h.a,2,5126,!1,16,0),e.enableVertexAttribArray(h.c),e.vertexAttribPointer(h.c,2,5126,!1,16,8),e.uniform1i(h.e,0));e.uniformMatrix4fv(h.f,!1,this.q);
+e.uniformMatrix4fv(h.d,!1,this.F);f&&e.uniformMatrix4fv(h.g,!1,zg(this.Q,c.brightness,c.contrast,c.hue,c.saturation));e.uniform1f(h.b,c.opacity);e.bindTexture(3553,this.Qa);e.drawArrays(5,0,4);xo(this,"postcompose",d,b)};function xo(b,c,d,e){b=b.a;if(ld(b,c)){var f=e.viewState;b.dispatchEvent(new Al(c,b,new no(d,f.center,f.resolution,f.rotation,e.size,e.extent,e.pixelRatio),null,e,null,d))}}vo.prototype.n=function(){this.e=this.Qa=null;this.f=void 0};function yo(b,c){vo.call(this,b,c);this.c=null}u(yo,vo);function zo(b,c){var d=c.c(),e=b.b.d,f=e.createTexture();e.bindTexture(3553,f);e.texImage2D(3553,0,6408,6408,5121,d);e.texParameteri(3553,10242,33071);e.texParameteri(3553,10243,33071);e.texParameteri(3553,10241,9729);e.texParameteri(3553,10240,9729);return f}yo.prototype.Zb=function(b,c,d,e){var f=this.a;return f.a().yd(c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};
+yo.prototype.re=function(b,c){var d=this.b.d,e=b.viewState,f=e.center,g=e.resolution,h=e.rotation,k=this.c,n=this.Qa,p=this.a.a(),q=b.viewHints,r=b.extent;m(c.extent)&&(r=pe(r,c.extent));q[0]||q[1]||se(r)||(e=e.projection,q=p.g,null===q||(e=q),r=p.rc(r,g,b.pixelRatio,e),null!==r&&(e=r.state,0==e?(Wc(r,"change",this.o,!1,this),r.load()):2==e&&(k=r,n=zo(this,r),null===this.Qa||b.postRenderFunctions.push(ta(function(b,c){b.isContextLost()||b.deleteTexture(c)},d,this.Qa)))));null!==k&&(d=this.b.f.g,Ao(this,
+d.width,d.height,f,g,h,k.D()),f=this.q,Od(f),Rd(f,1,-1),Qd(f,0,-1),this.c=k,this.Qa=n,Kj(b.attributions,k.f),Lj(b,p));return!0};function Ao(b,c,d,e,f,g,h){c*=f;d*=f;b=b.F;Od(b);Rd(b,2/c,2/d);Sd(b,-g);Qd(b,h[0]-e[0],h[1]-e[1]);Rd(b,(h[2]-h[0])/2,(h[3]-h[1])/2);Qd(b,1,1)};function Bo(){this.a="precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"}u(Bo,Tn);da(Bo);function Co(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}"}u(Co,Un);da(Co);function Do(b,c){this.b=b.getUniformLocation(c,"e");this.d=b.getUniformLocation(c,"d");this.a=b.getAttribLocation(c,"b");this.c=b.getAttribLocation(c,"c")};function Eo(b,c){vo.call(this,b,c);this.t=Bo.Ja();this.V=Co.Ja();this.c=null;this.r=new ao([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.p=this.d=null;this.j=-1}u(Eo,vo);Eo.prototype.M=function(){eo(this.b.f,this.r);Eo.S.M.call(this)};Eo.prototype.n=function(){Eo.S.n.call(this);this.c=null};
+Eo.prototype.re=function(b,c,d){var e=this.b,f=d.a,g=b.viewState,h=g.projection,k=this.a,n=k.a(),p=Gj(n,h),q=ec(p.a,g.resolution,0),r=p.ka(q),s=n.Gc(q,b.pixelRatio,h),v=s/p.sa(q),y=r/v,C=n.bd(),F=g.center,G;r==g.resolution?(F=Oj(F,r,b.size),G=ne(F,r,g.rotation,b.size)):G=b.extent;r=Aj(p,G,r);if(null!==this.d&&sf(this.d,r)&&this.j==n.c)y=this.p;else{var w=[r.d-r.a+1,r.c-r.b+1],w=Math.max(w[0]*s,w[1]*s),U=Math.pow(2,Math.ceil(Math.log(w)/Math.LN2)),w=y*U,N=p.Lb(q),Y=N[0]+r.a*s*y,y=N[1]+r.b*s*y,y=[Y,
+y,Y+w,y+w];wo(this,b,U);f.viewport(0,0,U,U);f.clearColor(0,0,0,0);f.clear(16384);f.disable(3042);U=ho(d,this.t,this.V);d.Gd(U);null===this.c&&(this.c=new Do(f,U));go(d,34962,this.r);f.enableVertexAttribArray(this.c.a);f.vertexAttribPointer(this.c.a,2,5126,!1,16,0);f.enableVertexAttribArray(this.c.c);f.vertexAttribPointer(this.c.c,2,5126,!1,16,8);f.uniform1i(this.c.b,0);d={};d[q]={};var T=sa(n.fe,n,d,Nj(function(b){return null!==b&&2==b.state&&Fo(e.c,b.mb())},n,v,h)),qa=k.ea();m(qa)||(qa=!0);var U=
+!0,Y=Vd(),vb=new of(0,0,0,0),Ka,ac,Sb;for(ac=r.a;ac<=r.d;++ac)for(Sb=r.b;Sb<=r.c;++Sb){N=n.Fb(q,ac,Sb,v,h);if(m(c.extent)&&(Ka=yj(p,N.a,Y),!qe(Ka,c.extent)))continue;Ka=N.state;if(2==Ka){if(Fo(e.c,N.mb())){d[q][nf(N.a)]=N;continue}}else if(4==Ka||3==Ka&&!qa)continue;U=!1;Ka=p.$c(N.a,T,null,vb,Y);Ka||(N=p.kd(N.a,vb,Y),null===N||T(q+1,N))}c=Va(sb(d),Number);gb(c);for(var T=new Float32Array(4),La,Pa,Ud,qa=0,vb=c.length;qa<vb;++qa)for(La in Pa=d[c[qa]],Pa)N=Pa[La],Ka=yj(p,N.a,Y),ac=2*(Ka[2]-Ka[0])/w,
+Sb=2*(Ka[3]-Ka[1])/w,Ud=2*(Ka[0]-y[0])/w-1,Ka=2*(Ka[1]-y[1])/w-1,Jd(T,ac,Sb,Ud,Ka),f.uniform4fv(this.c.d,T),Go(e,N,s,C*v),f.drawArrays(5,0,4);U?(this.d=r,this.p=y,this.j=n.c):(this.p=this.d=null,this.j=-1,b.animate=!0)}Mj(b.usedTiles,n,q,r);var qd=e.i;Pj(b,n,p,v,h,G,q,k.r(),function(b){var c;(c=2!=b.state||Fo(e.c,b.mb()))||(c=b.mb()in qd.b);c||Qj(qd,[b,Cj(p,b.a),p.ka(b.a[0]),s,C*v])},this);Jj(b,n);Lj(b,n);f=this.q;Od(f);Qd(f,(F[0]-y[0])/(y[2]-y[0]),(F[1]-y[1])/(y[3]-y[1]));0!==g.rotation&&Sd(f,g.rotation);
+Rd(f,b.size[0]*g.resolution/(y[2]-y[0]),b.size[1]*g.resolution/(y[3]-y[1]));Qd(f,-.5,-.5);return!0};function Ho(b,c){vo.call(this,b,c);this.d=!1;this.t=-1;this.r=NaN;this.j=Vd();this.c=this.p=null}u(Ho,vo);l=Ho.prototype;l.xf=function(b,c,d){var e=b.viewState,f=this.c;null===f||f.ia()||mo(f,d,e.center,e.resolution,e.rotation,b.size,b.pixelRatio,c.opacity,c.brightness,c.contrast,c.hue,c.saturation,b.skippedFeatureUids)};l.M=function(){var b=this.c;null!==b&&(jo(b,this.b.f)(),this.c=null);Ho.S.M.call(this)};l.Zb=function(){};l.vj=function(){Ij(this)};
+l.re=function(b,c,d){function e(b){var c;m(b.a)?c=b.a.call(b,n):m(f.r)&&(c=(0,f.r)(b,n));if(null!=c){if(null!=c){var d,e,g=!1;d=0;for(e=c.length;d<e;++d)g=An(s,b,c[d],zn(n,p),this.vj,this)||g;b=g}else b=!1;this.d=this.d||b}}var f=this.a;c=f.a();Kj(b.attributions,c.e);Lj(b,c);if(!this.d&&(b.viewHints[0]||b.viewHints[1]))return!0;var g=b.extent,h=b.viewState,k=h.projection,n=h.resolution,p=b.pixelRatio,h=f.c,q=f.Ab,r=f.get("renderOrder");m(r)||(r=yn);g=Yd(g,q*n);if(!this.d&&this.r==n&&this.t==h&&this.p==
+r&&$d(this.j,g))return!0;null===this.c||b.postRenderFunctions.push(jo(this.c,d));this.d=!1;var s=new io(.5*n/p,g);c.Hb(g,n,k);if(null===r)c.Db(g,n,e,this);else{var v=[];c.Db(g,n,function(b){v.push(b)},this);gb(v,r);Ta(v,e,this)}ko(s,d);this.r=n;this.t=h;this.p=r;this.j=g;this.c=s;return!0};function Io(){this.b=0;this.d={};this.c=this.a=null}l=Io.prototype;l.clear=function(){this.b=0;this.d={};this.c=this.a=null};function Fo(b,c){return b.d.hasOwnProperty(c)}l.forEach=function(b,c){for(var d=this.a;null!==d;)b.call(c,d.dc,d.ud,this),d=d.Za};l.get=function(b){b=this.d[b];if(b===this.c)return b.dc;b===this.a?(this.a=this.a.Za,this.a.Mb=null):(b.Za.Mb=b.Mb,b.Mb.Za=b.Za);b.Za=null;b.Mb=this.c;this.c=this.c.Za=b;return b.dc};l.Ub=function(){return this.b};
+l.I=function(){var b=Array(this.b),c=0,d;for(d=this.c;null!==d;d=d.Mb)b[c++]=d.ud;return b};l.kb=function(){var b=Array(this.b),c=0,d;for(d=this.c;null!==d;d=d.Mb)b[c++]=d.dc;return b};l.pop=function(){var b=this.a;delete this.d[b.ud];null!==b.Za&&(b.Za.Mb=null);this.a=b.Za;null===this.a&&(this.c=null);--this.b;return b.dc};l.set=function(b,c){var d={ud:b,Za:null,Mb:this.c,dc:c};null===this.c?this.a=d:this.c.Za=d;this.c=d;this.d[b]=d;++this.b};function Jo(b,c){this.g=b;this.a=c;this.c={};this.d={};this.b={};this.f=null;(this.e=Za(wa,"OES_element_index_uint"))&&c.getExtension("OES_element_index_uint");z(this.g,"webglcontextlost",this.lk,!1,this);z(this.g,"webglcontextrestored",this.mk,!1,this)}
+function go(b,c,d){var e=b.a,f=d.a,g=ma(d);if(g in b.c)e.bindBuffer(c,b.c[g].buffer);else{var h=e.createBuffer();e.bindBuffer(c,h);var k;34962==c?k=new Float32Array(f):34963==c&&(k=b.e?new Uint32Array(f):new Uint16Array(f));e.bufferData(c,k,d.c);b.c[g]={b:d,buffer:h}}}function eo(b,c){var d=b.a,e=ma(c),f=b.c[e];d.isContextLost()||d.deleteBuffer(f.buffer);delete b.c[e]}l=Jo.prototype;
+l.M=function(){var b=this.a;b.isContextLost()||(ob(this.c,function(c){b.deleteBuffer(c.buffer)}),ob(this.b,function(c){b.deleteProgram(c)}),ob(this.d,function(c){b.deleteShader(c)}))};l.kk=function(){return this.a};function Ko(b,c){var d=ma(c);if(d in b.d)return b.d[d];var e=b.a,f=e.createShader(c.H());e.shaderSource(f,c.a);e.compileShader(f);return b.d[d]=f}
+function ho(b,c,d){var e=ma(c)+"/"+ma(d);if(e in b.b)return b.b[e];var f=b.a,g=f.createProgram();f.attachShader(g,Ko(b,c));f.attachShader(g,Ko(b,d));f.linkProgram(g);return b.b[e]=g}l.lk=function(){yb(this.c);yb(this.d);yb(this.b);this.f=null};l.mk=function(){};l.Gd=function(b){if(b==this.f)return!1;this.a.useProgram(b);this.f=b;return!0};function Lo(b,c){Yj.call(this,0,c);this.a=If("CANVAS");this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Mf(b,this.a,0);this.p=0;this.q=Tf();this.n=!0;this.d=Zf(this.a,{antialias:!0,depth:!1,bh:!0,preserveDrawingBuffer:!1,stencil:!0});this.f=new Jo(this.a,this.d);z(this.a,"webglcontextlost",this.tj,!1,this);z(this.a,"webglcontextrestored",this.uj,!1,this);this.c=new Io;this.j=null;this.i=new fk(sa(function(b){var c=b[1];b=b[2];var f=c[0]-this.j[0],c=c[1]-this.j[1];
+return 65536*Math.log(b)+Math.sqrt(f*f+c*c)/b},this),function(b){return b[0].mb()});this.r=sa(function(){if(!this.i.ia()){jk(this.i);var b=gk(this.i);Go(this,b[0],b[3],b[4])}},this);this.o=0;Mo(this)}u(Lo,Yj);
+function Go(b,c,d,e){var f=b.d,g=c.mb();if(Fo(b.c,g))b=b.c.get(g),f.bindTexture(3553,b.Qa),9729!=b.nf&&(f.texParameteri(3553,10240,9729),b.nf=9729),9729!=b.of&&(f.texParameteri(3553,10240,9729),b.of=9729);else{var h=f.createTexture();f.bindTexture(3553,h);if(0<e){var k=b.q.canvas,n=b.q;b.p!=d?(k.width=d,k.height=d,b.p=d):n.clearRect(0,0,d,d);n.drawImage(c.Na(),e,e,d,d,0,0,d,d);f.texImage2D(3553,0,6408,6408,5121,k)}else f.texImage2D(3553,0,6408,6408,5121,c.Na());f.texParameteri(3553,10240,9729);f.texParameteri(3553,
+10241,9729);f.texParameteri(3553,10242,33071);f.texParameteri(3553,10243,33071);b.c.set(g,{Qa:h,nf:9729,of:9729})}}l=Lo.prototype;l.Yc=function(b){return b instanceof J?new yo(this,b):b instanceof K?new Eo(this,b):b instanceof L?new Ho(this,b):null};
+function No(b,c,d){var e=b.e;if(ld(e,c)){var f=b.f,g=d.extent,h=d.size,k=d.viewState,n=d.pixelRatio,p=k.resolution,q=k.center,r=k.rotation,k=new no(f,q,p,r,h,g,n),g=new io(.5*p/n,g);e.dispatchEvent(new Al(c,e,k,g,d,null,f));ko(g,f);g.ia()||mo(g,f,q,p,r,h,n,1,0,1,0,1,{});jo(g,f)();c=Va(sb(k.c),Number);gb(c);d=0;for(e=c.length;d<e;++d)for(f=k.c[c[d].toString()],h=0,n=f.length;h<n;++h)f[h](k);b.g=g}}
+l.M=function(){var b=this.d;b.isContextLost()||this.c.forEach(function(c){null===c||b.deleteTexture(c.Qa)});tc(this.f);Lo.S.M.call(this)};l.$g=function(b,c){for(var d=this.d,e;1024<this.c.Ub()-this.o;){e=this.c.a.dc;if(null===e)if(+this.c.a.ud==c.index)break;else--this.o;else d.deleteTexture(e.Qa);this.c.pop()}};l.H=function(){return"webgl"};l.tj=function(b){b.preventDefault();this.c.clear();this.o=0;ob(this.b,function(b){b.n()})};l.uj=function(){Mo(this);this.e.render()};
+function Mo(b){b=b.d;b.activeTexture(33984);b.blendFuncSeparate(770,771,1,771);b.disable(2884);b.disable(2929);b.disable(3089);b.disable(2960)}
+l.Ld=function(b){var c=this.f,d=this.d;if(d.isContextLost())return!1;if(null===b)return this.n&&(Sg(this.a,!1),this.n=!1),!1;this.j=b.focus;this.c.set((-b.index).toString(),null);++this.o;var e=[],f=b.layerStatesArray,g=b.viewState.resolution,h,k,n,p;h=0;for(k=f.length;h<k;++h)p=f[h],p.visible&&g>=p.minResolution&&g<p.maxResolution&&"ready"==p.gc&&(n=dk(this,p.layer),n.re(b,p,c)&&e.push(p));f=b.size[0]*b.pixelRatio;g=b.size[1]*b.pixelRatio;if(this.a.width!=f||this.a.height!=g)this.a.width=f,this.a.height=
+g;d.bindFramebuffer(36160,null);d.clearColor(0,0,0,0);d.clear(16384);d.enable(3042);d.viewport(0,0,this.a.width,this.a.height);No(this,"precompose",b);h=0;for(k=e.length;h<k;++h)p=e[h],n=dk(this,p.layer),n.xf(b,p,c);this.n||(Sg(this.a,!0),this.n=!0);Zj(b);1024<this.c.Ub()-this.o&&b.postRenderFunctions.push(sa(this.$g,this));this.i.ia()||(b.postRenderFunctions.push(this.r),b.animate=!0);No(this,"postcompose",b);ek(this,b);b.postRenderFunctions.push(ak)};var Oo=["canvas","webgl","dom"];
+function O(b){td.call(this);var c=Po(b);this.Yd=m(b.pixelRatio)?b.pixelRatio:ag;this.Xd=c.logos;this.q=new Vh(this.Yk,void 0,this);sc(this,this.q);this.Uc=Kd();this.Zd=Kd();this.Wd=0;this.d=null;this.Ea=Vd();this.p=this.Q=null;this.b=Ff("DIV","ol-viewport");this.b.style.position="relative";this.b.style.overflow="hidden";this.b.style.width="100%";this.b.style.height="100%";this.b.style.msTouchAction="none";gg&&(this.b.className="ol-touch");this.Da=Ff("DIV","ol-overlaycontainer");this.b.appendChild(this.Da);
+this.t=Ff("DIV","ol-overlaycontainer-stopevent");z(this.t,["click","dblclick","mousedown","touchstart","MSPointerDown",mj,Ib?"DOMMouseScroll":"mousewheel"],vc);this.b.appendChild(this.t);b=new ej(this);z(b,rb(pj),this.gf,!1,this);sc(this,b);this.ga=c.keyboardEventTarget;this.r=new ji;z(this.r,"key",this.ef,!1,this);sc(this,this.r);b=new ri(this.b);z(b,"mousewheel",this.ef,!1,this);sc(this,b);this.i=c.controls;this.Vc=c.deviceOptions;this.g=c.interactions;this.n=c.overlays;this.ba=new c.$k(this.b,
+this);sc(this,this.ba);this.gc=new ei;sc(this,this.gc);z(this.gc,"resize",this.j,!1,this);this.V=null;this.F=[];this.oa=[];this.Ab=new kk(sa(this.Jh,this),sa(this.ri,this));this.ca={};z(this,xd("layergroup"),this.ai,!1,this);z(this,xd("view"),this.$i,!1,this);z(this,xd("size"),this.pi,!1,this);z(this,xd("target"),this.qi,!1,this);this.G(c.xl);this.i.forEach(function(b){b.setMap(this)},this);z(this.i,"add",function(b){b.element.setMap(this)},!1,this);z(this.i,"remove",function(b){b.element.setMap(null)},
+!1,this);this.g.forEach(function(b){b.setMap(this)},this);z(this.g,"add",function(b){b.element.setMap(this)},!1,this);z(this.g,"remove",function(b){b.element.setMap(null)},!1,this);this.n.forEach(function(b){b.setMap(this)},this);z(this.n,"add",function(b){b.element.setMap(this)},!1,this);z(this.n,"remove",function(b){b.element.setMap(null)},!1,this)}u(O,td);l=O.prototype;l.Qg=function(b){this.i.push(b)};l.Rg=function(b){this.g.push(b)};l.Se=function(b){this.Eb().Yb().push(b)};l.Te=function(b){this.n.push(b)};
+l.Ua=function(b){this.render();Array.prototype.push.apply(this.F,arguments)};l.M=function(){Nf(this.b);O.S.M.call(this)};l.oe=function(b,c,d,e,f){if(null!==this.d)return b=this.Ga(b),bk(this.ba,b,this.d,c,m(d)?d:null,m(e)?e:dd,m(f)?f:null)};l.ih=function(b){return this.Ga(this.ad(b))};
+l.ad=function(b){if(m(b.offsetX)&&m(b.offsetY))return[b.offsetX,b.offsetY];if(m(b.changedTouches)){var c=b.changedTouches[0];b=Og(this.b);return[c.clientX-b.x,c.clientY-b.y]}c=this.b;b=Og(b);c=Og(c);c=new vf(b.x-c.x,b.y-c.y);return[c.x,c.y]};l.qc=function(){return this.get("target")};O.prototype.getTarget=O.prototype.qc;l=O.prototype;l.Fh=function(){var b=this.qc();return m(b)?Af(b):null};l.Ga=function(b){var c=this.d;if(null===c)return null;b=b.slice();return Xj(c.pixelToCoordinateMatrix,b,b)};
+l.hh=function(){return this.i};l.Ah=function(){return this.n};l.oh=function(){return this.g};l.Eb=function(){return this.get("layergroup")};O.prototype.getLayerGroup=O.prototype.Eb;O.prototype.ea=function(){return this.Eb().Yb()};O.prototype.f=function(b){var c=this.d;if(null===c)return null;b=b.slice(0,2);return Xj(c.coordinateToPixelMatrix,b,b)};O.prototype.e=function(){return this.get("size")};O.prototype.getSize=O.prototype.e;O.prototype.a=function(){return this.get("view")};
+O.prototype.getView=O.prototype.a;l=O.prototype;l.Lh=function(){return this.b};l.Jh=function(b,c,d,e){var f=this.d;if(!(null!==f&&c in f.wantedTiles&&f.wantedTiles[c][nf(b.a)]))return Infinity;b=d[0]-f.focus[0];d=d[1]-f.focus[1];return 65536*Math.log(e)+Math.sqrt(b*b+d*d)/e};l.ef=function(b,c){var d=new cj(c||b.type,this,b);this.gf(d)};
+l.gf=function(b){if(null!==this.d){this.V=b.coordinate;b.frameState=this.d;var c=this.g.a,d;if(!1!==this.dispatchEvent(b))for(d=c.length-1;0<=d;d--){var e=c[d];if(e.a()&&!e.handleEvent(b))break}}};
+l.ni=function(){var b=this.d,c=this.Ab;if(!c.ia()){var d=16,e=d,f=0;if(null!==b){var f=b.viewHints,g=this.Vc;f[0]&&(d=!1===g.loadTilesWhileAnimating?0:8,e=2);f[1]&&(d=!1===g.loadTilesWhileInteracting?0:8,e=2);f=qb(b.wantedTiles)}d*=f;e*=f;if(c.d<d){jk(c);d=Math.min(d-c.d,e,c.Ub());for(e=0;e<d;++e)f=gk(c)[0],Wc(f,"change",c.g,!1,c),f.load();c.d+=d}}c=this.oa;d=0;for(e=c.length;d<e;++d)c[d](this,b);c.length=0};l.pi=function(){this.render()};
+l.qi=function(){var b=this.qc(),b=m(b)?Af(b):null;qi(this.r);null===b?Nf(this.b):(b.appendChild(this.b),ki(this.r,null===this.ga?b:this.ga));this.j()};l.ri=function(){this.render()};l.si=function(){this.render()};l.$i=function(){null!==this.Q&&(Yc(this.Q),this.Q=null);var b=this.a();null!==b&&(this.Q=z(b,"propertychange",this.si,!1,this));this.render()};l.bi=function(){this.render()};l.ci=function(){this.render()};
+l.ai=function(){if(null!==this.p){for(var b=this.p.length,c=0;c<b;++c)Yc(this.p[c]);this.p=null}b=this.Eb();null!=b&&(this.p=[z(b,"propertychange",this.ci,!1,this),z(b,"change",this.bi,!1,this)]);this.render()};l.Zk=function(){var b=this.q;Wh(b);b.Ye()};l.render=function(){null!=this.q.X||this.q.start()};l.Uk=function(b){if(m(this.i.remove(b)))return b};l.Vk=function(b){var c;m(this.g.remove(b))&&(c=b);return c};l.Wk=function(b){return this.Eb().Yb().remove(b)};l.Xk=function(b){if(m(this.n.remove(b)))return b};
+l.Yk=function(b){var c,d,e,f=this.e(),g=this.a(),h=null;if(m(f)&&0<f[0]&&0<f[1]&&null!==g&&af(g)){var h=cb(g.j),k=this.Eb().Da(),n={};c=0;for(d=k.length;c<d;++c)n[ma(k[c].layer)]=k[c];e=$e(g);h={animate:!1,attributions:{},coordinateToPixelMatrix:this.Uc,extent:null,focus:null===this.V?e.center:this.V,index:this.Wd++,layerStates:n,layerStatesArray:k,logos:Cb(this.Xd),pixelRatio:this.Yd,pixelToCoordinateMatrix:this.Zd,postRenderFunctions:[],size:f,skippedFeatureUids:this.ca,tileQueue:this.Ab,time:b,
+usedTiles:{},viewState:e,viewHints:h,wantedTiles:{}}}if(null!==h){b=this.F;c=f=0;for(d=b.length;c<d;++c)g=b[c],g(this,h)&&(b[f++]=g);b.length=f;h.extent=ne(e.center,e.resolution,e.rotation,h.size)}this.d=h;this.ba.Ld(h);null!==h&&(h.animate&&this.render(),Array.prototype.push.apply(this.oa,h.postRenderFunctions),0!==this.F.length||h.viewHints[0]||h.viewHints[1]||de(h.extent,this.Ea)||(this.dispatchEvent(new Zg("moveend",this,h)),c=h.extent,d=this.Ea,m(d)&&(d[0]=c[0],d[1]=c[1],d[2]=c[2],d[3]=c[3])));
+this.dispatchEvent(new Zg("postrender",this,h));$h(this.ni,this)};l.cg=function(b){this.set("layergroup",b)};O.prototype.setLayerGroup=O.prototype.cg;O.prototype.J=function(b){this.set("size",b)};O.prototype.setSize=O.prototype.J;O.prototype.qa=function(b){this.set("target",b)};O.prototype.setTarget=O.prototype.qa;O.prototype.Ta=function(b){this.set("view",b)};O.prototype.setView=O.prototype.Ta;O.prototype.eb=function(b){b=ma(b).toString();this.ca[b]=!0;this.render()};
+O.prototype.j=function(){var b=this.qc(),b=m(b)?Af(b):null;if(null===b)this.J(void 0);else{var c=zf(b),d=Hb&&b.currentStyle;d&&Rf(xf(c))&&"auto"!=d.width&&"auto"!=d.height&&!d.boxSizing?(c=Tg(b,d.width,"width","pixelWidth"),b=Tg(b,d.height,"height","pixelHeight"),b=new wf(c,b)):(d=new wf(b.offsetWidth,b.offsetHeight),c=Vg(b,"padding"),b=Yg(b),b=new wf(d.width-b.left-c.left-c.right-b.right,d.height-b.top-c.top-c.bottom-b.bottom));this.J([b.width,b.height])}};
+O.prototype.fc=function(b){b=ma(b).toString();delete this.ca[b];this.render()};
+function Po(b){var c=null;m(b.keyboardEventTarget)&&(c=ia(b.keyboardEventTarget)?document.getElementById(b.keyboardEventTarget):b.keyboardEventTarget);var d={},e={};if(!m(b.logo)||"boolean"==typeof b.logo&&b.logo)e["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszWWMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvYasvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvXH1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1VkbMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLPVcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqTacrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaarldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+HizeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDnBAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSFhYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJREFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxCBrb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7ahgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCnB3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDgq82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC"]="http://openlayers.org/";
+else{var f=b.logo;ia(f)?e[f]="":la(f)&&(e[f.src]=f.href)}f=b.layers instanceof I?b.layers:new I({layers:b.layers});d.layergroup=f;d.target=b.target;d.view=m(b.view)?b.view:new A;var f=Yj,g;m(b.renderer)?ga(b.renderer)?g=b.renderer:ia(b.renderer)&&(g=[b.renderer]):g=Oo;var h,k;h=0;for(k=g.length;h<k;++h){var n=g[h];if("canvas"==n){if(dg){f=En;break}}else if("dom"==n){f=Qn;break}else if("webgl"==n&&$f){f=Lo;break}}var p;m(b.controls)?p=ga(b.controls)?new B(cb(b.controls)):b.controls:p=gh();g=m(b.deviceOptions)?
+b.deviceOptions:{};var q;m(b.interactions)?q=ga(b.interactions)?new B(cb(b.interactions)):b.interactions:q=mm();b=m(b.overlays)?ga(b.overlays)?new B(cb(b.overlays)):b.overlays:new B;return{controls:p,deviceOptions:g,interactions:q,keyboardEventTarget:c,logos:e,overlays:b,$k:f,xl:d}}tm();function P(b){td.call(this);this.q=m(b.insertFirst)?b.insertFirst:!0;this.r=m(b.stopEvent)?b.stopEvent:!0;this.aa=If("DIV");this.aa.style.position="absolute";this.a={Wc:"",vd:"",Md:"",Nd:"",visible:!0};this.b=null;z(this,xd("element"),this.Uh,!1,this);z(this,xd("map"),this.hi,!1,this);z(this,xd("offset"),this.ji,!1,this);z(this,xd("position"),this.li,!1,this);z(this,xd("positioning"),this.mi,!1,this);m(b.element)&&this.Ee(b.element);this.j(m(b.offset)?b.offset:[0,0]);this.p(m(b.positioning)?b.positioning:
+"top-left");m(b.position)&&this.f(b.position)}u(P,td);P.prototype.d=function(){return this.get("element")};P.prototype.getElement=P.prototype.d;P.prototype.e=function(){return this.get("map")};P.prototype.getMap=P.prototype.e;P.prototype.g=function(){return this.get("offset")};P.prototype.getOffset=P.prototype.g;P.prototype.n=function(){return this.get("position")};P.prototype.getPosition=P.prototype.n;P.prototype.i=function(){return this.get("positioning")};P.prototype.getPositioning=P.prototype.i;
+l=P.prototype;l.Uh=function(){Kf(this.aa);var b=this.d();null!=b&&Jf(this.aa,b)};l.hi=function(){null!==this.b&&(Nf(this.aa),Yc(this.b),this.b=null);var b=this.e();null!=b&&(this.b=z(b,"postrender",this.render,!1,this),Qo(this),b=this.r?b.t:b.Da,this.q?Mf(b,this.aa,0):Jf(b,this.aa))};l.render=function(){Qo(this)};l.ji=function(){Qo(this)};l.li=function(){Qo(this)};l.mi=function(){Qo(this)};l.Ee=function(b){this.set("element",b)};P.prototype.setElement=P.prototype.Ee;
+P.prototype.setMap=function(b){this.set("map",b)};P.prototype.setMap=P.prototype.setMap;P.prototype.j=function(b){this.set("offset",b)};P.prototype.setOffset=P.prototype.j;P.prototype.f=function(b){this.set("position",b)};P.prototype.setPosition=P.prototype.f;P.prototype.p=function(b){this.set("positioning",b)};P.prototype.setPositioning=P.prototype.p;
+function Qo(b){var c=b.e(),d=b.n();if(m(c)&&null!==c.d&&m(d)){var d=c.f(d),e=c.e(),c=b.aa.style,f=b.g(),g=b.i(),h=f[0],f=f[1];if("bottom-right"==g||"center-right"==g||"top-right"==g)""!==b.a.vd&&(b.a.vd=c.left=""),h=Math.round(e[0]-d[0]-h)+"px",b.a.Md!=h&&(b.a.Md=c.right=h);else{""!==b.a.Md&&(b.a.Md=c.right="");if("bottom-center"==g||"center-center"==g||"top-center"==g)h-=Qg(b.aa).width/2;h=Math.round(d[0]+h)+"px";b.a.vd!=h&&(b.a.vd=c.left=h)}if("bottom-left"==g||"bottom-center"==g||"bottom-right"==
+g)""!==b.a.Nd&&(b.a.Nd=c.top=""),d=Math.round(e[1]-d[1]-f)+"px",b.a.Wc!=d&&(b.a.Wc=c.bottom=d);else{""!==b.a.Wc&&(b.a.Wc=c.bottom="");if("center-left"==g||"center-center"==g||"center-right"==g)f-=Qg(b.aa).height/2;d=Math.round(d[1]+f)+"px";b.a.Nd!=d&&(b.a.Nd=c.top=d)}b.a.visible||(Sg(b.aa,!0),b.a.visible=!0)}else b.a.visible&&(Sg(b.aa,!1),b.a.visible=!1)};function Ro(b){b=m(b)?b:{};this.e=m(b.collapsed)?b.collapsed:!0;this.f=m(b.collapsible)?b.collapsible:!0;this.f||(this.e=!1);var c=m(b.className)?b.className:"ol-overviewmap",d=m(b.tipLabel)?b.tipLabel:"Overview map";this.j=m(b.collapseLabel)?b.collapseLabel:"\u00ab";this.q=m(b.label)?b.label:"\u00bb";this.p=Ff("SPAN",{},this.f&&!this.e?this.j:this.q);d=Ff("BUTTON",{type:"button",title:d},this.p);z(d,"click",this.hj,!1,this);z(d,["mouseout",xc],function(){this.blur()},!1);var e=Ff("DIV","ol-overviewmap-map"),
+f=this.b=new O({controls:new B,interactions:new B,target:e});m(b.layers)&&b.layers.forEach(function(b){f.Se(b)},this);var g=Ff("DIV","ol-overviewmap-box");this.g=new P({position:[0,0],positioning:"bottom-left",element:g});this.b.Te(this.g);c=Ff("DIV",c+" ol-unselectable ol-control"+(this.e&&this.f?" ol-collapsed":"")+(this.f?"":" ol-uncollapsible"),e,d);$g.call(this,{element:c,render:m(b.render)?b.render:So,target:b.target})}u(Ro,$g);l=Ro.prototype;
+l.setMap=function(b){var c=this.a;null===b&&null!==c&&Xc(c,xd("view"),this.uf,!1,this);Ro.S.setMap.call(this,b);null!==b&&(0===this.b.ea().Ib()&&this.b.O("layergroup",b),To(this),z(b,xd("view"),this.uf,!1,this),this.b.j(),Uo(this))};function To(b){var c=b.a.a();null===c||b.b.a().O("rotation",c)}
+function So(){var b=this.a,c=this.b;if(null!==b.d&&null!==c.d){var d=b.e(),b=b.a().g(d),e=c.e(),d=c.a().g(e),f=c.f(me(b)),c=c.f(ie(b)),c=new wf(Math.abs(f[0]-c[0]),Math.abs(f[1]-c[1])),f=e[0],e=e[1];c.width<.1*f||c.height<.1*e||c.width>.75*f||c.height>.75*e?Uo(this):$d(d,b)||(b=this.b,d=this.a.a(),b.a().Oa(d.a()))}Vo(this)}l.uf=function(){To(this)};function Uo(b){var c=b.a;b=b.b;var d=c.e(),c=c.a().g(d),d=b.e();b=b.a();var e=Math.log(7.5)/Math.LN2;ue(c,1/(.1*Math.pow(2,e/2)));b.ge(c,d)}
+function Vo(b){var c=b.a,d=b.b;if(null!==c.d&&null!==d.d){var e=c.e(),f=c.a(),g=d.a();d.e();var c=f.e(),h=b.g,d=b.g.d(),f=f.g(e),e=g.b(),g=he(f),f=je(f),k;b=b.a.a().a();m(b)&&(k=[g[0]-b[0],g[1]-b[1]],Dd(k,c),yd(k,b));h.f(k);null!=d&&(k=new wf(Math.abs((g[0]-f[0])/e),Math.abs((f[1]-g[1])/e)),c=Rf(xf(zf(d))),!Hb||Tb("10")||c&&Tb("8")?(d=d.style,Ib?d.MozBoxSizing="border-box":Jb?d.WebkitBoxSizing="border-box":d.boxSizing="border-box",d.width=Math.max(k.width,0)+"px",d.height=Math.max(k.height,0)+"px"):
+(b=d.style,c?(c=Vg(d,"padding"),d=Yg(d),b.pixelWidth=k.width-d.left-c.left-c.right-d.right,b.pixelHeight=k.height-d.top-c.top-c.bottom-d.bottom):(b.pixelWidth=k.width,b.pixelHeight=k.height)))}}l.hj=function(b){b.preventDefault();Wo(this)};function Wo(b){Eg(b.element,"ol-collapsed");Qf(b.p,b.e?b.j:b.q);b.e=!b.e;var c=b.b;b.e||null!==c.d||(c.j(),Uo(b),Wc(c,"postrender",function(){Vo(this)},!1,b))}l.gj=function(){return this.f};
+l.jj=function(b){this.f!==b&&(this.f=b,Eg(this.element,"ol-uncollapsible"),!b&&this.e&&Wo(this))};l.ij=function(b){this.f&&this.e!==b&&Wo(this)};l.fj=function(){return this.e};function Xo(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-scale-line";this.f=Ff("DIV",c+"-inner");this.aa=Ff("DIV",c+" ol-unselectable",this.f);this.q=null;this.g=m(b.minWidth)?b.minWidth:64;this.b=!1;this.t=void 0;this.r="";this.e=null;$g.call(this,{element:this.aa,render:m(b.render)?b.render:Yo,target:b.target});z(this,xd("units"),this.F,!1,this);this.p(b.units||"metric")}u(Xo,$g);var Zo=[1,2,5];Xo.prototype.j=function(){return this.get("units")};Xo.prototype.getUnits=Xo.prototype.j;
+function Yo(b){b=b.frameState;null===b?this.q=null:this.q=b.viewState;$o(this)}Xo.prototype.F=function(){$o(this)};Xo.prototype.p=function(b){this.set("units",b)};Xo.prototype.setUnits=Xo.prototype.p;
+function $o(b){var c=b.q;if(null===c)b.b&&(Sg(b.aa,!1),b.b=!1);else{var d=c.center,e=c.projection,c=e.je(c.resolution,d),f=e.c,g=b.j();"degrees"!=f||"metric"!=g&&"imperial"!=g&&"us"!=g&&"nautical"!=g?"ft"!=f&&"m"!=f||"degrees"!=g?b.e=null:(null===b.e&&(b.e=De(e,Ee("EPSG:4326"))),d=Math.cos(bc(b.e(d)[1])),e=ze.radius,"ft"==f&&(e/=.3048),c*=180/(Math.PI*d*e)):(b.e=null,d=Math.cos(bc(d[1])),c*=Math.PI*d*ze.radius/180);d=b.g*c;f="";"degrees"==g?d<1/60?(f="\u2033",c*=3600):1>d?(f="\u2032",c*=60):f="\u00b0":
+"imperial"==g?.9144>d?(f="in",c/=.0254):1609.344>d?(f="ft",c/=.3048):(f="mi",c/=1609.344):"nautical"==g?(c/=1852,f="nm"):"metric"==g?1>d?(f="mm",c*=1E3):1E3>d?f="m":(f="km",c/=1E3):"us"==g&&(.9144>d?(f="in",c*=39.37):1609.344>d?(f="ft",c/=.30480061):(f="mi",c/=1609.3472));for(d=3*Math.floor(Math.log(b.g*c)/Math.log(10));;){e=Zo[d%3]*Math.pow(10,Math.floor(d/3));g=Math.round(e/c);if(isNaN(g)){Sg(b.aa,!1);b.b=!1;return}if(g>=b.g)break;++d}c=e+f;b.r!=c&&(b.f.innerHTML=c,b.r=c);b.t!=g&&(b.f.style.width=
+g+"px",b.t=g);b.b||(Sg(b.aa,!0),b.b=!0)}};function ap(b){pc.call(this);this.c=b;this.a={}}u(ap,pc);var bp=[];ap.prototype.La=function(b,c,d,e){ga(c)||(c&&(bp[0]=c.toString()),c=bp);for(var f=0;f<c.length;f++){var g=z(b,c[f],d||this.handleEvent,e||!1,this.c||this);if(!g)break;this.a[g.key]=g}return this};
+ap.prototype.Fe=function(b,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)this.Fe(b,c[g],d,e,f);else d=d||this.handleEvent,f=f||this.c||this,d=Qc(d),e=!!e,c=Ec(b)?Lc(b.gb,String(c),d,e,f):b?(b=Sc(b))?Lc(b,c,d,e,f):null:null,c&&(Yc(c),delete this.a[c.key]);return this};function cp(b){ob(b.a,Yc);b.a={}}ap.prototype.M=function(){ap.S.M.call(this);cp(this)};ap.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};function dp(b,c,d){jd.call(this);this.target=b;this.handle=c||b;this.a=d||new Gg(NaN,NaN,NaN,NaN);this.b=zf(b);this.c=new ap(this);sc(this,this.c);z(this.handle,["touchstart","mousedown"],this.df,!1,this)}u(dp,jd);var ep=Hb||Ib&&Tb("1.9.3");l=dp.prototype;l.clientX=0;l.clientY=0;l.screenX=0;l.screenY=0;l.dg=0;l.eg=0;l.nc=0;l.oc=0;l.Rb=!1;l.M=function(){dp.S.M.call(this);Xc(this.handle,["touchstart","mousedown"],this.df,!1,this);cp(this.c);ep&&this.b.releaseCapture();this.handle=this.target=null};
+l.df=function(b){var c="mousedown"==b.type;if(this.Rb||c&&!Cc(b))this.dispatchEvent("earlycancel");else if(fp(b),this.dispatchEvent(new gp("start",this,b.clientX,b.clientY))){this.Rb=!0;b.preventDefault();var c=this.b,d=c.documentElement,e=!ep;this.c.La(c,["touchmove","mousemove"],this.ii,e);this.c.La(c,["touchend","mouseup"],this.od,e);ep?(d.setCapture(!1),this.c.La(d,"losecapture",this.od)):this.c.La(c?c.parentWindow||c.defaultView:window,"blur",this.od);this.e&&this.c.La(this.e,"scroll",this.sk,
+e);this.clientX=this.dg=b.clientX;this.clientY=this.eg=b.clientY;this.screenX=b.screenX;this.screenY=b.screenY;this.nc=this.target.offsetLeft;this.oc=this.target.offsetTop;this.d=Sf(xf(this.b));ua()}};l.od=function(b){cp(this.c);ep&&this.b.releaseCapture();if(this.Rb){fp(b);this.Rb=!1;var c=hp(this,this.nc),d=ip(this,this.oc);this.dispatchEvent(new gp("end",this,b.clientX,b.clientY,0,c,d))}else this.dispatchEvent("earlycancel")};
+function fp(b){var c=b.type;"touchstart"==c||"touchmove"==c?Ac(b,b.a.targetTouches[0],b.b):"touchend"!=c&&"touchcancel"!=c||Ac(b,b.a.changedTouches[0],b.b)}
+l.ii=function(b){fp(b);var c=1*(b.clientX-this.clientX),d=b.clientY-this.clientY;this.clientX=b.clientX;this.clientY=b.clientY;this.screenX=b.screenX;this.screenY=b.screenY;if(!this.Rb){var e=this.dg-this.clientX,f=this.eg-this.clientY;if(0<e*e+f*f)if(this.dispatchEvent(new gp("start",this,b.clientX,b.clientY)))this.Rb=!0;else{this.Sa||this.od(b);return}}d=jp(this,c,d);c=d.x;d=d.y;this.Rb&&this.dispatchEvent(new gp("beforedrag",this,b.clientX,b.clientY,0,c,d))&&(kp(this,b,c,d),b.preventDefault())};
+function jp(b,c,d){var e=Sf(xf(b.b));c+=e.x-b.d.x;d+=e.y-b.d.y;b.d=e;b.nc+=c;b.oc+=d;c=hp(b,b.nc);b=ip(b,b.oc);return new vf(c,b)}l.sk=function(b){var c=jp(this,0,0);b.clientX=this.clientX;b.clientY=this.clientY;kp(this,b,c.x,c.y)};function kp(b,c,d,e){b.target.style.left=d+"px";b.target.style.top=e+"px";b.dispatchEvent(new gp("drag",b,c.clientX,c.clientY,0,d,e))}
+function hp(b,c){var d=b.a,e=isNaN(d.left)?null:d.left,d=isNaN(d.width)?0:d.width;return Math.min(null!=e?e+d:Infinity,Math.max(null!=e?e:-Infinity,c))}function ip(b,c){var d=b.a,e=isNaN(d.top)?null:d.top,d=isNaN(d.height)?0:d.height;return Math.min(null!=e?e+d:Infinity,Math.max(null!=e?e:-Infinity,c))}function gp(b,c,d,e,f,g,h){uc.call(this,b);this.clientX=d;this.clientY=e;this.left=m(g)?g:c.nc;this.top=m(h)?h:c.oc}u(gp,uc);function lp(b){b=m(b)?b:{};this.e=void 0;this.f=mp;this.g=null;this.j=!1;var c=m(b.className)?b.className:"ol-zoomslider",d=Ff("DIV",[c+"-thumb","ol-unselectable"]),c=Ff("DIV",[c,"ol-unselectable","ol-control"],d);this.b=new dp(d);sc(this,this.b);z(this.b,"start",this.Th,!1,this);z(this.b,"drag",this.Rh,!1,this);z(this.b,"end",this.Sh,!1,this);z(c,"click",this.Qh,!1,this);z(d,"click",vc);$g.call(this,{element:c,render:m(b.render)?b.render:np})}u(lp,$g);var mp=0;l=lp.prototype;
+l.setMap=function(b){lp.S.setMap.call(this,b);null===b||b.render()};
+function np(b){if(null!==b.frameState){if(!this.j){var c=this.element,d=Qg(c),e=Of(c),c=Vg(e,"margin"),f=new wf(e.offsetWidth,e.offsetHeight),e=f.width+c.right+c.left,c=f.height+c.top+c.bottom;this.g=[e,c];e=d.width-e;c=d.height-c;d.width>d.height?(this.f=1,d=new Gg(0,0,e,0)):(this.f=mp,d=new Gg(0,0,0,c));this.b.a=d||new Gg(NaN,NaN,NaN,NaN);this.j=!0}b=b.frameState.viewState.resolution;b!==this.e&&(this.e=b,b=1-Ze(this.a.a())(b),d=this.b,c=Of(this.element),1==this.f?Kg(c,d.a.left+d.a.width*b):Kg(c,
+d.a.left,d.a.top+d.a.height*b))}}l.Qh=function(b){var c=this.a,d=c.a(),e=d.b();c.Ua(jf({resolution:e,duration:200,easing:cf}));b=op(this,b.offsetX-this.g[0]/2,b.offsetY-this.g[1]/2);b=pp(this,b);d.d(d.constrainResolution(b))};l.Th=function(){bf(this.a.a(),1)};l.Rh=function(b){b=op(this,b.left,b.top);this.e=pp(this,b);this.a.a().d(this.e)};l.Sh=function(){var b=this.a,c=b.a();bf(c,-1);b.Ua(jf({resolution:this.e,duration:200,easing:cf}));b=c.constrainResolution(this.e);c.d(b)};
+function op(b,c,d){var e=b.b.a;return Yb(1===b.f?(c-e.left)/e.width:(d-e.top)/e.height,0,1)}function pp(b,c){return Ye(b.a.a())(1-c)};function qp(b){b=m(b)?b:{};this.b=m(b.extent)?b.extent:null;var c=m(b.className)?b.className:"ol-zoom-extent",d=Ff("BUTTON",{type:"button",title:m(b.tipLabel)?b.tipLabel:"Fit to extent"});z(d,"click",this.e,!1,this);z(d,["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control",d);$g.call(this,{element:c,target:b.target})}u(qp,$g);qp.prototype.e=function(b){b.preventDefault();var c=this.a;b=c.a();var d=null===this.b?b.q.D():this.b,c=c.e();b.ge(d,c)};function Q(b){td.call(this);b=m(b)?b:{};this.a=null;z(this,xd("tracking"),this.n,!1,this);this.b(m(b.tracking)?b.tracking:!1)}u(Q,td);Q.prototype.M=function(){this.b(!1);Q.S.M.call(this)};
+Q.prototype.j=function(b){b=b.a;if(null!=b.alpha){var c=bc(b.alpha);this.set("alpha",c);"boolean"==typeof b.absolute&&b.absolute?this.set("heading",c):null!=b.webkitCompassHeading&&null!=b.webkitCompassAccuracy&&-1!=b.webkitCompassAccuracy&&this.set("heading",bc(b.webkitCompassHeading))}null!=b.beta&&this.set("beta",bc(b.beta));null!=b.gamma&&this.set("gamma",bc(b.gamma));this.l()};Q.prototype.e=function(){return this.get("alpha")};Q.prototype.getAlpha=Q.prototype.e;Q.prototype.f=function(){return this.get("beta")};
+Q.prototype.getBeta=Q.prototype.f;Q.prototype.g=function(){return this.get("gamma")};Q.prototype.getGamma=Q.prototype.g;Q.prototype.i=function(){return this.get("heading")};Q.prototype.getHeading=Q.prototype.i;Q.prototype.d=function(){return this.get("tracking")};Q.prototype.getTracking=Q.prototype.d;Q.prototype.n=function(){if(eg){var b=this.d();b&&null===this.a?this.a=z(ba,"deviceorientation",this.j,!1,this):b||null===this.a||(Yc(this.a),this.a=null)}};
+Q.prototype.b=function(b){this.set("tracking",b)};Q.prototype.setTracking=Q.prototype.b;function rp(b){td.call(this);this.i=b;z(this.i,["change","input"],this.g,!1,this);z(this,xd("value"),this.n,!1,this);z(this,xd("checked"),this.f,!1,this)}u(rp,td);rp.prototype.a=function(){return this.get("checked")};rp.prototype.getChecked=rp.prototype.a;rp.prototype.b=function(){return this.get("value")};rp.prototype.getValue=rp.prototype.b;rp.prototype.e=function(b){this.set("value",b)};rp.prototype.setValue=rp.prototype.e;rp.prototype.d=function(b){this.set("checked",b)};
+rp.prototype.setChecked=rp.prototype.d;rp.prototype.g=function(){var b=this.i;"checkbox"===b.type||"radio"===b.type?this.d(b.checked):this.e(b.value)};rp.prototype.f=function(){this.i.checked=this.a()};rp.prototype.n=function(){this.i.value=this.b()};function R(b){td.call(this);this.X=void 0;this.b="geometry";this.g=null;this.a=void 0;this.f=null;z(this,xd(this.b),this.pd,!1,this);m(b)&&(b instanceof Mk||null===b?this.Ma(b):this.G(b))}u(R,td);R.prototype.clone=function(){var b=new R(this.L());b.e(this.b);var c=this.N();null!=c&&b.Ma(c.clone());c=this.g;null===c||b.i(c);return b};R.prototype.N=function(){return this.get(this.b)};R.prototype.getGeometry=R.prototype.N;l=R.prototype;l.nh=function(){return this.X};l.mh=function(){return this.b};
+l.Si=function(){return this.g};l.Ti=function(){return this.a};l.$h=function(){this.l()};l.pd=function(){null!==this.f&&(Yc(this.f),this.f=null);var b=this.N();null!=b&&(this.f=z(b,"change",this.$h,!1,this),this.l())};l.Ma=function(b){this.set(this.b,b)};R.prototype.setGeometry=R.prototype.Ma;R.prototype.i=function(b){this.g=b;null===b?b=void 0:ka(b)||(b=ga(b)?b:[b],b=bd(b));this.a=b;this.l()};R.prototype.d=function(b){this.X=b;this.l()};
+R.prototype.e=function(b){Xc(this,xd(this.b),this.pd,!1,this);this.b=b;z(this,xd(this.b),this.pd,!1,this);this.pd()};function sp(b){b=m(b)?b:{};this.g=this.d=this.e=this.c=this.b=this.a=null;this.f=void 0;this.tf(m(b.style)?b.style:Wl);m(b.features)?ga(b.features)?this.Mc(new B(cb(b.features))):this.Mc(b.features):this.Mc(new B);m(b.map)&&this.setMap(b.map)}l=sp.prototype;l.rf=function(b){this.a.push(b)};l.Ni=function(){return this.a};l.sf=function(){tp(this)};l.Yh=function(b){b=b.element;this.c[ma(b).toString()]=z(b,"change",this.sf,!1,this);tp(this)};
+l.Zh=function(b){b=ma(b.element).toString();Yc(this.c[b]);delete this.c[b];tp(this)};l.Qi=function(){tp(this)};l.Ri=function(b){if(null!==this.a){var c=this.f;m(c)||(c=Wl);var d=b.a;b=b.frameState;var e=b.viewState.resolution,f=zn(e,b.pixelRatio),g,h,k,n;this.a.forEach(function(b){n=b.a;k=m(n)?n.call(b,e):c(b,e);if(null!=k)for(h=k.length,g=0;g<h;++g)An(d,b,k[g],f,this.Qi,this)},this)}};l.wd=function(b){this.a.remove(b)};function tp(b){null===b.e||b.e.render()}
+l.Mc=function(b){null!==this.b&&(Ta(this.b,Yc),this.b=null);null!==this.c&&(Ta(rb(this.c),Yc),this.c=null);this.a=b;null!==b&&(this.b=[z(b,"add",this.Yh,!1,this),z(b,"remove",this.Zh,!1,this)],this.c={},b.forEach(function(b){this.c[ma(b).toString()]=z(b,"change",this.sf,!1,this)},this));tp(this)};l.setMap=function(b){null!==this.d&&(Yc(this.d),this.d=null);tp(this);this.e=b;null!==b&&(this.d=z(b,"postcompose",this.Ri,!1,this),b.render())};l.tf=function(b){this.g=b;this.f=Vl(b);tp(this)};l.Oi=function(){return this.g};
+l.Pi=function(){return this.f};function up(){this.defaultDataProjection=null}function vp(b,c,d){var e;m(d)&&(e={dataProjection:m(d.dataProjection)?d.dataProjection:b.Ba(c),featureProjection:d.featureProjection});return wp(b,e)}function wp(b,c){var d;m(c)&&(d={featureProjection:c.featureProjection,dataProjection:null!=c.dataProjection?c.dataProjection:b.defaultDataProjection});return d}
+function xp(b,c,d){var e=m(d)?Ee(d.featureProjection):null;d=m(d)?Ee(d.dataProjection):null;return null===e||null===d||Ue(e,d)?b:b instanceof Mk?(c?b.clone():b).e(c?e:d,c?d:e):Xe(c?cb(b):b,c?e:d,c?d:e)};var yp=ba.JSON.parse,zp=ba.JSON.stringify;function Ap(){this.defaultDataProjection=null}u(Ap,up);function Bp(b){return la(b)?b:ia(b)?(b=yp(b),m(b)?b:null):null}l=Ap.prototype;l.H=function(){return"json"};l.Nb=function(b,c){return Cp(this,Bp(b),vp(this,b,c))};l.ja=function(b,c){return this.b(Bp(b),vp(this,b,c))};l.Jc=function(b,c){var d=Bp(b),e=vp(this,b,c);return Dp(d,e)};l.Ba=function(b){b=Bp(b).crs;return null!=b?"name"==b.type?Ee(b.properties.name):"EPSG"==b.type?Ee("EPSG:"+b.properties.code):null:this.defaultDataProjection};
+l.Pd=function(b,c){return zp(this.a(b,c))};l.Qb=function(b,c){return zp(this.d(b,c))};l.Qc=function(b,c){return zp(this.e(b,c))};function Ep(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee(null!=b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326");this.c=b.geometryName}u(Ep,Ap);function Dp(b,c){return null===b?null:xp((0,Fp[b.type])(b),!1,c)}
+var Fp={Point:function(b){return new jl(b.coordinates)},LineString:function(b){return new M(b.coordinates)},Polygon:function(b){return new H(b.coordinates)},MultiPoint:function(b){return new un(b.coordinates)},MultiLineString:function(b){return new rn(b.coordinates)},MultiPolygon:function(b){return new vn(b.coordinates)},GeometryCollection:function(b,c){var d=Va(b.geometries,function(b){return Dp(b,c)});return new jn(d)}},Gp={Point:function(b){return{type:"Point",coordinates:b.K()}},LineString:function(b){return{type:"LineString",
+coordinates:b.K()}},Polygon:function(b){return{type:"Polygon",coordinates:b.K()}},MultiPoint:function(b){return{type:"MultiPoint",coordinates:b.K()}},MultiLineString:function(b){return{type:"MultiLineString",coordinates:b.K()}},MultiPolygon:function(b){return{type:"MultiPolygon",coordinates:b.K()}},GeometryCollection:function(b,c){return{type:"GeometryCollection",geometries:Va(b.d,function(b){return(0,Gp[b.H()])(xp(b,!0,c))})}},Circle:function(){return{type:"GeometryCollection",geometries:[]}}};
+function Cp(b,c,d){d=Dp(c.geometry,d);var e=new R;m(b.c)&&e.e(b.c);e.Ma(d);m(c.id)&&e.d(c.id);m(c.properties)&&e.G(c.properties);return e}Ep.prototype.b=function(b,c){if("Feature"==b.type)return[Cp(this,b,c)];if("FeatureCollection"==b.type){var d=[],e=b.features,f,g;f=0;for(g=e.length;f<g;++f)d.push(Cp(this,e[f],c));return d}return[]};
+Ep.prototype.a=function(b,c){c=wp(this,c);var d={type:"Feature"},e=b.X;null!=e&&(d.id=e);e=b.N();null!=e&&(e=(0,Gp[e.H()])(xp(e,!0,c)),d.geometry=e);e=b.L();zb(e,b.b);xb(e)||(d.properties=e);return d};Ep.prototype.d=function(b,c){c=wp(this,c);var d=[],e,f;e=0;for(f=b.length;e<f;++e)d.push(this.a(b[e],c));return{type:"FeatureCollection",features:d}};Ep.prototype.e=function(b,c){return(0,Gp[b.H()])(xp(b,!0,wp(this,c)))};function Hp(b){if("undefined"!=typeof XMLSerializer)return(new XMLSerializer).serializeToString(b);if(b=b.xml)return b;throw Error("Your browser does not support serializing XML documents");};var Ip;a:if(document.implementation&&document.implementation.createDocument)Ip=document.implementation.createDocument("","",null);else{if("undefined"!=typeof ActiveXObject){var Jp=new ActiveXObject("MSXML2.DOMDocument");if(Jp){Jp.resolveExternals=!1;Jp.validateOnParse=!1;try{Jp.setProperty("ProhibitDTD",!0),Jp.setProperty("MaxXMLSize",2048),Jp.setProperty("MaxElementDepth",256)}catch(Kp){}}if(Jp){Ip=Jp;break a}}throw Error("Your browser does not support creating new documents");}var Lp=Ip;
+function Mp(b,c){return Lp.createElementNS(b,c)}function Np(b,c){null===b&&(b="");return Lp.createNode(1,c,b)}var Op=document.implementation&&document.implementation.createDocument?Mp:Np;function Pp(b,c){return Qp(b,c,[]).join("")}function Qp(b,c,d){if(4==b.nodeType||3==b.nodeType)c?d.push(String(b.nodeValue).replace(/(\r\n|\r|\n)/g,"")):d.push(b.nodeValue);else for(b=b.firstChild;null!==b;b=b.nextSibling)Qp(b,c,d);return d}function Rp(b){return b.localName}
+function Sp(b){var c=b.localName;return m(c)?c:b.baseName}var Tp=Hb?Sp:Rp;function Up(b){return b instanceof Document}function Vp(b){return la(b)&&9==b.nodeType}var Wp=Hb?Vp:Up;function Xp(b){return b instanceof Node}function Yp(b){return la(b)&&m(b.nodeType)}var Zp=Hb?Yp:Xp;function $p(b,c,d){return b.getAttributeNS(c,d)||""}function aq(b,c,d){var e="";b=bq(b,c,d);m(b)&&(e=b.nodeValue);return e}var cq=document.implementation&&document.implementation.createDocument?$p:aq;
+function dq(b,c,d){return b.getAttributeNodeNS(c,d)}function eq(b,c,d){var e=null;b=b.attributes;for(var f,g,h=0,k=b.length;h<k;++h)if(f=b[h],f.namespaceURI==c&&(g=f.prefix?f.prefix+":"+d:d,g==f.nodeName)){e=f;break}return e}var bq=document.implementation&&document.implementation.createDocument?dq:eq;function fq(b,c,d,e){b.setAttributeNS(c,d,e)}function gq(b,c,d,e){null===c?b.setAttribute(d,e):(c=b.ownerDocument.createNode(2,d,c),c.nodeValue=e,b.setAttributeNode(c))}
+var hq=document.implementation&&document.implementation.createDocument?fq:gq;function iq(b){return(new DOMParser).parseFromString(b,"application/xml")}function jq(b,c){return function(d,e){var f=b.call(c,d,e);m(f)&&db(e[e.length-1],f)}}function kq(b,c){return function(d,e){var f=b.call(m(c)?c:this,d,e);m(f)&&e[e.length-1].push(f)}}function lq(b,c){return function(d,e){var f=b.call(m(c)?c:this,d,e);m(f)&&(e[e.length-1]=f)}}
+function mq(b){return function(c,d){var e=b.call(m(void 0)?void 0:this,c,d);m(e)&&Bb(d[d.length-1],m(void 0)?void 0:c.localName).push(e)}}function S(b,c){return function(d,e){var f=b.call(m(void 0)?void 0:this,d,e);m(f)&&(e[e.length-1][m(c)?c:d.localName]=f)}}function nq(b,c,d){return oq(b,c,d)}function V(b,c){return function(d,e,f){b.call(m(c)?c:this,d,e,f);f[f.length-1].node.appendChild(d)}}
+function pq(b){var c,d;return function(e,f,g){if(!m(c)){c={};var h={};h[e.localName]=b;c[e.namespaceURI]=h;d=qq(e.localName)}rq(c,d,f,g)}}function qq(b,c){return function(d,e,f){d=e[e.length-1].node;e=b;m(e)||(e=f);f=c;m(c)||(f=d.namespaceURI);return Op(f,e)}}var sq=qq();function tq(b,c){for(var d=c.length,e=Array(d),f=0;f<d;++f)e[f]=b[c[f]];return e}function oq(b,c,d){d=m(d)?d:{};var e,f;e=0;for(f=b.length;e<f;++e)d[b[e]]=c;return d}
+function uq(b,c,d,e){for(c=c.firstElementChild;null!==c;c=c.nextElementSibling){var f=b[c.namespaceURI];m(f)&&(f=f[c.localName],m(f)&&f.call(e,c,d))}}function W(b,c,d,e,f){e.push(b);uq(c,d,e,f);return e.pop()}function rq(b,c,d,e,f,g){for(var h=(m(f)?f:d).length,k,n,p=0;p<h;++p)k=d[p],m(k)&&(n=c.call(g,k,e,m(f)?f[p]:void 0),m(n)&&b[n.namespaceURI][n.localName].call(g,n,k,e))}function vq(b,c,d,e,f,g,h){f.push(b);rq(c,d,e,f,g,h);f.pop()};function wq(){this.defaultDataProjection=null}u(wq,up);l=wq.prototype;l.H=function(){return"xml"};l.Nb=function(b,c){if(Wp(b))return xq(this,b,c);if(Zp(b))return this.Pf(b,c);if(ia(b)){var d=iq(b);return xq(this,d,c)}return null};function xq(b,c,d){b=yq(b,c,d);return 0<b.length?b[0]:null}l.ja=function(b,c){if(Wp(b))return yq(this,b,c);if(Zp(b))return this.Ob(b,c);if(ia(b)){var d=iq(b);return yq(this,d,c)}return[]};
+function yq(b,c,d){var e=[];for(c=c.firstChild;null!==c;c=c.nextSibling)1==c.nodeType&&db(e,b.Ob(c,d));return e}l.Jc=function(b,c){if(Wp(b))return this.i(b,c);if(Zp(b)){var d=this.Id(b,[vp(this,b,m(c)?c:{})]);return m(d)?d:null}return ia(b)?(d=iq(b),this.i(d,c)):null};l.Ba=function(b){return Wp(b)?this.Lc(b):Zp(b)?this.tc(b):ia(b)?(b=iq(b),this.Lc(b)):null};l.Pd=function(b,c){var d=this.p(b,c);return Hp(d)};l.Qb=function(b,c){var d=this.a(b,c);return Hp(d)};l.Qc=function(b,c){var d=this.o(b,c);return Hp(d)};function zq(b){b=m(b)?b:{};this.featureType=b.featureType;this.featureNS=b.featureNS;this.srsName=b.srsName;this.schemaLocation="";this.defaultDataProjection=null}u(zq,wq);l=zq.prototype;
+l.pe=function(b,c){var d=Tp(b),e;if("FeatureCollection"==d)e=W(null,this.Td,b,c,this);else if("featureMembers"==d||"featureMember"==d){e=c[0];var f=x(e,"featureType");if(!m(f)&&null!==b.firstElementChild){var g=b.firstElementChild,f=g.nodeName.split(":").pop();e.featureType=f;e.featureNS=g.namespaceURI}var g={},h={};g[f]="featureMembers"==d?kq(this.ye,this):lq(this.ye,this);h[x(e,"featureNS")]=g;e=W([],h,b,c)}m(e)||(e=[]);return e};
+l.Td=Object({"http://www.opengis.net/gml":{featureMember:kq(zq.prototype.pe),featureMembers:lq(zq.prototype.pe)}});l.Id=function(b,c){var d=c[0],e=b.firstElementChild.getAttribute("srsName");d.srsName=e;e=W(null,this.Le,b,c,this);if(null!=e)return xp(e,!1,d)};
+l.ye=function(b,c){var d,e=b.getAttribute("fid")||cq(b,"http://www.opengis.net/gml","id"),f={},g;for(d=b.firstElementChild;null!==d;d=d.nextElementSibling){var h=Tp(d);if(0===d.childNodes.length||1===d.childNodes.length&&3===d.firstChild.nodeType){var k=Pp(d,!1);/^[\s\xa0]*$/.test(k)&&(k=void 0);f[h]=k}else"boundedBy"!==h&&(g=h),f[h]=this.Id(d,c)}d=new R(f);m(g)&&d.e(g);e&&d.d(e);return d};l.Vf=function(b,c){var d=this.Hd(b,c);if(null!=d){var e=new jl(null);kl(e,"XYZ",d);return e}};
+l.Tf=function(b,c){var d=W([],this.yg,b,c,this);if(m(d))return new un(d)};l.Sf=function(b,c){var d=W([],this.xg,b,c,this);if(m(d)){var e=new rn(null);tn(e,d);return e}};l.Uf=function(b,c){var d=W([],this.zg,b,c,this);if(m(d)){var e=new vn(null);xn(e,d);return e}};l.Kf=function(b,c){uq(this.Cg,b,c,this)};l.mf=function(b,c){uq(this.vg,b,c,this)};l.Lf=function(b,c){uq(this.Dg,b,c,this)};l.Jd=function(b,c){var d=this.Hd(b,c);if(null!=d){var e=new M(null);qn(e,"XYZ",d);return e}};
+l.Ik=function(b,c){var d=W(null,this.Sc,b,c,this);if(null!=d)return d};l.Rf=function(b,c){var d=this.Hd(b,c);if(m(d)){var e=new hl(null);il(e,"XYZ",d);return e}};l.Kd=function(b,c){var d=W([null],this.Ud,b,c,this);if(m(d)&&null!==d[0]){var e=new H(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)db(f,d[h]),g.push(f.length);wl(e,"XYZ",f,g);return e}};l.Hd=function(b,c){return W(null,this.Sc,b,c,this)};l.yg=Object({"http://www.opengis.net/gml":{pointMember:kq(zq.prototype.Kf),pointMembers:kq(zq.prototype.Kf)}});
+l.xg=Object({"http://www.opengis.net/gml":{lineStringMember:kq(zq.prototype.mf),lineStringMembers:kq(zq.prototype.mf)}});l.zg=Object({"http://www.opengis.net/gml":{polygonMember:kq(zq.prototype.Lf),polygonMembers:kq(zq.prototype.Lf)}});l.Cg=Object({"http://www.opengis.net/gml":{Point:kq(zq.prototype.Hd)}});l.vg=Object({"http://www.opengis.net/gml":{LineString:kq(zq.prototype.Jd)}});l.Dg=Object({"http://www.opengis.net/gml":{Polygon:kq(zq.prototype.Kd)}});l.Tc=Object({"http://www.opengis.net/gml":{LinearRing:lq(zq.prototype.Ik)}});
+l.Ob=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};m(c)&&Eb(d,vp(this,b,c));return this.pe(b,[d])};l.tc=function(b){return Ee(m(this.n)?this.n:b.firstElementChild.getAttribute("srsName"))};function Aq(b){b=Pp(b,!1);return Bq(b)}function Bq(b){if(b=/^\s*(true|1)|(false|0)\s*$/.exec(b))return m(b[1])||!1}function Cq(b){b=Pp(b,!1);if(b=/^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/.exec(b)){var c=Date.UTC(parseInt(b[1],10),parseInt(b[2],10)-1,parseInt(b[3],10),parseInt(b[4],10),parseInt(b[5],10),parseInt(b[6],10))/1E3;if("Z"!=b[7]){var d="-"==b[8]?-1:1,c=c+60*d*parseInt(b[9],10);m(b[10])&&(c+=3600*d*parseInt(b[10],10))}return c}}
+function Dq(b){b=Pp(b,!1);return Eq(b)}function Eq(b){if(b=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(b))return parseFloat(b[1])}function Fq(b){b=Pp(b,!1);return Gq(b)}function Gq(b){if(b=/^\s*(\d+)\s*$/.exec(b))return parseInt(b[1],10)}function Hq(b){b=Pp(b,!1);return Aa(b)}function Iq(b,c){Jq(b,c?"1":"0")}function Kq(b,c){b.appendChild(Lp.createTextNode(c.toPrecision()))}function Lq(b,c){b.appendChild(Lp.createTextNode(c.toString()))}function Jq(b,c){b.appendChild(Lp.createTextNode(c))};function X(b){b=m(b)?b:{};zq.call(this,b);this.g=m(b.surface)?b.surface:!1;this.d=m(b.curve)?b.curve:!1;this.e=m(b.multiCurve)?b.multiCurve:!0;this.f=m(b.multiSurface)?b.multiSurface:!0;this.schemaLocation=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd"}u(X,zq);l=X.prototype;l.Lk=function(b,c){var d=W([],this.wg,b,c,this);if(m(d)){var e=new rn(null);tn(e,d);return e}};
+l.Mk=function(b,c){var d=W([],this.Ag,b,c,this);if(m(d)){var e=new vn(null);xn(e,d);return e}};l.We=function(b,c){uq(this.sg,b,c,this)};l.fg=function(b,c){uq(this.Gg,b,c,this)};l.Ok=function(b,c){return W([null],this.Bg,b,c,this)};l.Qk=function(b,c){return W([null],this.Fg,b,c,this)};l.Pk=function(b,c){return W([null],this.Ud,b,c,this)};l.Kk=function(b,c){return W([null],this.Sc,b,c,this)};l.vi=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&c[c.length-1].push(d)};
+l.ah=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&(c[c.length-1][0]=d)};l.Wf=function(b,c){var d=W([null],this.Hg,b,c,this);if(m(d)&&null!==d[0]){var e=new H(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)db(f,d[h]),g.push(f.length);wl(e,"XYZ",f,g);return e}};l.Nf=function(b,c){var d=W([null],this.tg,b,c,this);if(m(d)){var e=new M(null);qn(e,"XYZ",d);return e}};l.Hk=function(b,c){var d=W([null],this.ug,b,c,this);return Xd(d[1][0],d[1][1],d[2][0],d[2][1])};
+l.Jk=function(b,c){for(var d=Pp(b,!1),e=/^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/,f=[],g;g=e.exec(d);)f.push(parseFloat(g[1])),d=d.substr(g[0].length);if(""===d){d=x(c[0],"srsName");e="enu";null===d||(e=Ce(Ee(d)));if("neu"===e)for(d=0,e=f.length;d<e;d+=3)g=f[d],f[d]=f[d+1],f[d+1]=g;d=f.length;2==d&&f.push(0);return 0===d?void 0:f}};
+l.Ae=function(b,c){var d=Pp(b,!1).replace(/^\s*|\s*$/g,""),e=x(c[0],"srsName"),f=b.parentNode.getAttribute("srsDimension"),g="enu";null===e||(g=Ce(Ee(e)));d=d.split(/\s+/);e=2;fa(b.getAttribute("srsDimension"))?fa(b.getAttribute("dimension"))?null===f||(e=Gq(f)):e=Gq(b.getAttribute("dimension")):e=Gq(b.getAttribute("srsDimension"));for(var h,k,n=[],p=0,q=d.length;p<q;p+=e)f=parseFloat(d[p]),h=parseFloat(d[p+1]),k=3===e?parseFloat(d[p+2]):0,"en"===g.substr(0,2)?n.push(f,h,k):n.push(h,f,k);return n};
+l.Sc=Object({"http://www.opengis.net/gml":{pos:lq(X.prototype.Jk),posList:lq(X.prototype.Ae)}});l.Ud=Object({"http://www.opengis.net/gml":{interior:X.prototype.vi,exterior:X.prototype.ah}});
+l.Le=Object({"http://www.opengis.net/gml":{Point:lq(zq.prototype.Vf),MultiPoint:lq(zq.prototype.Tf),LineString:lq(zq.prototype.Jd),MultiLineString:lq(zq.prototype.Sf),LinearRing:lq(zq.prototype.Rf),Polygon:lq(zq.prototype.Kd),MultiPolygon:lq(zq.prototype.Uf),Surface:lq(X.prototype.Wf),MultiSurface:lq(X.prototype.Mk),Curve:lq(X.prototype.Nf),MultiCurve:lq(X.prototype.Lk),Envelope:lq(X.prototype.Hk)}});l.wg=Object({"http://www.opengis.net/gml":{curveMember:kq(X.prototype.We),curveMembers:kq(X.prototype.We)}});
+l.Ag=Object({"http://www.opengis.net/gml":{surfaceMember:kq(X.prototype.fg),surfaceMembers:kq(X.prototype.fg)}});l.sg=Object({"http://www.opengis.net/gml":{LineString:kq(zq.prototype.Jd),Curve:kq(X.prototype.Nf)}});l.Gg=Object({"http://www.opengis.net/gml":{Polygon:kq(zq.prototype.Kd),Surface:kq(X.prototype.Wf)}});l.Hg=Object({"http://www.opengis.net/gml":{patches:lq(X.prototype.Ok)}});l.tg=Object({"http://www.opengis.net/gml":{segments:lq(X.prototype.Qk)}});
+l.ug=Object({"http://www.opengis.net/gml":{lowerCorner:kq(X.prototype.Ae),upperCorner:kq(X.prototype.Ae)}});l.Bg=Object({"http://www.opengis.net/gml":{PolygonPatch:lq(X.prototype.Pk)}});l.Fg=Object({"http://www.opengis.net/gml":{LineStringSegment:lq(X.prototype.Kk)}});function Mq(b,c,d){d=x(d[d.length-1],"srsName");c=c.K();for(var e=c.length,f=Array(e),g,h=0;h<e;++h){g=c[h];var k=h,n="enu";null!=d&&(n=Ce(Ee(d)));f[k]="en"===n.substr(0,2)?g[0]+" "+g[1]:g[1]+" "+g[0]}Jq(b,f.join(" "))}
+l.og=function(b,c,d){var e=x(d[d.length-1],"srsName");null!=e&&b.setAttribute("srsName",e);e=Op(b.namespaceURI,"pos");b.appendChild(e);d=x(d[d.length-1],"srsName");b="enu";null!=d&&(b=Ce(Ee(d)));c=c.K();Jq(e,"en"===b.substr(0,2)?c[0]+" "+c[1]:c[1]+" "+c[0])};var Nq={"http://www.opengis.net/gml":{lowerCorner:V(Jq),upperCorner:V(Jq)}};l=X.prototype;
+l.zl=function(b,c,d){var e=x(d[d.length-1],"srsName");m(e)&&b.setAttribute("srsName",e);vq({node:b},Nq,sq,[c[0]+" "+c[1],c[2]+" "+c[3]],d,["lowerCorner","upperCorner"],this)};l.lg=function(b,c,d){var e=x(d[d.length-1],"srsName");null!=e&&b.setAttribute("srsName",e);e=Op(b.namespaceURI,"posList");b.appendChild(e);Mq(e,c,d)};l.Eg=function(b,c){var d=c[c.length-1],e=d.node,f=x(d,"exteriorWritten");m(f)||(d.exteriorWritten=!0);return Op(e.namespaceURI,m(f)?"interior":"exterior")};
+l.Sd=function(b,c,d){var e=x(d[d.length-1],"srsName");"PolygonPatch"!==b.nodeName&&null!=e&&b.setAttribute("srsName",e);"Polygon"===b.nodeName||"PolygonPatch"===b.nodeName?(c=c.dd(),vq({node:b,srsName:e},Oq,this.Eg,c,d,void 0,this)):"Surface"===b.nodeName&&(e=Op(b.namespaceURI,"patches"),b.appendChild(e),b=Op(e.namespaceURI,"PolygonPatch"),e.appendChild(b),this.Sd(b,c,d))};
+l.Od=function(b,c,d){var e=x(d[d.length-1],"srsName");"LineStringSegment"!==b.nodeName&&null!=e&&b.setAttribute("srsName",e);"LineString"===b.nodeName||"LineStringSegment"===b.nodeName?(e=Op(b.namespaceURI,"posList"),b.appendChild(e),Mq(e,c,d)):"Curve"===b.nodeName&&(e=Op(b.namespaceURI,"segments"),b.appendChild(e),b=Op(e.namespaceURI,"LineStringSegment"),e.appendChild(b),this.Od(b,c,d))};
+l.ng=function(b,c,d){var e=d[d.length-1],f=x(e,"srsName"),e=x(e,"surface");null!=f&&b.setAttribute("srsName",f);c=c.gd();vq({node:b,srsName:f,surface:e},Pq,this.b,c,d,void 0,this)};l.Dl=function(b,c,d){var e=x(d[d.length-1],"srsName");null!=e&&b.setAttribute("srsName",e);c=c.xd();vq({node:b,srsName:e},Qq,qq("pointMember"),c,d,void 0,this)};
+l.mg=function(b,c,d){var e=d[d.length-1],f=x(e,"srsName"),e=x(e,"curve");null!=f&&b.setAttribute("srsName",f);c=c.Ec();vq({node:b,srsName:f,curve:e},Rq,this.b,c,d,void 0,this)};l.pg=function(b,c,d){var e=Op(b.namespaceURI,"LinearRing");b.appendChild(e);this.lg(e,c,d)};l.qg=function(b,c,d){var e=this.c(c,d);m(e)&&(b.appendChild(e),this.Sd(e,c,d))};l.Gl=function(b,c,d){var e=Op(b.namespaceURI,"Point");b.appendChild(e);this.og(e,c,d)};
+l.kg=function(b,c,d){var e=this.c(c,d);m(e)&&(b.appendChild(e),this.Od(e,c,d))};l.Rd=function(b,c,d){var e=d[d.length-1],f=Cb(e);f.node=b;var g;ga(c)?m(e.dataProjection)?g=Xe(c,e.featureProjection,e.dataProjection):g=c:g=xp(c,!0,e);vq(f,Sq,this.c,[g],d,void 0,this)};
+l.hg=function(b,c,d){var e=c.X;m(e)&&b.setAttribute("fid",e);var e=d[d.length-1],f=x(e,"featureNS"),g=c.b;m(e.ac)||(e.ac={},e.ac[f]={});var h=c.L();c=[];var k=[],n;for(n in h){var p=h[n];null!==p&&(c.push(n),k.push(p),n==g?n in e.ac[f]||(e.ac[f][n]=V(this.Rd,this)):n in e.ac[f]||(e.ac[f][n]=V(Jq)))}n=Cb(e);n.node=b;vq(n,e.ac,qq(void 0,f),k,d,c)};
+var Pq={"http://www.opengis.net/gml":{surfaceMember:V(X.prototype.qg),polygonMember:V(X.prototype.qg)}},Qq={"http://www.opengis.net/gml":{pointMember:V(X.prototype.Gl)}},Rq={"http://www.opengis.net/gml":{lineStringMember:V(X.prototype.kg),curveMember:V(X.prototype.kg)}},Oq={"http://www.opengis.net/gml":{exterior:V(X.prototype.pg),interior:V(X.prototype.pg)}},Sq={"http://www.opengis.net/gml":{Curve:V(X.prototype.Od),MultiCurve:V(X.prototype.mg),Point:V(X.prototype.og),MultiPoint:V(X.prototype.Dl),
+LineString:V(X.prototype.Od),MultiLineString:V(X.prototype.mg),LinearRing:V(X.prototype.lg),Polygon:V(X.prototype.Sd),MultiPolygon:V(X.prototype.ng),Surface:V(X.prototype.Sd),MultiSurface:V(X.prototype.ng),Envelope:V(X.prototype.zl)}},Tq={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};X.prototype.b=function(b,c){return Op("http://www.opengis.net/gml",Tq[c[c.length-1].node.nodeName])};
+X.prototype.c=function(b,c){var d=c[c.length-1],e=x(d,"multiSurface"),f=x(d,"surface"),g=x(d,"curve"),d=x(d,"multiCurve"),h;ga(b)?h="Envelope":(h=b.H(),"MultiPolygon"===h&&!0===e?h="MultiSurface":"Polygon"===h&&!0===f?h="Surface":"LineString"===h&&!0===g?h="Curve":"MultiLineString"===h&&!0===d&&(h="MultiCurve"));return Op("http://www.opengis.net/gml",h)};
+X.prototype.o=function(b,c){c=wp(this,c);var d=Op("http://www.opengis.net/gml","geom"),e={node:d,srsName:this.srsName,curve:this.d,surface:this.g,multiSurface:this.f,multiCurve:this.e};m(c)&&Eb(e,c);this.Rd(d,b,[e]);return d};
+X.prototype.a=function(b,c){c=wp(this,c);var d=Op("http://www.opengis.net/gml","featureMembers");hq(d,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.schemaLocation);var e={srsName:this.srsName,curve:this.d,surface:this.g,multiSurface:this.f,multiCurve:this.e,featureNS:this.featureNS,featureType:this.featureType};m(c)&&Eb(e,c);var e=[e],f=e[e.length-1],g=x(f,"featureType"),h=x(f,"featureNS"),k={};k[h]={};k[h][g]=V(this.hg,this);f=Cb(f);f.node=d;vq(f,k,qq(g,h),b,e);return d};function Uq(b){b=m(b)?b:{};zq.call(this,b);this.schemaLocation=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd"}u(Uq,zq);l=Uq.prototype;
+l.Qf=function(b,c){var d=Pp(b,!1).replace(/^\s*|\s*$/g,""),e=x(c[0],"srsName"),f=b.parentNode.getAttribute("srsDimension"),g="enu";null===e||(g=Ce(Ee(e)));d=d.split(/[\s,]+/);e=2;fa(b.getAttribute("srsDimension"))?fa(b.getAttribute("dimension"))?null===f||(e=Gq(f)):e=Gq(b.getAttribute("dimension")):e=Gq(b.getAttribute("srsDimension"));for(var h,k,n=[],p=0,q=d.length;p<q;p+=e)f=parseFloat(d[p]),h=parseFloat(d[p+1]),k=3===e?parseFloat(d[p+2]):0,"en"===g.substr(0,2)?n.push(f,h,k):n.push(h,f,k);return n};
+l.Gk=function(b,c){var d=W([null],this.rg,b,c,this);return Xd(d[1][0],d[1][1],d[1][3],d[1][4])};l.ti=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&c[c.length-1].push(d)};l.tk=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&(c[c.length-1][0]=d)};l.Sc=Object({"http://www.opengis.net/gml":{coordinates:lq(Uq.prototype.Qf)}});l.Ud=Object({"http://www.opengis.net/gml":{innerBoundaryIs:Uq.prototype.ti,outerBoundaryIs:Uq.prototype.tk}});l.rg=Object({"http://www.opengis.net/gml":{coordinates:kq(Uq.prototype.Qf)}});
+l.Le=Object({"http://www.opengis.net/gml":{Point:lq(zq.prototype.Vf),MultiPoint:lq(zq.prototype.Tf),LineString:lq(zq.prototype.Jd),MultiLineString:lq(zq.prototype.Sf),LinearRing:lq(zq.prototype.Rf),Polygon:lq(zq.prototype.Kd),MultiPolygon:lq(zq.prototype.Uf),Box:lq(Uq.prototype.Gk)}});function Vq(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.c=b.readExtensions}u(Vq,wq);var Wq=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function Xq(b,c,d){b.push(parseFloat(c.getAttribute("lon")),parseFloat(c.getAttribute("lat")));"ele"in d?(b.push(x(d,"ele")),zb(d,"ele")):b.push(0);"time"in d?(b.push(x(d,"time")),zb(d,"time")):b.push(0);return b}
+function Yq(b,c){var d=c[c.length-1],e=b.getAttribute("href");null!==e&&(d.link=e);uq(Zq,b,c)}function $q(b,c){c[c.length-1].extensionsNode_=b}function ar(b,c){var d=c[0],e=W({flatCoordinates:[]},br,b,c);if(m(e)){var f=x(e,"flatCoordinates");zb(e,"flatCoordinates");var g=new M(null);qn(g,"XYZM",f);xp(g,!1,d);d=new R(g);d.G(e);return d}}
+function cr(b,c){var d=c[0],e=W({flatCoordinates:[],ends:[]},dr,b,c);if(m(e)){var f=x(e,"flatCoordinates");zb(e,"flatCoordinates");var g=x(e,"ends");zb(e,"ends");var h=new rn(null);sn(h,"XYZM",f,g);xp(h,!1,d);d=new R(h);d.G(e);return d}}function er(b,c){var d=c[0],e=W({},fr,b,c);if(m(e)){var f=Xq([],b,e),f=new jl(f,"XYZM");xp(f,!1,d);d=new R(f);d.G(e);return d}}
+var gr={rte:ar,trk:cr,wpt:er},hr=nq(Wq,{rte:kq(ar),trk:kq(cr),wpt:kq(er)}),Zq=nq(Wq,{text:S(Hq,"linkText"),type:S(Hq,"linkType")}),br=nq(Wq,{name:S(Hq),cmt:S(Hq),desc:S(Hq),src:S(Hq),link:Yq,number:S(Fq),extensions:$q,type:S(Hq),rtept:function(b,c){var d=W({},ir,b,c);m(d)&&Xq(x(c[c.length-1],"flatCoordinates"),b,d)}}),ir=nq(Wq,{ele:S(Dq),time:S(Cq)}),dr=nq(Wq,{name:S(Hq),cmt:S(Hq),desc:S(Hq),src:S(Hq),link:Yq,number:S(Fq),type:S(Hq),extensions:$q,trkseg:function(b,c){var d=c[c.length-1];uq(jr,b,c);
+x(d,"ends").push(x(d,"flatCoordinates").length)}}),jr=nq(Wq,{trkpt:function(b,c){var d=W({},kr,b,c);m(d)&&Xq(x(c[c.length-1],"flatCoordinates"),b,d)}}),kr=nq(Wq,{ele:S(Dq),time:S(Cq)}),fr=nq(Wq,{ele:S(Dq),time:S(Cq),magvar:S(Dq),geoidheight:S(Dq),name:S(Hq),cmt:S(Hq),desc:S(Hq),src:S(Hq),link:Yq,sym:S(Hq),type:S(Hq),fix:S(Hq),sat:S(Fq),hdop:S(Dq),vdop:S(Dq),pdop:S(Dq),ageofdgpsdata:S(Dq),dgpsid:S(Fq),extensions:$q});
+function lr(b,c){null===c&&(c=[]);for(var d=0,e=c.length;d<e;++d){var f=c[d];if(m(b.c)){var g=f.get("extensionsNode_")||null;b.c(f,g)}f.set("extensionsNode_",void 0)}}Vq.prototype.Pf=function(b,c){if(!Za(Wq,b.namespaceURI))return null;var d=gr[b.localName];if(!m(d))return null;d=d(b,[vp(this,b,c)]);if(!m(d))return null;lr(this,[d]);return d};Vq.prototype.Ob=function(b,c){if(!Za(Wq,b.namespaceURI))return[];if("gpx"==b.localName){var d=W([],hr,b,[vp(this,b,c)]);if(m(d))return lr(this,d),d}return[]};
+Vq.prototype.Lc=function(){return this.defaultDataProjection};Vq.prototype.tc=function(){return this.defaultDataProjection};function mr(b,c,d){b.setAttribute("href",c);c=x(d[d.length-1],"properties");vq({node:b},nr,sq,[x(c,"linkText"),x(c,"linkType")],d,or)}
+function pr(b,c,d){var e=d[d.length-1],f=e.node.namespaceURI,g=x(e,"properties");hq(b,null,"lat",c[1]);hq(b,null,"lon",c[0]);switch(x(e,"geometryLayout")){case "XYZM":0!==c[3]&&(g.time=c[3]);case "XYZ":0!==c[2]&&(g.ele=c[2]);break;case "XYM":0!==c[2]&&(g.time=c[2])}c=qr[f];e=tq(g,c);vq({node:b,properties:g},rr,sq,e,d,c)}
+var or=["text","type"],nr=oq(Wq,{text:V(Jq),type:V(Jq)}),sr=oq(Wq,"name cmt desc src link number type rtept".split(" ")),tr=oq(Wq,{name:V(Jq),cmt:V(Jq),desc:V(Jq),src:V(Jq),link:V(mr),number:V(Lq),type:V(Jq),rtept:pq(V(pr))}),ur=oq(Wq,"name cmt desc src link number type trkseg".split(" ")),xr=oq(Wq,{name:V(Jq),cmt:V(Jq),desc:V(Jq),src:V(Jq),link:V(mr),number:V(Lq),type:V(Jq),trkseg:pq(V(function(b,c,d){vq({node:b,geometryLayout:c.a,properties:{}},vr,wr,c.K(),d)}))}),wr=qq("trkpt"),vr=oq(Wq,{trkpt:V(pr)}),
+qr=oq(Wq,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),rr=oq(Wq,{ele:V(Kq),time:V(function(b,c){var d=new Date(1E3*c),d=d.getUTCFullYear()+"-"+Ma(d.getUTCMonth()+1)+"-"+Ma(d.getUTCDate())+"T"+Ma(d.getUTCHours())+":"+Ma(d.getUTCMinutes())+":"+Ma(d.getUTCSeconds())+"Z";b.appendChild(Lp.createTextNode(d))}),magvar:V(Kq),geoidheight:V(Kq),name:V(Jq),cmt:V(Jq),desc:V(Jq),src:V(Jq),link:V(mr),sym:V(Jq),type:V(Jq),fix:V(Jq),sat:V(Lq),
+hdop:V(Kq),vdop:V(Kq),pdop:V(Kq),ageofdgpsdata:V(Kq),dgpsid:V(Lq)}),yr={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function zr(b,c){var d=b.N();if(m(d))return Op(c[c.length-1].node.namespaceURI,yr[d.H()])}
+var Ar=oq(Wq,{rte:V(function(b,c,d){var e=d[0],f=c.L();b={node:b,properties:f};c=c.N();m(c)&&(c=xp(c,!0,e),b.geometryLayout=c.a,e=c.K(),f.rtept=e);e=sr[d[d.length-1].node.namespaceURI];f=tq(f,e);vq(b,tr,sq,f,d,e)}),trk:V(function(b,c,d){var e=d[0],f=c.L();b={node:b,properties:f};c=c.N();m(c)&&(c=xp(c,!0,e),e=c.Ec(),f.trkseg=e);e=ur[d[d.length-1].node.namespaceURI];f=tq(f,e);vq(b,xr,sq,f,d,e)}),wpt:V(function(b,c,d){var e=d[0],f=d[d.length-1],g=c.L();f.properties=g;c=c.N();m(c)&&(c=xp(c,!0,e),f.geometryLayout=
+c.a,pr(b,c.K(),d))})});Vq.prototype.a=function(b,c){c=wp(this,c);var d=Op("http://www.topografix.com/GPX/1/1","gpx");vq({node:d},Ar,zr,b,[c]);return d};function Br(b){b=Cr(b);return Va(b,function(b){return b.b.substring(b.c,b.a)})}function Dr(b,c,d){this.b=b;this.c=c;this.a=d}function Cr(b){for(var c=RegExp("\r\n|\r|\n","g"),d=0,e,f=[];e=c.exec(b);)d=new Dr(b,d,e.index),f.push(d),d=c.lastIndex;d<b.length&&(d=new Dr(b,d,b.length),f.push(d));return f};function Er(){this.defaultDataProjection=null}u(Er,up);l=Er.prototype;l.H=function(){return"text"};l.Nb=function(b,c){return this.Ic(ia(b)?b:"",wp(this,c))};l.ja=function(b,c){return this.ze(ia(b)?b:"",wp(this,c))};l.Jc=function(b,c){return this.Kc(ia(b)?b:"",wp(this,c))};l.Ba=function(b){return this.Ce(ia(b)?b:"")};l.Pd=function(b,c){return this.Qd(b,wp(this,c))};l.Qb=function(b,c){return this.ig(b,wp(this,c))};l.Qc=function(b,c){return this.Rc(b,wp(this,c))};function Fr(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.a=m(b.altitudeMode)?b.altitudeMode:"none"}u(Fr,Er);var Gr=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,Hr=/^H.([A-Z]{3}).*?:(.*)/,Ir=/^HFDTE(\d{2})(\d{2})(\d{2})/;
+Fr.prototype.Ic=function(b,c){var d=this.a,e=Br(b),f={},g=[],h=2E3,k=0,n=1,p,q;p=0;for(q=e.length;p<q;++p){var r=e[p],s;if("B"==r.charAt(0)){if(s=Gr.exec(r)){var r=parseInt(s[1],10),v=parseInt(s[2],10),y=parseInt(s[3],10),C=parseInt(s[4],10)+parseInt(s[5],10)/6E4;"S"==s[6]&&(C=-C);var F=parseInt(s[7],10)+parseInt(s[8],10)/6E4;"W"==s[9]&&(F=-F);g.push(F,C);"none"!=d&&g.push("gps"==d?parseInt(s[11],10):"barometric"==d?parseInt(s[12],10):0);g.push(Date.UTC(h,k,n,r,v,y)/1E3)}}else if("H"==r.charAt(0))if(s=
+Ir.exec(r))n=parseInt(s[1],10),k=parseInt(s[2],10)-1,h=2E3+parseInt(s[3],10);else if(s=Hr.exec(r))f[s[1]]=Aa(s[2]),Ir.exec(r)}if(0===g.length)return null;e=new M(null);qn(e,"none"==d?"XYM":"XYZM",g);d=new R(xp(e,!1,c));d.G(f);return d};Fr.prototype.ze=function(b,c){var d=this.Ic(b,c);return null===d?[]:[d]};Fr.prototype.Ce=function(){return this.defaultDataProjection};function Jr(b){b=m(b)?b:{};this.d=b.font;this.e=b.rotation;this.c=b.scale;this.b=b.text;this.g=b.textAlign;this.o=b.textBaseline;this.a=m(b.fill)?b.fill:null;this.f=m(b.stroke)?b.stroke:null;this.i=m(b.offsetX)?b.offsetX:0;this.n=m(b.offsetY)?b.offsetY:0}l=Jr.prototype;l.kh=function(){return this.d};l.yh=function(){return this.i};l.zh=function(){return this.n};l.dk=function(){return this.a};l.ek=function(){return this.e};l.fk=function(){return this.c};l.gk=function(){return this.f};l.hk=function(){return this.b};
+l.Hh=function(){return this.g};l.Ih=function(){return this.o};l.dl=function(b){this.d=b};l.cl=function(b){this.a=b};l.ik=function(b){this.e=b};l.jk=function(b){this.c=b};l.kl=function(b){this.f=b};l.ll=function(b){this.b=b};l.ml=function(b){this.g=b};l.nl=function(b){this.o=b};function Kr(b){function c(b){return ga(b)?b:ia(b)?(!(b in e)&&"#"+b in e&&(b="#"+b),c(e[b])):d}b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");var d=m(b.defaultStyle)?b.defaultStyle:Lr,e={};this.b=m(b.extractStyles)?b.extractStyles:!0;this.c=e;this.d=function(){var b=this.get("Style");if(m(b))return b;b=this.get("styleUrl");return m(b)?c(b):d}}u(Kr,wq);
+var Mr=["http://www.google.com/kml/ext/2.2"],Nr=[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"],Or=[255,255,255,1],Pr=new Rl({color:Or}),Qr=[2,20],Rr=[32,32],Sr=new Sj({anchor:Qr,anchorXUnits:"pixels",anchorYUnits:"pixels",crossOrigin:"anonymous",rotation:0,scale:1,size:Rr,src:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"}),Tr=new Nl({color:Or,width:1}),Ur=new Jr({font:"normal 16px Helvetica",
+fill:Pr,stroke:Tr,scale:1}),Lr=[new Tl({fill:Pr,image:Sr,text:Ur,stroke:Tr,zIndex:0})],Vr={fraction:"fraction",pixels:"pixels"};function Wr(b){b=Pp(b,!1);if(b=/^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(b))return b=b[1],[parseInt(b.substr(6,2),16),parseInt(b.substr(4,2),16),parseInt(b.substr(2,2),16),parseInt(b.substr(0,2),16)/255]}
+function Xr(b){b=Pp(b,!1);for(var c=[],d=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i,e;e=d.exec(b);)c.push(parseFloat(e[1]),parseFloat(e[2]),e[3]?parseFloat(e[3]):0),b=b.substr(e[0].length);return""!==b?void 0:c}function Yr(b){var c=Pp(b,!1);return null!=b.baseURI?Ph(b.baseURI,Aa(c)).toString():Aa(c)}function Zr(b){b=Dq(b);if(m(b))return Math.sqrt(b)}function $r(b,c){return W(null,as,b,c)}
+function bs(b,c){var d=W({k:[],gg:[]},cs,b,c);if(m(d)){var e=d.k,d=d.gg,f,g;f=0;for(g=Math.min(e.length,d.length);f<g;++f)e[4*f+3]=d[f];d=new M(null);qn(d,"XYZM",e);return d}}function ds(b,c){var d=W(null,es,b,c);if(m(d)){var e=new M(null);qn(e,"XYZ",d);return e}}function fs(b,c){var d=W(null,es,b,c);if(m(d)){var e=new H(null);wl(e,"XYZ",d,[d.length]);return e}}
+function gs(b,c){var d=W([],hs,b,c);if(!m(d))return null;if(0===d.length)return new jn(d);var e=!0,f=d[0].H(),g,h,k;h=1;for(k=d.length;h<k;++h)if(g=d[h],g.H()!=f){e=!1;break}if(e){if("Point"==f){g=d[0];e=g.a;f=g.k;h=1;for(k=d.length;h<k;++h)g=d[h],db(f,g.k);d=new un(null);Qk(d,e,f);d.l();return d}return"LineString"==f?(g=new rn(null),tn(g,d),g):"Polygon"==f?(g=new vn(null),xn(g,d),g):"GeometryCollection"==f?new jn(d):null}return new jn(d)}
+function is(b,c){var d=W(null,es,b,c);if(null!=d){var e=new jl(null);kl(e,"XYZ",d);return e}}function js(b,c){var d=W([null],ks,b,c);if(null!=d&&null!==d[0]){var e=new H(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)db(f,d[h]),g.push(f.length);wl(e,"XYZ",f,g);return e}}
+function ls(b,c){var d=W({},ms,b,c);if(!m(d))return null;var e=x(d,"fillStyle",Pr),f=x(d,"fill");m(f)&&!f&&(e=null);var f=x(d,"imageStyle",Sr),g=x(d,"textStyle",Ur),h=x(d,"strokeStyle",Tr),d=x(d,"outline");m(d)&&!d&&(h=null);return[new Tl({fill:e,image:f,stroke:h,text:g,zIndex:void 0})]}
+var ns=nq(Nr,{value:lq(Hq)}),ps=nq(Nr,{Data:function(b,c){var d=b.getAttribute("name");if(null!==d){var e=W(void 0,ns,b,c);m(e)&&(c[c.length-1][d]=e)}},SchemaData:function(b,c){uq(os,b,c)}}),as=nq(Nr,{coordinates:lq(Xr)}),ks=nq(Nr,{innerBoundaryIs:function(b,c){var d=W(void 0,qs,b,c);m(d)&&c[c.length-1].push(d)},outerBoundaryIs:function(b,c){var d=W(void 0,rs,b,c);m(d)&&(c[c.length-1][0]=d)}}),cs=nq(Nr,{when:function(b,c){var d=c[c.length-1].gg,e=Pp(b,!1);if(e=/^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/.exec(e)){var f=
+Date.UTC(parseInt(e[1],10),m(e[3])?parseInt(e[3],10)-1:0,m(e[5])?parseInt(e[5],10):1,m(e[7])?parseInt(e[7],10):0,m(e[8])?parseInt(e[8],10):0,m(e[9])?parseInt(e[9],10):0);if(m(e[10])&&"Z"!=e[10]){var g="-"==e[11]?-1:1,f=f+60*g*parseInt(e[12],10);m(e[13])&&(f+=3600*g*parseInt(e[13],10))}d.push(f)}else d.push(0)}},nq(Mr,{coord:function(b,c){var d=c[c.length-1].k,e=Pp(b,!1);(e=/^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i.exec(e))?
+d.push(parseFloat(e[1]),parseFloat(e[2]),parseFloat(e[3]),0):d.push(0,0,0,0)}})),es=nq(Nr,{coordinates:lq(Xr)}),ss=nq(Nr,{href:S(Yr)},nq(Mr,{x:S(Dq),y:S(Dq),w:S(Dq),h:S(Dq)})),ts=nq(Nr,{Icon:S(function(b,c){var d=W({},ss,b,c);return m(d)?d:null}),heading:S(Dq),hotSpot:S(function(b){var c=b.getAttribute("xunits"),d=b.getAttribute("yunits");return{x:parseFloat(b.getAttribute("x")),Je:Vr[c],y:parseFloat(b.getAttribute("y")),Ke:Vr[d]}}),scale:S(Zr)}),qs=nq(Nr,{LinearRing:lq($r)}),us=nq(Nr,{color:S(Wr),
+scale:S(Zr)}),vs=nq(Nr,{color:S(Wr),width:S(Dq)}),hs=nq(Nr,{LineString:kq(ds),LinearRing:kq(fs),MultiGeometry:kq(gs),Point:kq(is),Polygon:kq(js)}),ws=nq(Mr,{Track:kq(bs)}),rs=nq(Nr,{LinearRing:lq($r)}),xs=nq(Nr,{Style:S(ls),key:S(Hq),styleUrl:S(function(b){var c=Aa(Pp(b,!1));return null!=b.baseURI?Ph(b.baseURI,c).toString():c})}),zs=nq(Nr,{ExtendedData:function(b,c){uq(ps,b,c)},MultiGeometry:S(gs,"geometry"),LineString:S(ds,"geometry"),LinearRing:S(fs,"geometry"),Point:S(is,"geometry"),Polygon:S(js,
+"geometry"),Style:S(ls),StyleMap:function(b,c){var d=W(void 0,ys,b,c);if(m(d)){var e=c[c.length-1];ga(d)?e.Style=d:ia(d)&&(e.styleUrl=d)}},address:S(Hq),description:S(Hq),name:S(Hq),open:S(Aq),phoneNumber:S(Hq),styleUrl:S(Yr),visibility:S(Aq)},nq(Mr,{MultiTrack:S(function(b,c){var d=W([],ws,b,c);if(m(d)){var e=new rn(null);tn(e,d);return e}},"geometry"),Track:S(bs,"geometry")})),As=nq(Nr,{color:S(Wr),fill:S(Aq),outline:S(Aq)}),os=nq(Nr,{SimpleData:function(b,c){var d=b.getAttribute("name");if(null!==
+d){var e=Hq(b);c[c.length-1][d]=e}}}),ms=nq(Nr,{IconStyle:function(b,c){var d=W({},ts,b,c);if(m(d)){var e=c[c.length-1],f=x(d,"Icon",{}),g;g=x(f,"href");g=m(g)?g:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";var h,k,n,p=x(d,"hotSpot");m(p)?(h=[p.x,p.y],k=p.Je,n=p.Ke):"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"===g?(h=Qr,n=k="pixels"):/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(g)&&(h=[.5,0],n=k="fraction");var q,p=x(f,"x"),r=x(f,"y");m(p)&&m(r)&&(q=[p,r]);
+var s,p=x(f,"w"),f=x(f,"h");m(p)&&m(f)&&(s=[p,f]);var v,f=x(d,"heading");m(f)&&(v=bc(f));d=x(d,"scale");"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"==g&&(s=Rr);h=new Sj({anchor:h,anchorOrigin:"bottom-left",anchorXUnits:k,anchorYUnits:n,crossOrigin:"anonymous",offset:q,offsetOrigin:"bottom-left",rotation:v,scale:d,size:s,src:g});e.imageStyle=h}},LabelStyle:function(b,c){var d=W({},us,b,c);m(d)&&(c[c.length-1].textStyle=new Jr({fill:new Rl({color:x(d,"color",Or)}),scale:x(d,"scale")}))},
+LineStyle:function(b,c){var d=W({},vs,b,c);m(d)&&(c[c.length-1].strokeStyle=new Nl({color:x(d,"color",Or),width:x(d,"width",1)}))},PolyStyle:function(b,c){var d=W({},As,b,c);if(m(d)){var e=c[c.length-1];e.fillStyle=new Rl({color:x(d,"color",Or)});var f=x(d,"fill");m(f)&&(e.fill=f);d=x(d,"outline");m(d)&&(e.outline=d)}}}),ys=nq(Nr,{Pair:function(b,c){var d=W({},xs,b,c);if(m(d)){var e=x(d,"key");m(e)&&"normal"==e&&(e=x(d,"styleUrl"),m(e)&&(c[c.length-1]=e),d=x(d,"Style"),m(d)&&(c[c.length-1]=d))}}});
+l=Kr.prototype;l.Of=function(b,c){Tp(b);var d=nq(Nr,{Folder:jq(this.Of,this),Placemark:kq(this.Be,this),Style:sa(this.Sk,this),StyleMap:sa(this.Rk,this)}),d=W([],d,b,c,this);if(m(d))return d};l.Be=function(b,c){var d=W({geometry:null},zs,b,c);if(m(d)){var e=new R,f=b.getAttribute("id");null===f||e.d(f);f=c[0];null!=d.geometry&&xp(d.geometry,!1,f);e.G(d);this.b&&e.i(this.d);return e}};
+l.Sk=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=ls(b,c);m(e)&&(d=null!=b.baseURI?Ph(b.baseURI,"#"+d).toString():"#"+d,this.c[d]=e)}};l.Rk=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=W(void 0,ys,b,c);m(e)&&(d=null!=b.baseURI?Ph(b.baseURI,"#"+d).toString():"#"+d,this.c[d]=e)}};l.Pf=function(b,c){if(!Za(Nr,b.namespaceURI))return null;var d=this.Be(b,[vp(this,b,c)]);return m(d)?d:null};
+l.Ob=function(b,c){if(!Za(Nr,b.namespaceURI))return[];var d;d=Tp(b);if("Document"==d||"Folder"==d)return d=this.Of(b,[vp(this,b,c)]),m(d)?d:[];if("Placemark"==d)return d=this.Be(b,[vp(this,b,c)]),m(d)?[d]:[];if("kml"==d){d=[];var e;for(e=b.firstElementChild;null!==e;e=e.nextElementSibling){var f=this.Ob(e,c);m(f)&&db(d,f)}return d}return[]};l.Nk=function(b){if(Wp(b))return Bs(this,b);if(Zp(b))return Cs(this,b);if(ia(b))return b=iq(b),Bs(this,b)};
+function Bs(b,c){var d;for(d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType){var e=Cs(b,d);if(m(e))return e}}function Cs(b,c){var d;for(d=c.firstElementChild;null!==d;d=d.nextElementSibling)if(Za(Nr,d.namespaceURI)&&"name"==d.localName)return Hq(d);for(d=c.firstElementChild;null!==d;d=d.nextElementSibling){var e=Tp(d);if(Za(Nr,d.namespaceURI)&&("Document"==e||"Folder"==e||"Placemark"==e||"kml"==e)&&(e=Cs(b,d),m(e)))return e}}l.Lc=function(){return this.defaultDataProjection};l.tc=function(){return this.defaultDataProjection};
+function Ds(b,c){var d=ug(c),d=[255*(4==d.length?d[3]:1),d[2],d[1],d[0]],e;for(e=0;4>e;++e){var f=parseInt(d[e],10).toString(16);d[e]=1==f.length?"0"+f:f}Jq(b,d.join(""))}function Es(b,c,d){vq({node:b},Fs,Gs,[c],d)}
+function Hs(b,c,d){var e={node:b};null!=c.X&&b.setAttribute("id",c.X);b=c.L();var f=c.a;m(f)&&(f=f.call(c,0),null!==f&&0<f.length&&(b.Style=f[0],f=f[0].c,null!==f&&(b.name=f.b)));f=Is[d[d.length-1].node.namespaceURI];b=tq(b,f);vq(e,Js,sq,b,d,f);b=d[0];c=c.N();null!=c&&(c=xp(c,!0,b));vq(e,Js,Ks,[c],d)}function Ls(b,c,d){var e=c.k;b={node:b};b.layout=c.a;b.stride=c.s;vq(b,Ms,Ns,[e],d)}function Os(b,c,d){c=c.dd();var e=c.shift();b={node:b};vq(b,Ps,Qs,c,d);vq(b,Ps,Rs,[e],d)}
+function Ss(b,c){Kq(b,c*c)}
+var Ts=oq(Nr,["Document","Placemark"]),Ws=oq(Nr,{Document:V(function(b,c,d){vq({node:b},Us,Vs,c,d)}),Placemark:V(Hs)}),Us=oq(Nr,{Placemark:V(Hs)}),Xs={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry"},Ys=oq(Nr,["href"],oq(Mr,["x","y","w","h"])),Zs=oq(Nr,{href:V(Jq)},oq(Mr,{x:V(Kq),y:V(Kq),w:V(Kq),h:V(Kq)})),$s=oq(Nr,["scale","heading","Icon","hotSpot"]),bt=oq(Nr,{Icon:V(function(b,
+c,d){b={node:b};var e=Ys[d[d.length-1].node.namespaceURI],f=tq(c,e);vq(b,Zs,sq,f,d,e);e=Ys[Mr[0]];f=tq(c,e);vq(b,Zs,at,f,d,e)}),heading:V(Kq),hotSpot:V(function(b,c){b.setAttribute("x",c.x);b.setAttribute("y",c.y);b.setAttribute("xunits",c.Je);b.setAttribute("yunits",c.Ke)}),scale:V(Ss)}),ct=oq(Nr,["color","scale"]),dt=oq(Nr,{color:V(Ds),scale:V(Ss)}),et=oq(Nr,["color","width"]),ft=oq(Nr,{color:V(Ds),width:V(Kq)}),Fs=oq(Nr,{LinearRing:V(Ls)}),gt=oq(Nr,{LineString:V(Ls),Point:V(Ls),Polygon:V(Os)}),
+Is=oq(Nr,"name open visibility address phoneNumber description styleUrl Style".split(" ")),Js=oq(Nr,{MultiGeometry:V(function(b,c,d){b={node:b};var e=c.H(),f,g;"MultiPoint"==e?(f=c.xd(),g=ht):"MultiLineString"==e?(f=c.Ec(),g=it):"MultiPolygon"==e&&(f=c.gd(),g=jt);vq(b,gt,g,f,d)}),LineString:V(Ls),LinearRing:V(Ls),Point:V(Ls),Polygon:V(Os),Style:V(function(b,c,d){b={node:b};var e={},f=c.e,g=c.b,h=c.f;c=c.c;null!==h&&(e.IconStyle=h);null!==c&&(e.LabelStyle=c);null!==g&&(e.LineStyle=g);null!==f&&(e.PolyStyle=
+f);c=kt[d[d.length-1].node.namespaceURI];e=tq(e,c);vq(b,lt,sq,e,d,c)}),address:V(Jq),description:V(Jq),name:V(Jq),open:V(Iq),phoneNumber:V(Jq),styleUrl:V(Jq),visibility:V(Iq)}),Ms=oq(Nr,{coordinates:V(function(b,c,d){d=d[d.length-1];var e=x(d,"layout");d=x(d,"stride");var f;"XY"==e||"XYM"==e?f=2:("XYZ"==e||"XYZM"==e)&&(f=3);var g,h=c.length,k="";if(0<h){k+=c[0];for(e=1;e<f;++e)k+=","+c[e];for(g=d;g<h;g+=d)for(k+=" "+c[g],e=1;e<f;++e)k+=","+c[g+e]}Jq(b,k)})}),Ps=oq(Nr,{outerBoundaryIs:V(Es),innerBoundaryIs:V(Es)}),
+mt=oq(Nr,{color:V(Ds)}),kt=oq(Nr,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),lt=oq(Nr,{IconStyle:V(function(b,c,d){b={node:b};var e={},f=c.ab(),g=c.cd(),h={href:c.a.f};if(null!==f){h.w=f[0];h.h=f[1];var k=c.tb(),n=c.zb();null!==n&&null!==g&&0!==n[0]&&n[1]!==f[1]&&(h.x=n[0],h.y=g[1]-(n[1]+f[1]));null!==k&&0!==k[0]&&k[1]!==f[1]&&(e.hotSpot={x:k[0],Je:"pixels",y:f[1]-k[1],Ke:"pixels"})}e.Icon=h;f=c.n;1!==f&&(e.scale=f);c=c.i;0!==c&&(e.heading=c);c=$s[d[d.length-1].node.namespaceURI];e=tq(e,c);
+vq(b,bt,sq,e,d,c)}),LabelStyle:V(function(b,c,d){b={node:b};var e={},f=c.a;null!==f&&(e.color=f.a);c=c.c;m(c)&&1!==c&&(e.scale=c);c=ct[d[d.length-1].node.namespaceURI];e=tq(e,c);vq(b,dt,sq,e,d,c)}),LineStyle:V(function(b,c,d){b={node:b};var e=et[d[d.length-1].node.namespaceURI];c=tq({color:c.a,width:c.c},e);vq(b,ft,sq,c,d,e)}),PolyStyle:V(function(b,c,d){vq({node:b},mt,nt,[c.a],d)})});function at(b,c,d){return Op(Mr[0],"gx:"+d)}
+function Vs(b,c){return Op(c[c.length-1].node.namespaceURI,"Placemark")}function Ks(b,c){if(null!=b)return Op(c[c.length-1].node.namespaceURI,Xs[b.H()])}var nt=qq("color"),Ns=qq("coordinates"),Qs=qq("innerBoundaryIs"),ht=qq("Point"),it=qq("LineString"),Gs=qq("LinearRing"),jt=qq("Polygon"),Rs=qq("outerBoundaryIs");
+Kr.prototype.a=function(b,c){c=wp(this,c);var d=Op(Nr[4],"kml");hq(d,"http://www.w3.org/2000/xmlns/","xmlns:gx",Mr[0]);hq(d,"http://www.w3.org/2000/xmlns/","xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");hq(d,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation","http://www.opengis.net/kml/2.2 https://developers.google.com/kml/schema/kml22gx.xsd");var e={node:d},f={};1<b.length?f.Document=b:1==b.length&&(f.Placemark=b[0]);var g=Ts[d.namespaceURI],f=tq(f,g);vq(e,Ws,sq,f,[c],g);
+return d};function ot(){this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326")}u(ot,wq);function pt(b,c){var d=b.getAttribute("k"),e=b.getAttribute("v");c[c.length-1].Oc[d]=e}
+var qt=[null],rt=nq(qt,{nd:function(b,c){c[c.length-1].pc.push(b.getAttribute("ref"))},tag:pt}),tt=nq(qt,{node:function(b,c){var d=c[0],e=c[c.length-1],f=b.getAttribute("id"),g=[parseFloat(b.getAttribute("lon")),parseFloat(b.getAttribute("lat"))];e.pf[f]=g;var h=W({Oc:{}},st,b,c);xb(h.Oc)||(g=new jl(g),xp(g,!1,d),d=new R(g),d.d(f),d.G(h.Oc),e.features.push(d))},way:function(b,c){for(var d=c[0],e=b.getAttribute("id"),f=W({pc:[],Oc:{}},rt,b,c),g=c[c.length-1],h=[],k=0,n=f.pc.length;k<n;k++)db(h,x(g.pf,
+f.pc[k]));f.pc[0]==f.pc[f.pc.length-1]?(k=new H(null),wl(k,"XY",h,[h.length])):(k=new M(null),qn(k,"XY",h));xp(k,!1,d);d=new R(k);d.d(e);d.G(f.Oc);g.features.push(d)}}),st=nq(qt,{tag:pt});ot.prototype.Ob=function(b,c){var d=vp(this,b,c);return"osm"==b.localName&&(d=W({pf:{},features:[]},tt,b,[d]),m(d.features))?d.features:[]};ot.prototype.Lc=function(){return this.defaultDataProjection};ot.prototype.tc=function(){return this.defaultDataProjection};function ut(b){return b.getAttributeNS("http://www.w3.org/1999/xlink","href")};function vt(){}vt.prototype.a=function(b){return Wp(b)?wt(this,b):Zp(b)?xt(this,b):ia(b)?(b=iq(b),wt(this,b)):null};function yt(b,c,d,e){var f;m(e)?f=m(void 0)?void 0:0:(e=[],f=0);var g,h;for(g=0;g<c;)for(h=b[g++],e[f++]=b[g++],e[f++]=h,h=2;h<d;++h)e[f++]=b[g++];e.length=f};function zt(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.a=m(b.factor)?b.factor:1E5}u(zt,Er);function At(b,c,d){d=m(d)?d:1E5;var e,f=Array(c);for(e=0;e<c;++e)f[e]=0;var g,h;g=0;for(h=b.length;g<h;)for(e=0;e<c;++e,++g){var k=b[g],n=k-f[e];f[e]=k;b[g]=n}return Bt(b,d)}function Ct(b,c,d){var e=m(d)?d:1E5,f=Array(c);for(d=0;d<c;++d)f[d]=0;b=Dt(b,e);var g,e=0;for(g=b.length;e<g;)for(d=0;d<c;++d,++e)f[d]+=b[e],b[e]=f[d];return b}
+function Bt(b,c){var d=m(c)?c:1E5,e,f;e=0;for(f=b.length;e<f;++e)b[e]=Math.round(b[e]*d);d=0;for(e=b.length;d<e;++d)f=b[d],b[d]=0>f?~(f<<1):f<<1;d="";e=0;for(f=b.length;e<f;++e){for(var g=b[e],h=void 0,k="";32<=g;)h=(32|g&31)+63,k+=String.fromCharCode(h),g>>=5;h=g+63;k+=String.fromCharCode(h);d+=k}return d}
+function Dt(b,c){var d=m(c)?c:1E5,e=[],f=0,g=0,h,k;h=0;for(k=b.length;h<k;++h){var n=b.charCodeAt(h)-63,f=f|(n&31)<<g;32>n?(e.push(f),g=f=0):g+=5}f=0;for(g=e.length;f<g;++f)h=e[f],e[f]=h&1?~(h>>1):h>>1;f=0;for(g=e.length;f<g;++f)e[f]/=d;return e}l=zt.prototype;l.Ic=function(b,c){var d=this.Kc(b,c);return new R(d)};l.ze=function(b,c){return[this.Ic(b,c)]};l.Kc=function(b,c){var d=Ct(b,2,this.a);yt(d,d.length,2,d);d=dl(d,0,d.length,2);return xp(new M(d),!1,wp(this,c))};l.Ce=function(){return this.defaultDataProjection};
+l.Qd=function(b,c){var d=b.N();return null!=d?this.Rc(d,c):""};l.ig=function(b,c){return this.Qd(b[0],c)};l.Rc=function(b,c){b=xp(b,!0,wp(this,c));var d=b.k,e=b.s;yt(d,d.length,e,d);return At(d,e,this.a)};function Et(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee(null!=b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326")}u(Et,Ap);function Ft(b,c){var d=[],e,f,g,h;g=0;for(h=b.length;g<h;++g)e=b[g],0<g&&d.pop(),0<=e?f=c[e]:f=c[~e].slice().reverse(),d.push.apply(d,f);e=0;for(f=d.length;e<f;++e)d[e]=d[e].slice();return d}function Gt(b,c,d,e,f){b=b.geometries;var g=[],h,k;h=0;for(k=b.length;h<k;++h)g[h]=Ht(b[h],c,d,e,f);return g}
+function Ht(b,c,d,e,f){var g=b.type,h=It[g];c="Point"===g||"MultiPoint"===g?h(b,d,e):h(b,c);d=new R;d.Ma(xp(c,!1,f));m(b.id)&&d.d(b.id);m(b.properties)&&d.G(b.properties);return d}
+Et.prototype.b=function(b,c){if("Topology"==b.type){var d,e=null,f=null;m(b.transform)&&(d=b.transform,e=d.scale,f=d.translate);var g=b.arcs;if(m(d)){d=e;var h=f,k,n;k=0;for(n=g.length;k<n;++k)for(var p=g[k],q=d,r=h,s=0,v=0,y=void 0,C=void 0,F=void 0,C=0,F=p.length;C<F;++C)y=p[C],s+=y[0],v+=y[1],y[0]=s,y[1]=v,Jt(y,q,r)}d=[];h=rb(b.objects);k=0;for(n=h.length;k<n;++k)"GeometryCollection"===h[k].type?(p=h[k],d.push.apply(d,Gt(p,g,e,f,c))):(p=h[k],d.push(Ht(p,g,e,f,c)));return d}return[]};
+function Jt(b,c,d){b[0]=b[0]*c[0]+d[0];b[1]=b[1]*c[1]+d[1]}Et.prototype.Ba=function(){return this.defaultDataProjection};
+var It={Point:function(b,c,d){b=b.coordinates;null===c||null===d||Jt(b,c,d);return new jl(b)},LineString:function(b,c){var d=Ft(b.arcs,c);return new M(d)},Polygon:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=Ft(b.arcs[e],c);return new H(d)},MultiPoint:function(b,c,d){b=b.coordinates;var e,f;if(null!==c&&null!==d)for(e=0,f=b.length;e<f;++e)Jt(b[e],c,d);return new un(b)},MultiLineString:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=Ft(b.arcs[e],c);return new rn(d)},
+MultiPolygon:function(b,c){var d=[],e,f,g,h,k,n;k=0;for(n=b.arcs.length;k<n;++k){e=b.arcs[k];f=[];g=0;for(h=e.length;g<h;++g)f[g]=Ft(e[g],c);d[k]=f}return new vn(d)}};function Kt(b){b=m(b)?b:{};this.e=b.featureType;this.b=b.featureNS;this.c=m(b.gmlFormat)?b.gmlFormat:new X;this.d=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";this.defaultDataProjection=null}u(Kt,wq);Kt.prototype.Ob=function(b,c){var d={featureType:this.e,featureNS:this.b};Eb(d,vp(this,b,m(c)?c:{}));d=W([],this.c.Td,b,[d],this.c);m(d)||(d=[]);return d};
+Kt.prototype.g=function(b){if(Wp(b))return Lt(b);if(Zp(b))return W({},Mt,b,[]);if(ia(b))return b=iq(b),Lt(b)};Kt.prototype.f=function(b){if(Wp(b))return Nt(this,b);if(Zp(b))return Ot(this,b);if(ia(b))return b=iq(b),Nt(this,b)};function Nt(b,c){for(var d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType)return Ot(b,d)}var Pt={"http://www.opengis.net/gml":{boundedBy:S(zq.prototype.Id,"bounds")}};
+function Ot(b,c){var d={},e=Gq(c.getAttribute("numberOfFeatures"));d.numberOfFeatures=e;return W(d,Pt,c,[],b.c)}
+var Qt={"http://www.opengis.net/wfs":{totalInserted:S(Fq),totalUpdated:S(Fq),totalDeleted:S(Fq)}},Rt={"http://www.opengis.net/ogc":{FeatureId:kq(function(b){return b.getAttribute("fid")})}},St={"http://www.opengis.net/wfs":{Feature:function(b,c){uq(Rt,b,c)}}},Mt={"http://www.opengis.net/wfs":{TransactionSummary:S(function(b,c){return W({},Qt,b,c)},"transactionSummary"),InsertResults:S(function(b,c){return W([],St,b,c)},"insertIds")}};
+function Lt(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return W({},Mt,b,[])}var Tt={"http://www.opengis.net/wfs":{PropertyName:V(Jq)}};function Ut(b,c){var d=Op("http://www.opengis.net/ogc","Filter"),e=Op("http://www.opengis.net/ogc","FeatureId");d.appendChild(e);e.setAttribute("fid",c);b.appendChild(d)}
+var Vt={"http://www.opengis.net/wfs":{Insert:V(function(b,c,d){var e=d[d.length-1],e=Op(x(e,"featureNS"),x(e,"featureType"));b.appendChild(e);X.prototype.hg(e,c,d)}),Update:V(function(b,c,d){var e=d[d.length-1],f=x(e,"featureType"),g=x(e,"featurePrefix"),g=m(g)?g:"feature",h=x(e,"featureNS");b.setAttribute("typeName",g+":"+f);hq(b,"http://www.w3.org/2000/xmlns/","xmlns:"+g,h);f=c.X;if(m(f)){for(var g=c.I(),h=[],k=0,n=g.length;k<n;k++){var p=c.get(g[k]);m(p)&&h.push({name:g[k],value:p})}vq({node:b,
+srsName:x(e,"srsName")},Vt,qq("Property"),h,d);Ut(b,f)}}),Delete:V(function(b,c,d){var e=d[d.length-1];d=x(e,"featureType");var f=x(e,"featurePrefix"),f=m(f)?f:"feature",e=x(e,"featureNS");b.setAttribute("typeName",f+":"+d);hq(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,e);c=c.X;m(c)&&Ut(b,c)}),Property:V(function(b,c,d){var e=Op("http://www.opengis.net/wfs","Name");b.appendChild(e);Jq(e,c.name);null!=c.value&&(e=Op("http://www.opengis.net/wfs","Value"),b.appendChild(e),c.value instanceof Mk?X.prototype.Rd(e,
+c.value,d):Jq(e,c.value))}),Native:V(function(b,c){m(c.yl)&&b.setAttribute("vendorId",c.yl);m(c.al)&&b.setAttribute("safeToIgnore",c.al);m(c.value)&&Jq(b,c.value)})}},Wt={"http://www.opengis.net/wfs":{Query:V(function(b,c,d){var e=d[d.length-1],f=x(e,"featurePrefix"),g=x(e,"featureNS"),h=x(e,"propertyNames"),k=x(e,"srsName");b.setAttribute("typeName",(m(f)?f+":":"")+c);m(k)&&b.setAttribute("srsName",k);m(g)&&hq(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,g);c=Cb(e);c.node=b;vq(c,Tt,qq("PropertyName"),
+h,d);e=x(e,"bbox");m(e)&&(h=Op("http://www.opengis.net/ogc","Filter"),c=x(d[d.length-1],"geometryName"),f=Op("http://www.opengis.net/ogc","BBOX"),h.appendChild(f),g=Op("http://www.opengis.net/ogc","PropertyName"),Jq(g,c),f.appendChild(g),X.prototype.Rd(f,e,d),b.appendChild(h))})}};
+Kt.prototype.n=function(b){var c=Op("http://www.opengis.net/wfs","GetFeature");c.setAttribute("service","WFS");c.setAttribute("version","1.1.0");m(b)&&(m(b.handle)&&c.setAttribute("handle",b.handle),m(b.outputFormat)&&c.setAttribute("outputFormat",b.outputFormat),m(b.maxFeatures)&&c.setAttribute("maxFeatures",b.maxFeatures),m(b.resultType)&&c.setAttribute("resultType",b.resultType),m(b.rl)&&c.setAttribute("startIndex",b.rl),m(b.count)&&c.setAttribute("count",b.count));hq(c,"http://www.w3.org/2001/XMLSchema-instance",
+"xsi:schemaLocation",this.d);var d=b.featureTypes;b=[{node:c,srsName:b.srsName,featureNS:m(b.featureNS)?b.featureNS:this.b,featurePrefix:b.featurePrefix,geometryName:b.geometryName,bbox:b.bbox,Mf:m(b.Mf)?b.Mf:[]}];var e=Cb(b[b.length-1]);e.node=c;vq(e,Wt,qq("Query"),d,b);return c};
+Kt.prototype.j=function(b,c,d,e){var f=[],g=Op("http://www.opengis.net/wfs","Transaction");g.setAttribute("service","WFS");g.setAttribute("version","1.1.0");var h,k;m(e)&&(h=m(e.gmlOptions)?e.gmlOptions:{},m(e.handle)&&g.setAttribute("handle",e.handle));hq(g,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.d);null!=b&&(k={node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Eb(k,h),vq(k,Vt,qq("Insert"),b,f));null!=c&&(k={node:g,featureNS:e.featureNS,
+featureType:e.featureType,featurePrefix:e.featurePrefix},Eb(k,h),vq(k,Vt,qq("Update"),c,f));null!=d&&vq({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Vt,qq("Delete"),d,f);m(e.nativeElements)&&vq({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Vt,qq("Native"),e.nativeElements,f);return g};Kt.prototype.Lc=function(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return this.tc(b);return null};
+Kt.prototype.tc=function(b){b=b.firstElementChild.firstElementChild;if(null!=b)for(b=b.firstElementChild;null!==b;b=b.nextElementSibling)if(0!==b.childNodes.length&&(1!==b.childNodes.length||3!==b.firstChild.nodeType)){var c=[{}];this.c.Id(b,c);return Ee(c.pop().srsName)}return null};function Xt(b){b=m(b)?b:{};this.defaultDataProjection=null;this.a=m(b.splitCollection)?b.splitCollection:!1}u(Xt,Er);function Yt(b){b=b.K();return 0==b.length?"":b[0]+" "+b[1]}function Zt(b){b=b.K();for(var c=[],d=0,e=b.length;d<e;++d)c.push(b[d][0]+" "+b[d][1]);return c.join(",")}function $t(b){var c=[];b=b.dd();for(var d=0,e=b.length;d<e;++d)c.push("("+Zt(b[d])+")");return c.join(",")}function au(b){var c=b.H();b=(0,bu[c])(b);c=c.toUpperCase();return 0===b.length?c+" EMPTY":c+"("+b+")"}
+var bu={Point:Yt,LineString:Zt,Polygon:$t,MultiPoint:function(b){var c=[];b=b.xd();for(var d=0,e=b.length;d<e;++d)c.push("("+Yt(b[d])+")");return c.join(",")},MultiLineString:function(b){var c=[];b=b.Ec();for(var d=0,e=b.length;d<e;++d)c.push("("+Zt(b[d])+")");return c.join(",")},MultiPolygon:function(b){var c=[];b=b.gd();for(var d=0,e=b.length;d<e;++d)c.push("("+$t(b[d])+")");return c.join(",")},GeometryCollection:function(b){var c=[];b=b.af();for(var d=0,e=b.length;d<e;++d)c.push(au(b[d]));return c.join(",")}};
+l=Xt.prototype;l.Ic=function(b,c){var d=this.Kc(b,c);if(m(d)){var e=new R;e.Ma(d);return e}return null};l.ze=function(b,c){var d=[],e=this.Kc(b,c);this.a&&"GeometryCollection"==e.H()?d=e.d:d=[e];for(var f=[],g=0,h=d.length;g<h;++g)e=new R,e.Ma(d[g]),f.push(e);return f};l.Kc=function(b,c){var d;d=new cu(new du(b));d.a=eu(d.c);d=fu(d);return m(d)?xp(d,!1,c):null};l.Ce=function(){return null};l.Qd=function(b,c){var d=b.N();return m(d)?this.Rc(d,c):""};
+l.ig=function(b,c){if(1==b.length)return this.Qd(b[0],c);for(var d=[],e=0,f=b.length;e<f;++e)d.push(b[e].N());d=new jn(d);return this.Rc(d,c)};l.Rc=function(b,c){return au(xp(b,!0,c))};function du(b){this.c=b;this.a=-1}function gu(b,c){var d=m(c)?c:!1;return"0"<=b&&"9">=b||"."==b&&!d}
+function eu(b){var c=b.c.charAt(++b.a),d={position:b.a,value:c};if("("==c)d.type=2;else if(","==c)d.type=5;else if(")"==c)d.type=3;else if(gu(c)||"-"==c){d.type=4;var e,c=b.a,f=!1;do"."==e&&(f=!0),e=b.c.charAt(++b.a);while(gu(e,f));b=parseFloat(b.c.substring(c,b.a--));d.value=b}else if("a"<=c&&"z">=c||"A"<=c&&"Z">=c){d.type=1;c=b.a;do e=b.c.charAt(++b.a);while("a"<=e&&"z">=e||"A"<=e&&"Z">=e);b=b.c.substring(c,b.a--).toUpperCase();d.value=b}else{if(" "==c||"\t"==c||"\r"==c||"\n"==c)return eu(b);if(""===
+c)d.type=6;else throw Error("Unexpected character: "+c);}return d}function cu(b){this.c=b}l=cu.prototype;l.match=function(b){if(b=this.a.type==b)this.a=eu(this.c);return b};
+function fu(b){var c=b.a;if(b.match(1)){var d=c.value;if("GEOMETRYCOLLECTION"==d){a:{if(b.match(2)){c=[];do c.push(fu(b));while(b.match(5));if(b.match(3)){b=c;break a}}else if(hu(b)){b=[];break a}throw Error(iu(b));}return new jn(b)}var e=ju[d],c=ku[d];if(!m(e)||!m(c))throw Error("Invalid geometry type: "+d);b=e.call(b);return new c(b)}throw Error(iu(b));}l.we=function(){if(this.match(2)){var b=lu(this);if(this.match(3))return b}else if(hu(this))return null;throw Error(iu(this));};
+l.ve=function(){if(this.match(2)){var b=mu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};l.xe=function(){if(this.match(2)){var b=nu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};l.wk=function(){if(this.match(2)){var b;if(2==this.a.type)for(b=[this.we()];this.match(5);)b.push(this.we());else b=mu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};
+l.vk=function(){if(this.match(2)){var b=nu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};l.xk=function(){if(this.match(2)){for(var b=[this.xe()];this.match(5);)b.push(this.xe());if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};function lu(b){for(var c=[],d=0;2>d;++d){var e=b.a;if(b.match(4))c.push(e.value);else break}if(2==c.length)return c;throw Error(iu(b));}function mu(b){for(var c=[lu(b)];b.match(5);)c.push(lu(b));return c}
+function nu(b){for(var c=[b.ve()];b.match(5);)c.push(b.ve());return c}function hu(b){var c=1==b.a.type&&"EMPTY"==b.a.value;c&&(b.a=eu(b.c));return c}function iu(b){return"Unexpected `"+b.a.value+"` at position "+b.a.position+" in `"+b.c.c+"`"}var ku={POINT:jl,LINESTRING:M,POLYGON:H,MULTIPOINT:un,MULTILINESTRING:rn,MULTIPOLYGON:vn},ju={POINT:cu.prototype.we,LINESTRING:cu.prototype.ve,POLYGON:cu.prototype.xe,MULTIPOINT:cu.prototype.wk,MULTILINESTRING:cu.prototype.vk,MULTIPOLYGON:cu.prototype.xk};function ou(){this.version=void 0}u(ou,vt);function wt(b,c){for(var d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType)return xt(b,d);return null}function xt(b,c){b.version=Aa(c.getAttribute("version"));var d=W({version:b.version},pu,c,[]);return m(d)?d:null}function qu(b,c){return W({},ru,b,c)}function su(b,c){return W({},tu,b,c)}function uu(b,c){var d=qu(b,c);if(m(d)){var e=[Gq(b.getAttribute("width")),Gq(b.getAttribute("height"))];d.size=e;return d}}function vu(b,c){return W([],wu,b,c)}
+var xu=[null,"http://www.opengis.net/wms"],pu=nq(xu,{Service:S(function(b,c){return W({},yu,b,c)}),Capability:S(function(b,c){return W({},zu,b,c)})}),zu=nq(xu,{Request:S(function(b,c){return W({},Au,b,c)}),Exception:S(function(b,c){return W([],Bu,b,c)}),Layer:S(function(b,c){return W({},Cu,b,c)})}),yu=nq(xu,{Name:S(Hq),Title:S(Hq),Abstract:S(Hq),KeywordList:S(vu),OnlineResource:S(ut),ContactInformation:S(function(b,c){return W({},Du,b,c)}),Fees:S(Hq),AccessConstraints:S(Hq),LayerLimit:S(Fq),MaxWidth:S(Fq),
+MaxHeight:S(Fq)}),Du=nq(xu,{ContactPersonPrimary:S(function(b,c){return W({},Eu,b,c)}),ContactPosition:S(Hq),ContactAddress:S(function(b,c){return W({},Fu,b,c)}),ContactVoiceTelephone:S(Hq),ContactFacsimileTelephone:S(Hq),ContactElectronicMailAddress:S(Hq)}),Eu=nq(xu,{ContactPerson:S(Hq),ContactOrganization:S(Hq)}),Fu=nq(xu,{AddressType:S(Hq),Address:S(Hq),City:S(Hq),StateOrProvince:S(Hq),PostCode:S(Hq),Country:S(Hq)}),Bu=nq(xu,{Format:kq(Hq)}),Cu=nq(xu,{Name:S(Hq),Title:S(Hq),Abstract:S(Hq),KeywordList:S(vu),
+CRS:mq(Hq),EX_GeographicBoundingBox:S(function(b,c){var d=W({},Gu,b,c);if(m(d)){var e=x(d,"westBoundLongitude"),f=x(d,"southBoundLatitude"),g=x(d,"eastBoundLongitude"),d=x(d,"northBoundLatitude");return m(e)&&m(f)&&m(g)&&m(d)?[e,f,g,d]:void 0}}),BoundingBox:mq(function(b){var c=[Eq(b.getAttribute("minx")),Eq(b.getAttribute("miny")),Eq(b.getAttribute("maxx")),Eq(b.getAttribute("maxy"))],d=[Eq(b.getAttribute("resx")),Eq(b.getAttribute("resy"))];return{crs:b.getAttribute("CRS"),extent:c,res:d}}),Dimension:mq(function(b){return{name:b.getAttribute("name"),
+units:b.getAttribute("units"),unitSymbol:b.getAttribute("unitSymbol"),"default":b.getAttribute("default"),multipleValues:Bq(b.getAttribute("multipleValues")),nearestValue:Bq(b.getAttribute("nearestValue")),current:Bq(b.getAttribute("current")),values:Hq(b)}}),Attribution:S(function(b,c){return W({},Hu,b,c)}),AuthorityURL:mq(function(b,c){var d=qu(b,c);if(m(d)){var e=b.getAttribute("name");d.name=e;return d}}),Identifier:mq(Hq),MetadataURL:mq(function(b,c){var d=qu(b,c);if(m(d)){var e=b.getAttribute("type");
+d.type=e;return d}}),DataURL:mq(qu),FeatureListURL:mq(qu),Style:mq(function(b,c){return W({},Iu,b,c)}),MinScaleDenominator:S(Dq),MaxScaleDenominator:S(Dq),Layer:mq(function(b,c){var d=c[c.length-1],e=W({},Cu,b,c);if(m(e)){var f=Bq(b.getAttribute("queryable"));m(f)||(f=x(d,"queryable"));e.queryable=m(f)?f:!1;f=Gq(b.getAttribute("cascaded"));m(f)||(f=x(d,"cascaded"));e.cascaded=f;f=Bq(b.getAttribute("opaque"));m(f)||(f=x(d,"opaque"));e.opaque=m(f)?f:!1;f=Bq(b.getAttribute("noSubsets"));m(f)||(f=x(d,
+"noSubsets"));e.noSubsets=m(f)?f:!1;f=Eq(b.getAttribute("fixedWidth"));m(f)||(f=x(d,"fixedWidth"));e.fixedWidth=f;f=Eq(b.getAttribute("fixedHeight"));m(f)||(f=x(d,"fixedHeight"));e.fixedHeight=f;Ta(["Style","CRS","AuthorityURL"],function(b){var c=x(d,b);if(m(c)){var f=Bb(e,b),f=f.concat(c);e[b]=f}});Ta("EX_GeographicBoundingBox BoundingBox Dimension Attribution MinScaleDenominator MaxScaleDenominator".split(" "),function(b){m(x(e,b))||(e[b]=x(d,b))});return e}})}),Hu=nq(xu,{Title:S(Hq),OnlineResource:S(ut),
+LogoURL:S(uu)}),Gu=nq(xu,{westBoundLongitude:S(Dq),eastBoundLongitude:S(Dq),southBoundLatitude:S(Dq),northBoundLatitude:S(Dq)}),Au=nq(xu,{GetCapabilities:S(su),GetMap:S(su),GetFeatureInfo:S(su)}),tu=nq(xu,{Format:mq(Hq),DCPType:mq(function(b,c){return W({},Ju,b,c)})}),Ju=nq(xu,{HTTP:S(function(b,c){return W({},Ku,b,c)})}),Ku=nq(xu,{Get:S(qu),Post:S(qu)}),Iu=nq(xu,{Name:S(Hq),Title:S(Hq),Abstract:S(Hq),LegendURL:mq(uu),StyleSheetURL:S(qu),StyleURL:S(qu)}),ru=nq(xu,{Format:S(Hq),OnlineResource:S(ut)}),
+wu=nq(xu,{Keyword:kq(Hq)});function Lu(){this.b="http://mapserver.gis.umn.edu/mapserver";this.c=new Uq;this.defaultDataProjection=null}u(Lu,wq);
+function Mu(b,c,d){c.namespaceURI=b.b;var e=Tp(c),f=[];if(0===c.childNodes.length)return f;"msGMLOutput"==e&&Ta(c.childNodes,function(b){if(1===b.nodeType){var c=d[0],e=b.localName,n=new RegExp(Ja("_layer"),""),e=e.replace(n,"")+"_feature";c.featureType=e;c.featureNS=this.b;n={};n[e]=kq(this.c.ye,this.c);c=nq([x(c,"featureNS"),null],n);b.namespaceURI=this.b;b=W([],c,b,d,this.c);m(b)&&db(f,b)}},b);"FeatureCollection"==e&&(b=W([],b.c.Td,c,[{}],b.c),m(b)&&(f=b));return f}
+Lu.prototype.Ob=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};m(c)&&Eb(d,vp(this,b,c));return Mu(this,b,[d])};var Nu=new xe(6378137);function Z(b){td.call(this);b=m(b)?b:{};this.a=null;this.e=We;this.d=void 0;z(this,xd("projection"),this.Ui,!1,this);z(this,xd("tracking"),this.Vi,!1,this);m(b.projection)&&this.n(Ee(b.projection));m(b.trackingOptions)&&this.j(b.trackingOptions);this.b(m(b.tracking)?b.tracking:!1)}u(Z,td);l=Z.prototype;l.M=function(){this.b(!1);Z.S.M.call(this)};l.Ui=function(){var b=this.g();null!=b&&(this.e=De(Ee("EPSG:4326"),b),null===this.a||this.set("position",this.e(this.a)))};
+l.Vi=function(){if(fg){var b=this.i();b&&!m(this.d)?this.d=ba.navigator.geolocation.watchPosition(sa(this.Ek,this),sa(this.Fk,this),this.f()):!b&&m(this.d)&&(ba.navigator.geolocation.clearWatch(this.d),this.d=void 0)}};
+l.Ek=function(b){b=b.coords;this.set("accuracy",b.accuracy);this.set("altitude",null===b.altitude?void 0:b.altitude);this.set("altitudeAccuracy",null===b.altitudeAccuracy?void 0:b.altitudeAccuracy);this.set("heading",null===b.heading?void 0:bc(b.heading));null===this.a?this.a=[b.longitude,b.latitude]:(this.a[0]=b.longitude,this.a[1]=b.latitude);var c=this.e(this.a);this.set("position",c);this.set("speed",null===b.speed?void 0:b.speed);b=zl(Nu,this.a,b.accuracy);b.ma(this.e);this.set("accuracyGeometry",
+b);this.l()};l.Fk=function(b){b.type="error";this.b(!1);this.dispatchEvent(b)};l.$e=function(){return this.get("accuracy")};Z.prototype.getAccuracy=Z.prototype.$e;Z.prototype.p=function(){return this.get("accuracyGeometry")||null};Z.prototype.getAccuracyGeometry=Z.prototype.p;Z.prototype.q=function(){return this.get("altitude")};Z.prototype.getAltitude=Z.prototype.q;Z.prototype.r=function(){return this.get("altitudeAccuracy")};Z.prototype.getAltitudeAccuracy=Z.prototype.r;Z.prototype.F=function(){return this.get("heading")};
+Z.prototype.getHeading=Z.prototype.F;Z.prototype.J=function(){return this.get("position")};Z.prototype.getPosition=Z.prototype.J;Z.prototype.g=function(){return this.get("projection")};Z.prototype.getProjection=Z.prototype.g;Z.prototype.t=function(){return this.get("speed")};Z.prototype.getSpeed=Z.prototype.t;Z.prototype.i=function(){return this.get("tracking")};Z.prototype.getTracking=Z.prototype.i;Z.prototype.f=function(){return this.get("trackingOptions")};Z.prototype.getTrackingOptions=Z.prototype.f;
+Z.prototype.n=function(b){this.set("projection",b)};Z.prototype.setProjection=Z.prototype.n;Z.prototype.b=function(b){this.set("tracking",b)};Z.prototype.setTracking=Z.prototype.b;Z.prototype.j=function(b){this.set("trackingOptions",b)};Z.prototype.setTrackingOptions=Z.prototype.j;function Ou(b,c,d){for(var e=[],f=b(0),g=b(1),h=c(f),k=c(g),n=[g,f],p=[k,h],q=[1,0],r={},s=1E5,v,y,C,F,G;0<--s&&0<q.length;)C=q.pop(),f=n.pop(),h=p.pop(),g=C.toString(),g in r||(e.push(h[0],h[1]),r[g]=!0),F=q.pop(),g=n.pop(),k=p.pop(),G=(C+F)/2,v=b(G),y=c(v),Uk(y[0],y[1],h[0],h[1],k[0],k[1])<d?(e.push(k[0],k[1]),g=F.toString(),r[g]=!0):(q.push(F,G,G,C),p.push(k,y,y,h),n.push(g,v,v,f));return e}function Pu(b,c,d,e,f){var g=Ee("EPSG:4326");return Ou(function(e){return[b,c+(d-c)*e]},Ve(g,e),f)}
+function Qu(b,c,d,e,f){var g=Ee("EPSG:4326");return Ou(function(e){return[c+(d-c)*e,b]},Ve(g,e),f)};function Ru(b){b=m(b)?b:{};this.o=this.g=null;this.d=this.b=Infinity;this.f=this.e=-Infinity;this.r=m(b.targetSize)?b.targetSize:100;this.p=m(b.maxLines)?b.maxLines:100;this.a=[];this.c=[];this.q=m(b.strokeStyle)?b.strokeStyle:Su;this.j=this.i=void 0;this.n=null;this.setMap(m(b.map)?b.map:null)}var Su=new Nl({color:"rgba(0,0,0,0.2)"}),Tu=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001];
+function Uu(b,c,d,e,f){var g=f;c=Pu(c,b.e,b.b,b.o,d);g=m(b.a[g])?b.a[g]:new M(null);qn(g,"XY",c);qe(g.D(),e)&&(b.a[f++]=g);return f}function Vu(b,c,d,e,f){var g=f;c=Qu(c,b.f,b.d,b.o,d);g=m(b.c[g])?b.c[g]:new M(null);qn(g,"XY",c);qe(g.D(),e)&&(b.c[f++]=g);return f}l=Ru.prototype;l.Wi=function(){return this.g};l.wh=function(){return this.a};l.Bh=function(){return this.c};
+l.jf=function(b){var c=b.vectorContext,d=b.frameState;b=d.extent;var e=d.viewState,f=e.center,g=e.projection,e=e.resolution,d=d.pixelRatio,d=e*e/(4*d*d);if(null===this.o||!Ue(this.o,g)){var h=g.D(),k=g.d,n=k[2],p=k[1],q=k[0];this.b=k[3];this.d=n;this.e=p;this.f=q;k=Ee("EPSG:4326");this.i=Ve(k,g);this.j=Ve(g,k);this.n=this.j(ke(h));this.o=g}for(var g=this.n[0],h=this.n[1],k=-1,r,p=Math.pow(this.r*e,2),q=[],s=[],e=0,n=Tu.length;e<n;++e){r=Tu[e]/2;q[0]=g-r;q[1]=h-r;s[0]=g+r;s[1]=h+r;this.i(q,q);this.i(s,
+s);r=Math.pow(s[0]-q[0],2)+Math.pow(s[1]-q[1],2);if(r<=p)break;k=Tu[e]}e=k;if(-1==e)this.a.length=this.c.length=0;else{g=this.j(f);f=g[0];g=g[1];h=this.p;f=Math.floor(f/e)*e;p=Yb(f,this.f,this.d);n=Uu(this,p,d,b,0);for(k=0;p!=this.f&&k++<h;)p=Math.max(p-e,this.f),n=Uu(this,p,d,b,n);p=Yb(f,this.f,this.d);for(k=0;p!=this.d&&k++<h;)p=Math.min(p+e,this.d),n=Uu(this,p,d,b,n);this.a.length=n;g=Math.floor(g/e)*e;f=Yb(g,this.e,this.b);n=Vu(this,f,d,b,0);for(k=0;f!=this.e&&k++<h;)f=Math.max(f-e,this.e),n=
+Vu(this,f,d,b,n);f=Yb(g,this.e,this.b);for(k=0;f!=this.b&&k++<h;)f=Math.min(f+e,this.b),n=Vu(this,f,d,b,n);this.c.length=n}c.wa(null,this.q);b=0;for(d=this.a.length;b<d;++b)f=this.a[b],c.Cb(f,null);b=0;for(d=this.c.length;b<d;++b)f=this.c[b],c.Cb(f,null)};l.setMap=function(b){null!==this.g&&(this.g.v("postcompose",this.jf,this),this.g.render());null!==b&&(b.u("postcompose",this.jf,this),b.render());this.g=b};function Wu(b,c,d,e,f,g,h){tj.call(this,b,c,d,0,e);this.o=f;this.a=new Image;null!==g&&(this.a.crossOrigin=g);this.d={};this.b=null;this.state=0;this.g=h}u(Wu,tj);Wu.prototype.c=function(b){if(m(b)){var c=ma(b);if(c in this.d)return this.d[c];b=xb(this.d)?this.a:this.a.cloneNode(!1);return this.d[c]=b}return this.a};Wu.prototype.i=function(){this.state=3;Ta(this.b,Yc);this.b=null;this.dispatchEvent("change")};
+Wu.prototype.n=function(){m(this.resolution)||(this.resolution=oe(this.extent)/this.a.height);this.state=2;Ta(this.b,Yc);this.b=null;this.dispatchEvent("change")};Wu.prototype.load=function(){0==this.state&&(this.state=1,this.b=[Wc(this.a,"error",this.i,!1,this),Wc(this.a,"load",this.n,!1,this)],this.g(this,this.o))};function Xu(b,c,d,e,f){tj.call(this,b,c,d,2,e);this.a=f}u(Xu,tj);Xu.prototype.c=function(){return this.a};function Yu(b,c,d,e,f){uj.call(this,b,c);this.g=d;this.c=new Image;null!==e&&(this.c.crossOrigin=e);this.d={};this.b=null;this.o=f}u(Yu,uj);l=Yu.prototype;l.Na=function(b){if(m(b)){var c=ma(b);if(c in this.d)return this.d[c];b=xb(this.d)?this.c:this.c.cloneNode(!1);return this.d[c]=b}return this.c};l.mb=function(){return this.g};l.Xi=function(){this.state=3;Ta(this.b,Yc);this.b=null;vj(this)};l.Yi=function(){this.state=this.c.naturalWidth&&this.c.naturalHeight?2:4;Ta(this.b,Yc);this.b=null;vj(this)};
+l.load=function(){0==this.state&&(this.state=1,this.b=[Wc(this.c,"error",this.Xi,!1,this),Wc(this.c,"load",this.Yi,!1,this)],this.o(this,this.g))};function Zu(b,c,d){return function(e,f,g){return d(b,c,e,f,g)}}function $u(){};function av(b,c){jd.call(this);this.a=new ap(this);var d=b;c&&(d=zf(b));this.a.La(d,"dragenter",this.nk);d!=b&&this.a.La(d,"dragover",this.ok);this.a.La(b,"dragover",this.pk);this.a.La(b,"drop",this.qk)}u(av,jd);l=av.prototype;l.Dc=!1;l.M=function(){av.S.M.call(this);this.a.hc()};l.nk=function(b){var c=b.a.dataTransfer;(this.Dc=!(!c||!(c.types&&(Za(c.types,"Files")||Za(c.types,"public.file-url"))||c.files&&0<c.files.length)))&&b.preventDefault()};
+l.ok=function(b){this.Dc&&(b.preventDefault(),b.a.dataTransfer.dropEffect="none")};l.pk=function(b){this.Dc&&(b.preventDefault(),b.lb(),b=b.a.dataTransfer,b.effectAllowed="all",b.dropEffect="copy")};l.qk=function(b){this.Dc&&(b.preventDefault(),b.lb(),b=new zc(b.a),b.type="drop",this.dispatchEvent(b))};function bv(b){b.prototype.then=b.prototype.then;b.prototype.$goog_Thenable=!0}function cv(b){if(!b)return!1;try{return!!b.$goog_Thenable}catch(c){return!1}};function dv(b,c){ev||fv();gv||(ev(),gv=!0);hv.push(new iv(b,c))}var ev;function fv(){if(ba.Promise&&ba.Promise.resolve){var b=ba.Promise.resolve();ev=function(){b.then(jv)}}else ev=function(){$h(jv)}}var gv=!1,hv=[];function jv(){for(;hv.length;){var b=hv;hv=[];for(var c=0;c<b.length;c++){var d=b[c];try{d.a.call(d.c)}catch(e){Zh(e)}}}gv=!1}function iv(b,c){this.a=b;this.c=c};function kv(b,c){this.c=lv;this.f=void 0;this.a=this.b=null;this.d=this.e=!1;try{var d=this;b.call(c,function(b){mv(d,nv,b)},function(b){mv(d,ov,b)})}catch(e){mv(this,ov,e)}}var lv=0,nv=2,ov=3;kv.prototype.then=function(b,c,d){return pv(this,ka(b)?b:null,ka(c)?c:null,d)};bv(kv);kv.prototype.cancel=function(b){this.c==lv&&dv(function(){var c=new qv(b);rv(this,c)},this)};
+function rv(b,c){if(b.c==lv)if(b.b){var d=b.b;if(d.a){for(var e=0,f=-1,g=0,h;h=d.a[g];g++)if(h=h.wc)if(e++,h==b&&(f=g),0<=f&&1<e)break;0<=f&&(d.c==lv&&1==e?rv(d,c):(e=d.a.splice(f,1)[0],sv(d,e,ov,c)))}}else mv(b,ov,c)}function tv(b,c){b.a&&b.a.length||b.c!=nv&&b.c!=ov||uv(b);b.a||(b.a=[]);b.a.push(c)}
+function pv(b,c,d,e){var f={wc:null,Gf:null,If:null};f.wc=new kv(function(b,h){f.Gf=c?function(d){try{var f=c.call(e,d);b(f)}catch(p){h(p)}}:b;f.If=d?function(c){try{var f=d.call(e,c);!m(f)&&c instanceof qv?h(c):b(f)}catch(p){h(p)}}:h});f.wc.b=b;tv(b,f);return f.wc}kv.prototype.g=function(b){this.c=lv;mv(this,nv,b)};kv.prototype.o=function(b){this.c=lv;mv(this,ov,b)};
+function mv(b,c,d){if(b.c==lv){if(b==d)c=ov,d=new TypeError("Promise cannot resolve to itself");else{if(cv(d)){b.c=1;d.then(b.g,b.o,b);return}if(la(d))try{var e=d.then;if(ka(e)){vv(b,d,e);return}}catch(f){c=ov,d=f}}b.f=d;b.c=c;uv(b);c!=ov||d instanceof qv||wv(b,d)}}function vv(b,c,d){function e(c){g||(g=!0,b.o(c))}function f(c){g||(g=!0,b.g(c))}b.c=1;var g=!1;try{d.call(c,f,e)}catch(h){e(h)}}function uv(b){b.e||(b.e=!0,dv(b.i,b))}
+kv.prototype.i=function(){for(;this.a&&this.a.length;){var b=this.a;this.a=[];for(var c=0;c<b.length;c++)sv(this,b[c],this.c,this.f)}this.e=!1};function sv(b,c,d,e){if(d==nv)c.Gf(e);else{if(c.wc)for(;b&&b.d;b=b.b)b.d=!1;c.If(e)}}function wv(b,c){b.d=!0;dv(function(){b.d&&xv.call(null,c)})}var xv=Zh;function qv(b){xa.call(this,b)}u(qv,xa);qv.prototype.name="cancel";/*
  Portions of this code are from MochiKit, received by
  The Closure Authors under the MIT license. All other code is Copyright
  2005-2009 The Closure Authors. All Rights Reserved.
 */
-function Rr(a,b){this.e=[];this.o=a;this.p=b||null;this.d=this.a=!1;this.b=void 0;this.l=this.q=this.g=!1;this.f=0;this.c=null;this.i=0}Rr.prototype.cancel=function(a){if(this.a)this.b instanceof Rr&&this.b.cancel();else{if(this.c){var b=this.c;delete this.c;a?b.cancel(a):(b.i--,0>=b.i&&b.cancel())}this.o?this.o.call(this.p,this):this.l=!0;this.a||(a=new Sr,Tr(this),Ur(this,!1,a))}};Rr.prototype.k=function(a,b){this.g=!1;Ur(this,a,b)};function Ur(a,b,c){a.a=!0;a.b=c;a.d=!b;Vr(a)}
-function Tr(a){if(a.a){if(!a.l)throw new Wr;a.l=!1}}function Xr(a,b,c,d){a.e.push([b,c,d]);a.a&&Vr(a)}Rr.prototype.then=function(a,b,c){var d,e,f=new Dr(function(a,b){d=a;e=b});Xr(this,d,function(a){a instanceof Sr?f.cancel():e(a)});return f.then(a,b,c)};pr(Rr);function Yr(a){return Wa(a.e,function(a){return la(a[1])})}
-function Vr(a){if(a.f&&a.a&&Yr(a)){var b=a.f,c=Zr[b];c&&(ba.clearTimeout(c.T),delete Zr[b]);a.f=0}a.c&&(a.c.i--,delete a.c);for(var b=a.b,d=c=!1;a.e.length&&!a.g;){var e=a.e.shift(),f=e[0],g=e[1],e=e[2];if(f=a.d?g:f)try{var h=f.call(e||a.p,b);l(h)&&(a.d=a.d&&(h==b||h instanceof Error),a.b=b=h);qr(b)&&(d=!0,a.g=!0)}catch(m){b=m,a.d=!0,Yr(a)||(c=!0)}}a.b=b;d&&(h=sa(a.k,a,!0),d=sa(a.k,a,!1),b instanceof Rr?(Xr(b,h,d),b.q=!0):b.then(h,d));c&&(b=new $r(b),Zr[b.T]=b,a.f=b.T)}
-function Wr(){va.call(this)}t(Wr,va);Wr.prototype.message="Deferred has already fired";Wr.prototype.name="AlreadyCalledError";function Sr(){va.call(this)}t(Sr,va);Sr.prototype.message="Deferred was canceled";Sr.prototype.name="CanceledError";function $r(a){this.T=ba.setTimeout(sa(this.c,this),0);this.a=a}$r.prototype.c=function(){delete Zr[this.T];throw this.a;};var Zr={};function as(a,b){l(a.name)?(this.name=a.name,this.code=Ac[a.name]):(this.code=a.code,this.name=bs(a.code));va.call(this,xa("%s %s",this.name,b))}t(as,va);function bs(a){var b=zc(function(b){return a==b});if(!l(b))throw Error("Invalid code: "+a);return b}var Ac={AbortError:3,EncodingError:5,InvalidModificationError:9,InvalidStateError:7,NotFoundError:1,NotReadableError:4,NoModificationAllowedError:6,PathExistsError:12,QuotaExceededError:10,SecurityError:2,SyntaxError:8,TypeMismatchError:11};function cs(a,b){fc.call(this,a.type,b)}t(cs,fc);function ds(){hd.call(this);this.Na=new FileReader;this.Na.onloadstart=sa(this.a,this);this.Na.onprogress=sa(this.a,this);this.Na.onload=sa(this.a,this);this.Na.onabort=sa(this.a,this);this.Na.onerror=sa(this.a,this);this.Na.onloadend=sa(this.a,this)}t(ds,hd);ds.prototype.getError=function(){return this.Na.error&&new as(this.Na.error,"reading file")};ds.prototype.a=function(a){this.dispatchEvent(new cs(a,this))};ds.prototype.I=function(){ds.K.I.call(this);delete this.Na};
-function es(a){var b=new Rr;a.ya("loadend",ta(function(a,b){var e=b.Na.result,f=b.getError();null==e||f?(Tr(a),Ur(a,!1,f)):(Tr(a),Ur(a,!0,e));b.Nb()},b,a));return b};function fs(a){a=l(a)?a:{};jr.call(this);this.d=l(a.formatConstructors)?a.formatConstructors:[];this.e=l(a.reprojectTo)?Be(a.reprojectTo):null;this.b=null;this.a=void 0}t(fs,jr);k=fs.prototype;k.I=function(){l(this.a)&&Yc(this.a);fs.K.I.call(this)};k.Tf=function(a){a=a.a.dataTransfer.files;var b,c,d;b=0;for(c=a.length;b<c;++b){var e=d=a[b],f=new ds,g=es(f);f.Na.readAsText(e,"");Xr(g,ta(this.ng,d),null,this)}};
-k.ng=function(a,b){var c=this.g,d=this.e;null===d&&(d=c.a().o);var c=this.d,e=[],f,g;f=0;for(g=c.length;f<g;++f){var h=new c[f],m;try{m=h.la(b)}catch(n){m=null}if(null!==m){var h=h.ra(b),h=Se(h,d),p,r;p=0;for(r=m.length;p<r;++p){var q=m[p],u=q.J();null!=u&&u.Mb(h);e.push(q)}}}this.dispatchEvent(new gs(hs,this,a,e,d))};k.La=dd;
-k.setMap=function(a){l(this.a)&&(Yc(this.a),this.a=void 0);null!==this.b&&(ec(this.b),this.b=null);fs.K.setMap.call(this,a);null!==a&&(this.b=new or(a.b),this.a=y(this.b,"drop",this.Tf,!1,this))};var hs="addfeatures";function gs(a,b,c,d,e){fc.call(this,a,b);this.features=d;this.file=c;this.projection=e}t(gs,fc);function is(){jr.call(this);this.l=!1;this.p={};this.b=[]}t(is,jr);function js(a){for(var b=a.length,c=0,d=0,e=0;e<b;e++)c+=a[e].clientX,d+=a[e].clientY;return[c/b,d/b]}k=is.prototype;k.jb=ca;k.ab=cd;k.$a=cd;
-k.La=function(a){if(!(a instanceof ki))return!0;var b=!1,c=a.type;if(c===ti||c===vi||c===ri)c=a.c,a.type==ri?delete this.p[c.pointerId]:a.type==ti?this.p[c.pointerId]=c:c.pointerId in this.p&&(this.p[c.pointerId]=c),this.b=vc(this.p);this.l&&(a.type==vi?this.jb(a):a.type==ri&&(this.l=this.ab(a)));a.type==ti&&(this.l=a=this.$a(a),b=this.kd(a));return!b};k.kd=cd;function ks(a){this.b=this.c=this.e=this.d=this.a=null;this.f=a}t(ks,ac);function ls(a){var b=a.e,c=a.c;a=Va([b,[b[0],c[1]],c,[c[0],b[1]]],a.a.ta,a.a);a[4]=a[0].slice();return new I([a])}ks.prototype.I=function(){this.setMap(null)};ks.prototype.g=function(a){var b=this.b,c=this.f;a.vectorContext.Ic(Infinity,function(a){a.Aa(c.d,c.b);a.Ca(c.c);a.Ob(b,null)})};ks.prototype.J=function(){return this.b};function ms(a){null===a.a||null===a.e||null===a.c||a.a.O()}
-ks.prototype.setMap=function(a){null!==this.d&&(Yc(this.d),this.d=null,this.a.O(),this.a=null);this.a=a;null!==this.a&&(this.d=y(a,"postcompose",this.g,!1,this),ms(this))};function ns(a,b){fc.call(this,a);this.coordinate=b}t(ns,fc);function os(a){is.call(this);a=l(a)?a:{};this.d=new ks(l(a.style)?a.style:null);this.a=null;this.e=l(a.condition)?a.condition:dd}t(os,is);k=os.prototype;k.jb=function(a){if(Ci(a)){var b=this.d;a=a.pixel;b.e=this.a;b.c=a;b.b=ls(b);ms(b)}};k.J=function(){return this.d.J()};k.xe=ca;
-k.ab=function(a){if(!Ci(a))return!0;this.d.setMap(null);var b=a.pixel[0]-this.a[0],c=a.pixel[1]-this.a[1];64<=b*b+c*c&&(this.xe(a),this.dispatchEvent(new ns("boxend",a.coordinate)));return!1};k.$a=function(a){if(Ci(a)&&mc(a.a)&&this.e(a)){this.a=a.pixel;this.d.setMap(a.map);var b=this.d,c=this.a;b.e=this.a;b.c=c;b.b=ls(b);ms(b);this.dispatchEvent(new ns("boxstart",a.coordinate));return!0}return!1};k.kd=ed;function ps(a,b,c){this.e=a;this.b=b;this.f=c;this.a=[];this.c=this.d=0}ps.prototype.update=function(a,b){this.a.push(a,b,ua())};function qs(a,b){var c=a.e,d=a.c,e=a.b-d,f=rs(a);return df({source:b,duration:f,easing:function(a){return d*(Math.exp(c*a*f)-1)/e}})}function rs(a){return Math.log(a.b/a.c)/a.e};function ss(a){is.call(this);this.a=(l(a)?a:{}).kinetic;this.d=this.e=null;this.i=l(a.condition)?a.condition:zi;this.f=!1}t(ss,is);ss.prototype.jb=function(a){var b=js(this.b);this.a&&this.a.update(b[0],b[1]);if(null!==this.d){var c=this.d[0]-b[0],d=b[1]-this.d[1];a=a.map;var e=a.a(),f=Xe(e),d=c=[c,d],g=f.resolution;d[0]*=g;d[1]*=g;Bd(c,f.rotation);vd(c,f.center);c=e.g(c);a.O();e.Ka(c)}this.d=b};
-ss.prototype.ab=function(a){a=a.map;var b=a.a();if(0===this.b.length){var c;if(c=!this.f&&this.a)if(c=this.a,6>c.a.length)c=!1;else{var d=ua()-c.f,e=c.a.length-3;if(c.a[e+2]<d)c=!1;else{for(var f=e-3;0<f&&c.a[f+2]>d;)f-=3;var d=c.a[e+2]-c.a[f+2],g=c.a[e]-c.a[f],e=c.a[e+1]-c.a[f+1];c.d=Math.atan2(e,g);c.c=Math.sqrt(g*g+e*e)/d;c=c.c>c.b}}c&&(c=this.a,c=(c.b-c.c)/c.e,e=this.a.d,f=b.a(),this.e=qs(this.a,f),a.Ea(this.e),f=a.f(f),c=a.ta([f[0]-c*Math.cos(e),f[1]-c*Math.sin(e)]),c=b.g(c),b.Ka(c));Ze(b,-1);
-a.O();return!1}this.d=null;return!0};ss.prototype.$a=function(a){if(0<this.b.length&&this.i(a)){var b=a.map,c=b.a();this.d=null;this.l||Ze(c,1);b.O();null!==this.e&&ab(b.u,this.e)&&(c.Ka(a.frameState.viewState.center),this.e=null);this.a&&(a=this.a,a.a.length=0,a.d=0,a.c=0);this.f=1<this.b.length;return!0}return!1};function ts(a,b){this.x=a;this.y=b}t(ts,sf);ts.prototype.clone=function(){return new ts(this.x,this.y)};ts.prototype.scale=sf.prototype.scale;ts.prototype.add=function(a){this.x+=a.x;this.y+=a.y;return this};ts.prototype.rotate=function(a){var b=Math.cos(a);a=Math.sin(a);var c=this.y*b+this.x*a;this.x=this.x*b-this.y*a;this.y=c;return this};function us(a){a=l(a)?a:{};is.call(this);this.f=l(a.condition)?a.condition:Ai;this.a=this.d=void 0;this.e=0}t(us,is);us.prototype.jb=function(a){if(Ci(a)){var b=a.map,c=b.e();a=a.pixel;a=new ts(a[0]-c[0]/2,c[1]/2-a[1]);c=Math.atan2(a.y,a.x);a=Math.sqrt(a.x*a.x+a.y*a.y);var d=b.a(),e=Xe(d);b.O();l(this.d)&&kr(b,d,e.rotation-(c-this.d));this.d=c;l(this.a)&&mr(b,d,e.resolution/a*this.a);l(this.a)&&(this.e=this.a/a);this.a=a}};
-us.prototype.ab=function(a){if(!Ci(a))return!0;a=a.map;var b=a.a();Ze(b,-1);var c=Xe(b),d=this.e-1,e=c.rotation,e=b.constrainRotation(e,0);kr(a,b,e,void 0,void 0);c=c.resolution;c=b.constrainResolution(c,0,d);mr(a,b,c,void 0,400);this.e=0;return!1};us.prototype.$a=function(a){return Ci(a)&&this.f(a)?(Ze(a.map.a(),1),this.a=this.d=void 0,!0):!1};us.prototype.kd=ed;function vs(a){a=l(a)?a:{};is.call(this);this.d=l(a.condition)?a.condition:xi;this.a=void 0}t(vs,is);vs.prototype.jb=function(a){if(Ci(a)){var b=a.map,c=b.e();a=a.pixel;c=Math.atan2(c[1]/2-a[1],a[0]-c[0]/2);if(l(this.a)){a=c-this.a;var d=b.a(),e=Xe(d);b.O();kr(b,d,e.rotation-a)}this.a=c}};vs.prototype.ab=function(a){if(!Ci(a))return!0;a=a.map;var b=a.a();Ze(b,-1);var c=Xe(b).rotation,c=b.constrainRotation(c,0);kr(a,b,c,void 0,250);return!1};
-vs.prototype.$a=function(a){return Ci(a)&&mc(a.a)&&this.d(a)?(a=a.map,Ze(a.a(),1),a.O(),this.a=void 0,!0):!1};function ws(a){a=l(a)?a:{};os.call(this,{condition:l(a.condition)?a.condition:Ai,style:l(a.style)?a.style:new Li({stroke:new Ji({color:[0,0,255,1]})})})}t(ws,os);ws.prototype.xe=function(){var a=this.g,b=a.a(),c=this.J().s(),d=je(c),e=a.e(),c=b.i(c,e),c=b.constrainResolution(c,0,void 0);mr(a,b,c,d,200)};function xs(a,b,c){ac.call(this);this.d=a;this.b=c;this.a=b||window;this.c=sa(this.Vd,this)}t(xs,ac);k=xs.prototype;k.T=null;k.Pd=!1;k.start=function(){ys(this);this.Pd=!1;var a=zs(this),b=As(this);a&&!b&&this.a.mozRequestAnimationFrame?(this.T=y(this.a,"MozBeforePaint",this.c),this.a.mozRequestAnimationFrame(null),this.Pd=!0):this.T=a&&b?a.call(this.a,this.c):this.a.setTimeout(fd(this.c),20)};
-function ys(a){if(null!=a.T){var b=zs(a),c=As(a);b&&!c&&a.a.mozRequestAnimationFrame?Yc(a.T):b&&c?c.call(a.a,a.T):a.a.clearTimeout(a.T)}a.T=null}k.Vd=function(){this.Pd&&this.T&&Yc(this.T);this.T=null;this.d.call(this.b,ua())};k.I=function(){ys(this);xs.K.I.call(this)};function zs(a){a=a.a;return a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||null}
-function As(a){a=a.a;return a.cancelRequestAnimationFrame||a.webkitCancelRequestAnimationFrame||a.mozCancelRequestAnimationFrame||a.oCancelRequestAnimationFrame||a.msCancelRequestAnimationFrame||null};function Bs(){this.a=ua()}new Bs;Bs.prototype.set=function(a){this.a=a};Bs.prototype.get=function(){return this.a};function Cs(a){hd.call(this);this.zc=a||window;this.Tc=y(this.zc,"resize",this.mg,!1,this);this.Uc=Bf(this.zc||window)}t(Cs,hd);k=Cs.prototype;k.Tc=null;k.zc=null;k.Uc=null;k.I=function(){Cs.K.I.call(this);this.Tc&&(Yc(this.Tc),this.Tc=null);this.Uc=this.zc=null};k.mg=function(){var a=Bf(this.zc||window),b=this.Uc;a==b||a&&b&&a.width==b.width&&a.height==b.height||(this.Uc=a,this.dispatchEvent("resize"))};function Ds(a,b,c,d,e){if(!(ub||wb&&Eb("525")))return!0;if(ob&&e)return Es(a);if(e&&!d)return!1;ka(b)&&(b=Fs(b));if(!c&&(17==b||18==b||ob&&91==b))return!1;if(wb&&d&&c)switch(a){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return!1}if(ub&&d&&b==a)return!1;switch(a){case 13:return!0;case 27:return!wb}return Es(a)}
-function Es(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||wb&&0==a)return!0;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;default:return!1}}function Fs(a){if(vb)a=Gs(a);else if(ob&&wb)a:switch(a){case 93:a=91;break a}return a}
-function Gs(a){switch(a){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return a}};function Hs(a,b){hd.call(this);a&&Is(this,a,b)}t(Hs,hd);k=Hs.prototype;k.qc=null;k.Yc=null;k.Ad=null;k.Zc=null;k.xa=-1;k.kb=-1;k.td=!1;
-var Js={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},Ks={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},Ls=ub||wb&&Eb("525"),Ms=ob&&vb;
-Hs.prototype.a=function(a){wb&&(17==this.xa&&!a.l||18==this.xa&&!a.c||ob&&91==this.xa&&!a.p)&&(this.kb=this.xa=-1);-1==this.xa&&(a.l&&17!=a.f?this.xa=17:a.c&&18!=a.f?this.xa=18:a.p&&91!=a.f&&(this.xa=91));Ls&&!Ds(a.f,this.xa,a.e,a.l,a.c)?this.handleEvent(a):(this.kb=Fs(a.f),Ms&&(this.td=a.c))};Hs.prototype.c=function(a){this.kb=this.xa=-1;this.td=a.c};
-Hs.prototype.handleEvent=function(a){var b=a.a,c,d,e=b.altKey;ub&&"keypress"==a.type?(c=this.kb,d=13!=c&&27!=c?b.keyCode:0):wb&&"keypress"==a.type?(c=this.kb,d=0<=b.charCode&&63232>b.charCode&&Es(c)?b.charCode:0):sb?(c=this.kb,d=Es(c)?b.keyCode:0):(c=b.keyCode||this.kb,d=b.charCode||0,Ms&&(e=this.td),ob&&63==d&&224==c&&(c=191));var f=c=Fs(c),g=b.keyIdentifier;c?63232<=c&&c in Js?f=Js[c]:25==c&&a.e&&(f=9):g&&g in Ks&&(f=Ks[g]);this.xa=f;a=new Ns(f,d,0,b);a.c=e;this.dispatchEvent(a)};
-function Is(a,b,c){a.Zc&&Os(a);a.qc=b;a.Yc=y(a.qc,"keypress",a,c);a.Ad=y(a.qc,"keydown",a.a,c,a);a.Zc=y(a.qc,"keyup",a.c,c,a)}function Os(a){a.Yc&&(Yc(a.Yc),Yc(a.Ad),Yc(a.Zc),a.Yc=null,a.Ad=null,a.Zc=null);a.qc=null;a.xa=-1;a.kb=-1}Hs.prototype.I=function(){Hs.K.I.call(this);Os(this)};function Ns(a,b,c,d){jc.call(this,d);this.type="key";this.f=a;this.k=b}t(Ns,jc);function Ps(a,b){hd.call(this);var c=this.a=a;(c=ma(c)&&1==c.nodeType?this.a:this.a?this.a.body:null)&&Sf(c,"direction");this.c=y(this.a,vb?"DOMMouseScroll":"mousewheel",this,b)}t(Ps,hd);
-Ps.prototype.handleEvent=function(a){var b=0,c=0,d=0;a=a.a;if("mousewheel"==a.type){c=1;if(ub||wb&&(pb||Eb("532.0")))c=40;d=Qs(-a.wheelDelta,c);l(a.wheelDeltaX)?(b=Qs(-a.wheelDeltaX,c),c=Qs(-a.wheelDeltaY,c)):c=d}else d=a.detail,100<d?d=3:-100>d&&(d=-3),l(a.axis)&&a.axis===a.HORIZONTAL_AXIS?b=d:c=d;ka(this.b)&&Jb(b,-this.b,this.b);ka(this.d)&&(c=Jb(c,-this.d,this.d));b=new Rs(d,a,0,c);this.dispatchEvent(b)};function Qs(a,b){return wb&&(ob||qb)&&0!=a%b?a:a/b}
-Ps.prototype.I=function(){Ps.K.I.call(this);Yc(this.c);this.c=null};function Rs(a,b,c,d){jc.call(this,b);this.type="mousewheel";this.detail=a;this.q=d}t(Rs,jc);function Ss(a){ld.call(this);this.i=Be(a.projection);this.f=l(a.attributions)?a.attributions:null;this.q=a.logo;this.l=l(a.state)?a.state:"ready"}t(Ss,ld);k=Ss.prototype;k.ed=ca;k.U=function(){return this.f};k.S=function(){return this.q};k.V=function(){return this.i};k.W=function(){return this.l};function Ts(a,b){a.l=b;a.n()};function O(a){pd.call(this);a=Fc(a);a.brightness=l(a.brightness)?a.brightness:0;a.contrast=l(a.contrast)?a.contrast:1;a.hue=l(a.hue)?a.hue:0;a.opacity=l(a.opacity)?a.opacity:1;a.saturation=l(a.saturation)?a.saturation:1;a.visible=l(a.visible)?a.visible:!0;a.maxResolution=l(a.maxResolution)?a.maxResolution:Infinity;a.minResolution=l(a.minResolution)?a.minResolution:0;this.L(a)}t(O,pd);O.prototype.d=function(){return this.get("brightness")};O.prototype.getBrightness=O.prototype.d;O.prototype.e=function(){return this.get("contrast")};
-O.prototype.getContrast=O.prototype.e;O.prototype.f=function(){return this.get("hue")};O.prototype.getHue=O.prototype.f;function Us(a){var b=a.d(),c=a.e(),d=a.f(),e=a.k(),f=a.l(),g=a.Va(),h=a.b(),m=a.s(),n=a.g(),p=a.i();return{layer:a,brightness:l(b)?Jb(b,-1,1):0,contrast:l(c)?Math.max(c,0):1,hue:l(d)?d:0,opacity:l(e)?Jb(e,0,1):1,saturation:l(f)?Math.max(f,0):1,Lb:g,visible:l(h)?!!h:!0,extent:m,maxResolution:l(n)?n:Infinity,minResolution:l(p)?Math.max(p,0):0}}O.prototype.s=function(){return this.get("extent")};
-O.prototype.getExtent=O.prototype.s;O.prototype.g=function(){return this.get("maxResolution")};O.prototype.getMaxResolution=O.prototype.g;O.prototype.i=function(){return this.get("minResolution")};O.prototype.getMinResolution=O.prototype.i;O.prototype.k=function(){return this.get("opacity")};O.prototype.getOpacity=O.prototype.k;O.prototype.l=function(){return this.get("saturation")};O.prototype.getSaturation=O.prototype.l;O.prototype.b=function(){return this.get("visible")};
-O.prototype.getVisible=O.prototype.b;O.prototype.q=function(a){this.set("brightness",a)};O.prototype.setBrightness=O.prototype.q;O.prototype.r=function(a){this.set("contrast",a)};O.prototype.setContrast=O.prototype.r;O.prototype.u=function(a){this.set("hue",a)};O.prototype.setHue=O.prototype.u;O.prototype.o=function(a){this.set("extent",a)};O.prototype.setExtent=O.prototype.o;O.prototype.D=function(a){this.set("maxResolution",a)};O.prototype.setMaxResolution=O.prototype.D;
-O.prototype.F=function(a){this.set("minResolution",a)};O.prototype.setMinResolution=O.prototype.F;O.prototype.Q=function(a){this.set("opacity",a)};O.prototype.setOpacity=O.prototype.Q;O.prototype.X=function(a){this.set("saturation",a)};O.prototype.setSaturation=O.prototype.X;O.prototype.ba=function(a){this.set("visible",a)};O.prototype.setVisible=O.prototype.ba;function P(a){var b=Fc(a);delete b.source;O.call(this,b);this.a=a.source;y(this.a,"change",this.sd,!1,this)}t(P,O);P.prototype.Oa=function(a){a=l(a)?a:[];a.push(Us(this));return a};P.prototype.ma=function(){return this.a};P.prototype.Va=function(){return this.a.l};P.prototype.sd=function(){this.n()};function Vs(a){this.minZoom=l(a.minZoom)?a.minZoom:0;this.a=a.resolutions;this.maxZoom=this.a.length-1;this.e=l(a.origin)?a.origin:null;this.g=null;l(a.origins)&&(this.g=a.origins);this.b=null;l(a.tileSizes)&&(this.b=a.tileSizes);this.f=l(a.tileSize)?a.tileSize:null===this.b?256:void 0}var Ws=[0,0,0];k=Vs.prototype;k.Lc=function(a,b,c,d,e){e=Xs(this,a,e);for(a=a[0]-1;a>=this.minZoom;){if(b.call(c,a,Ys(this,e,a,d)))return!0;--a}return!1};k.Oc=function(){return this.maxZoom};k.Pc=function(){return this.minZoom};
-k.mb=function(a){return null===this.e?this.g[a]:this.e};k.fa=function(a){return this.a[a]};k.fd=function(){return this.a};k.Sc=function(a,b,c){return a[0]<this.maxZoom?(c=Xs(this,a,c),Ys(this,c,a[0]+1,b)):null};function Zs(a,b,c,d){$s(a,b[0],b[1],c,!1,Ws);var e=Ws[1],f=Ws[2];$s(a,b[2],b[3],c,!0,Ws);return mf(e,Ws[1],f,Ws[2],d)}function Ys(a,b,c,d){return Zs(a,b,a.fa(c),d)}function at(a,b){var c=a.mb(b[0]),d=a.fa(b[0]),e=a.ia(b[0]);return[c[0]+(b[1]+.5)*e*d,c[1]+(b[2]+.5)*e*d]}
-function Xs(a,b,c){var d=a.mb(b[0]),e=a.fa(b[0]);a=a.ia(b[0]);var f=d[0]+b[1]*a*e;b=d[1]+b[2]*a*e;return Ud(f,b,f+a*e,b+a*e,c)}function $s(a,b,c,d,e,f){var g=Qb(a.a,d,0),h=d/a.fa(g),m=a.mb(g);a=a.ia(g);b=h*(b-m[0])/(d*a);c=h*(c-m[1])/(d*a);e?(b=Math.ceil(b)-1,c=Math.ceil(c)-1):(b=Math.floor(b),c=Math.floor(c));return gf(g,b,c,f)}k.ia=function(a){return l(this.f)?this.f:this.b[a]};
-function bt(a,b,c){b=l(b)?b:42;c=l(c)?c:256;a=Math.max(fe(a)/c,ge(a)/c);b+=1;c=Array(b);for(var d=0;d<b;++d)c[d]=a/Math.pow(2,d);return c}function ct(a){a=Be(a);var b=a.s();null===b&&(a=180*xe.degrees/a.ae(),b=Ud(-a,-a,a,a));return b};function dt(a){Ss.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state});this.u=l(a.opaque)?a.opaque:!1;this.D=l(a.tilePixelRatio)?a.tilePixelRatio:1;this.tileGrid=l(a.tileGrid)?a.tileGrid:null}t(dt,Ss);k=dt.prototype;k.Ed=cd;k.vd=function(a,b,c,d){var e=!0,f,g,h,m;for(h=d.a;h<=d.d;++h)for(m=d.b;m<=d.c;++m)g=this.hb(c,h,m),a[c]&&a[c][g]||(f=b(c,h,m),null===f?e=!1:(a[c]||(a[c]={}),a[c][g]=f));return e};k.Mc=function(){return 0};k.hb=hf;k.wa=function(){return this.tileGrid};
-function et(a,b){var c;if(null===a.tileGrid){if(c=b.e,null===c){c=ct(b);var d=l(void 0)?void 0:256,e=l(void 0)?void 0:"bottom-left",f=bt(c,void 0,d);c=new Vs({origin:ke(c,e),resolutions:f,tileSize:d});b.e=c}}else c=a.tileGrid;return c}k.pc=function(a,b,c){return et(this,c).ia(a)*this.D};k.Oe=ca;function ft(a,b){ac.call(this);this.d=a;this.a=b}t(ft,ac);ft.prototype.f=ca;ft.prototype.p=function(a){2===a.target.state&&gt(this)};function gt(a){var b=a.a;b.b()&&"ready"==b.Va()&&a.d.f.O()}function ht(a,b){b.Ed()&&a.postRenderFunctions.push(ta(function(a,b,e){b=na(a).toString();a.se(e.usedTiles[b])},b))}function it(a,b){if(null!=b){var c,d,e;d=0;for(e=b.length;d<e;++d)c=b[d],a[na(c).toString()]=c}}function jt(a,b){var c=b.q;l(c)&&(ja(c)?a.logos[c]="":ma(c)&&(a.logos[c.src]=c.href))}
-function kt(a,b,c,d){b=na(b).toString();c=c.toString();b in a?c in a[b]?(a=a[b][c],d.a<a.a&&(a.a=d.a),d.d>a.d&&(a.d=d.d),d.b<a.b&&(a.b=d.b),d.c>a.c&&(a.c=d.c)):a[b][c]=d:(a[b]={},a[b][c]=d)}function lt(a,b,c,d){return function(e,f,g){e=b.Rb(e,f,g,c,d);return a(e)?e:null}}function mt(a,b,c){return[b*(Math.round(a[0]/b)+c[0]%2/2),b*(Math.round(a[1]/b)+c[1]%2/2)]}
-function nt(a,b,c,d,e,f,g,h,m,n){var p=na(b).toString();p in a.wantedTiles||(a.wantedTiles[p]={});var r=a.wantedTiles[p];a=a.tileQueue;var q=c.minZoom,u,x,B,E,F,w;l(h)||(h=0);for(w=g;w>=q;--w)for(x=Ys(c,f,w,x),B=c.fa(w),E=x.a;E<=x.d;++E)for(F=x.b;F<=x.c;++F)g-w<=h?(u=b.Rb(w,E,F,d,e),0==u.state&&(r[kf(u.a)]=!0,u.d()in a.b||ot(a,[u,p,at(c,u.a),B])),l(m)&&m.call(n,u)):b.Oe(w,E,F)};function pt(a,b,c,d,e,f,g,h){Md(a);0===b&&0===c||Od(a,b,c);1==d&&1==e||Pd(a,d,e);0!==f&&Qd(a,f);0===g&&0===h||Od(a,g,h);return a}function qt(a,b){return a[0]==b[0]&&a[1]==b[1]&&a[4]==b[4]&&a[5]==b[5]&&a[12]==b[12]&&a[13]==b[13]}function rt(a,b,c){var d=a[1],e=a[5],f=a[13],g=b[0];b=b[1];c[0]=a[0]*g+a[4]*b+a[12];c[1]=d*g+e*b+f;return c};function st(a,b){ac.call(this);this.f=b;this.k=null;this.b={}}t(st,ac);
-function tt(a){var b=a.viewState,c=a.coordinateToPixelMatrix;pt(c,a.size[0]/2,a.size[1]/2,1/b.resolution,-1/b.resolution,-b.rotation,-b.center[0],-b.center[1]);a=a.pixelToCoordinateMatrix;var b=c[0],d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],m=c[6],n=c[7],p=c[8],r=c[9],q=c[10],u=c[11],x=c[12],B=c[13],E=c[14],c=c[15],F=b*h-d*g,w=b*m-e*g,U=b*n-f*g,Q=d*m-e*h,ea=d*n-f*h,Y=e*n-f*m,za=p*B-r*x,kb=p*E-q*x,Aa=p*c-u*x,Ab=r*E-q*B,tb=r*c-u*B,Ba=q*c-u*E,Ia=F*Ba-w*tb+U*Ab+Q*Aa-ea*kb+Y*za;0!=Ia&&(Ia=1/Ia,a[0]=(h*Ba-m*tb+
-n*Ab)*Ia,a[1]=(-d*Ba+e*tb-f*Ab)*Ia,a[2]=(B*Y-E*ea+c*Q)*Ia,a[3]=(-r*Y+q*ea-u*Q)*Ia,a[4]=(-g*Ba+m*Aa-n*kb)*Ia,a[5]=(b*Ba-e*Aa+f*kb)*Ia,a[6]=(-x*Y+E*U-c*w)*Ia,a[7]=(p*Y-q*U+u*w)*Ia,a[8]=(g*tb-h*Aa+n*za)*Ia,a[9]=(-b*tb+d*Aa-f*za)*Ia,a[10]=(x*ea-B*U+c*F)*Ia,a[11]=(-p*ea+r*U-u*F)*Ia,a[12]=(-g*Ab+h*kb-m*za)*Ia,a[13]=(b*Ab-d*kb+e*za)*Ia,a[14]=(-x*Q+B*w-E*F)*Ia,a[15]=(p*Q-r*w+q*F)*Ia)}st.prototype.Gc=function(a){return new ft(this,a)};st.prototype.I=function(){sc(this.b,ec);st.K.I.call(this)};
-function ut(){var a=Sn.gb();if(32<a.c){var b=0,c,d;for(c in a.a){d=a.a[c];var e;if(e=0===(b++&3))oc(d)?d=jd(d,void 0,void 0):(d=Sc(d),d=!!d&&Mc(d,void 0,void 0)),e=!d;e&&(delete a.a[c],--a.c)}}}
-function vt(a,b,c,d,e,f,g){var h,m=c.extent,n=c.viewState,p=n.resolution,n=n.rotation;if(null!==a.k){var r={};if(h=wt(a.k,m,p,n,b,{},function(a,b){var c=na(b).toString();if(!(c in r))return r[c]=!0,d.call(e,b,null)}))return h}m=a.f.ib().Oa();for(n=m.length-1;0<=n;--n){h=m[n];var q=h.layer;if(h.visible&&p>=h.minResolution&&p<h.maxResolution&&f.call(g,q)&&(h=xt(a,q).f(b,c,d,e)))return h}}function xt(a,b){var c=na(b).toString();if(c in a.b)return a.b[c];var d=a.Gc(b);return a.b[c]=d}
-st.prototype.hd=ca;st.prototype.u=function(a,b){for(var c in this.b)if(!(null!==b&&c in b.layerStates)){var d=this.b[c];delete this.b[c];ec(d)}};function yt(a,b){for(var c in a.b)if(!(c in b.layerStates)){b.postRenderFunctions.push(sa(a.u,a));break}};function zt(a,b){this.f=a;this.e=b;this.a=[];this.c=[];this.b={}}zt.prototype.clear=function(){this.a.length=0;this.c.length=0;Cc(this.b)};function At(a){var b=a.a,c=a.c,d=b[0];1==b.length?(b.length=0,c.length=0):(b[0]=b.pop(),c[0]=c.pop(),Bt(a,0));b=a.e(d);delete a.b[b];return d}function ot(a,b){var c=a.f(b);Infinity!=c&&(a.a.push(b),a.c.push(c),a.b[a.e(b)]=!0,Ct(a,0,a.a.length-1))}zt.prototype.fb=function(){return this.a.length};zt.prototype.ka=function(){return 0===this.a.length};
-function Bt(a,b){for(var c=a.a,d=a.c,e=c.length,f=c[b],g=d[b],h=b;b<e>>1;){var m=2*b+1,n=2*b+2,m=n<e&&d[n]<d[m]?n:m;c[b]=c[m];d[b]=d[m];b=m}c[b]=f;d[b]=g;Ct(a,h,b)}function Ct(a,b,c){var d=a.a;a=a.c;for(var e=d[c],f=a[c];c>b;){var g=c-1>>1;if(a[g]>f)d[c]=d[g],a[c]=a[g],c=g;else break}d[c]=e;a[c]=f}function Dt(a){var b=a.f,c=a.a,d=a.c,e=0,f=c.length,g,h,m;for(h=0;h<f;++h)g=c[h],m=b(g),Infinity==m?delete a.b[a.e(g)]:(d[e]=m,c[e++]=g);c.length=e;d.length=e;for(b=(a.a.length>>1)-1;0<=b;b--)Bt(a,b)};function Et(a,b){zt.call(this,function(b){return a.apply(null,b)},function(a){return a[0].d()});this.i=b;this.d=0}t(Et,zt);Et.prototype.g=function(){--this.d;this.i()};function Ft(a){jr.call(this);a=l(a)?a:{};this.a=l(a.condition)?a.condition:gd(zi,Bi);this.b=l(a.pixelDelta)?a.pixelDelta:128}t(Ft,jr);Ft.prototype.La=function(a){var b=!1;if("key"==a.type){var c=a.a.f;if(this.a(a)&&(40==c||37==c||39==c||38==c)){var d=a.map,b=d.a(),e=Xe(b),f=e.resolution*this.b,g=0,h=0;40==c?h=-f:37==c?g=-f:39==c?g=f:h=f;c=[g,h];Bd(c,e.rotation);e=b.a();l(e)&&(l(100)&&d.Ea(df({source:e,duration:100,easing:bf})),d=b.g([e[0]+c[0],e[1]+c[1]]),b.Ka(d));a.preventDefault();b=!0}}return!b};function Gt(a){jr.call(this);a=l(a)?a:{};this.b=l(a.condition)?a.condition:Bi;this.a=l(a.delta)?a.delta:1;this.d=l(a.duration)?a.duration:100}t(Gt,jr);Gt.prototype.La=function(a){var b=!1;if("key"==a.type){var c=a.a.k;if(this.b(a)&&(43==c||45==c)){b=a.map;c=43==c?this.a:-this.a;b.O();var d=b.a();lr(b,d,c,void 0,this.d);a.preventDefault();b=!0}}return!b};function Ht(a){a=l(a)?a:{};jr.call(this);this.a=0;this.i=l(a.duration)?a.duration:250;this.d=null;this.e=this.b=void 0}t(Ht,jr);Ht.prototype.La=function(a){var b=!1;if("mousewheel"==a.type){var b=a.map,c=a.a;this.d=a.coordinate;this.a+=c.q;l(this.b)||(this.b=ua());c=Math.max(80-(ua()-this.b),0);ba.clearTimeout(this.e);this.e=ba.setTimeout(sa(this.f,this,b),c);a.preventDefault();b=!0}return!b};
-Ht.prototype.f=function(a){var b=Jb(this.a,-1,1),c=a.a();a.O();lr(a,c,-b,this.d,this.i);this.a=0;this.d=null;this.e=this.b=void 0};function It(a){is.call(this);a=l(a)?a:{};this.d=null;this.e=void 0;this.a=!1;this.f=0;this.i=l(a.threshold)?a.threshold:.3}t(It,is);It.prototype.jb=function(a){var b=0,c=this.b[0],d=this.b[1],c=Math.atan2(d.clientY-c.clientY,d.clientX-c.clientX);l(this.e)&&(b=c-this.e,this.f+=b,!this.a&&Math.abs(this.f)>this.i&&(this.a=!0));this.e=c;a=a.map;c=Zf(a.b);d=js(this.b);d[0]-=c.x;d[1]-=c.y;this.d=a.ta(d);this.a&&(c=a.a(),d=Xe(c),a.O(),kr(a,c,d.rotation+b,this.d))};
-It.prototype.ab=function(a){if(2>this.b.length){a=a.map;var b=a.a();Ze(b,-1);if(this.a){var c=Xe(b).rotation,d=this.d,c=b.constrainRotation(c,0);kr(a,b,c,d,250)}return!1}return!0};It.prototype.$a=function(a){return 2<=this.b.length?(a=a.map,this.d=null,this.e=void 0,this.a=!1,this.f=0,this.l||Ze(a.a(),1),a.O(),!0):!1};function Jt(a){a=l(a)?a:{};is.call(this);this.d=null;this.f=l(a.duration)?a.duration:400;this.a=void 0;this.e=1}t(Jt,is);Jt.prototype.jb=function(a){var b=1,c=this.b[0],d=this.b[1],e=c.clientX-d.clientX,c=c.clientY-d.clientY,e=Math.sqrt(e*e+c*c);l(this.a)&&(b=this.a/e);this.a=e;1!=b&&(this.e=b);a=a.map;var e=a.a(),c=Xe(e),d=Zf(a.b),f=js(this.b);f[0]-=d.x;f[1]-=d.y;this.d=a.ta(f);a.O();mr(a,e,c.resolution*b,this.d)};
-Jt.prototype.ab=function(a){if(2>this.b.length){a=a.map;var b=a.a();Ze(b,-1);var c=Xe(b).resolution,d=this.d,e=this.f,c=b.constrainResolution(c,0,this.e-1);mr(a,b,c,d,e);return!1}return!0};Jt.prototype.$a=function(a){return 2<=this.b.length?(a=a.map,this.d=null,this.a=void 0,this.e=1,this.l||Ze(a.a(),1),a.O(),!0):!1};function Kt(a){a=l(a)?a:{};var b=new A,c=new ps(-.005,.05,100);(l(a.altShiftDragRotate)?a.altShiftDragRotate:1)&&b.push(new vs);(l(a.doubleClickZoom)?a.doubleClickZoom:1)&&b.push(new nr({delta:a.zoomDelta,duration:a.zoomDuration}));(l(a.dragPan)?a.dragPan:1)&&b.push(new ss({kinetic:c}));(l(a.pinchRotate)?a.pinchRotate:1)&&b.push(new It);(l(a.pinchZoom)?a.pinchZoom:1)&&b.push(new Jt({duration:a.zoomDuration}));if(l(a.keyboard)?a.keyboard:1)b.push(new Ft),b.push(new Gt({delta:a.zoomDelta,duration:a.zoomDuration}));
-(l(a.mouseWheelZoom)?a.mouseWheelZoom:1)&&b.push(new Ht({duration:a.zoomDuration}));(l(a.shiftDragZoom)?a.shiftDragZoom:1)&&b.push(new ws);return b};function R(a){var b=l(a)?a:{};a=Fc(b);delete a.layers;b=b.layers;O.call(this,a);this.a=null;y(this,td("layers"),this.dg,!1,this);l(b)?ha(b)&&(b=new A(cb(b))):b=new A;this.aa(b)}t(R,O);k=R.prototype;k.ce=function(){this.b()&&this.n()};
-k.dg=function(){null!==this.a&&(Ta(vc(this.a),Yc),this.a=null);var a=this.Ab();if(null!=a){this.a={add:y(a,"add",this.cg,!1,this),remove:y(a,"remove",this.eg,!1,this)};var a=a.a,b,c,d;b=0;for(c=a.length;b<c;b++)d=a[b],this.a[na(d).toString()]=y(d,["propertychange","change"],this.ce,!1,this)}this.n()};k.cg=function(a){a=a.element;this.a[na(a).toString()]=y(a,["propertychange","change"],this.ce,!1,this);this.n()};k.eg=function(a){a=na(a.element).toString();Yc(this.a[a]);delete this.a[a];this.n()};
-k.Ab=function(){return this.get("layers")};R.prototype.getLayers=R.prototype.Ab;R.prototype.aa=function(a){this.set("layers",a)};R.prototype.setLayers=R.prototype.aa;
-R.prototype.Oa=function(a){var b=l(a)?a:[],c=b.length;this.Ab().forEach(function(a){a.Oa(b)});a=Us(this);var d,e;for(d=b.length;c<d;c++)e=b[c],e.brightness=Jb(e.brightness+a.brightness,-1,1),e.contrast*=a.contrast,e.hue+=a.hue,e.opacity*=a.opacity,e.saturation*=a.saturation,e.visible=e.visible&&a.visible,e.maxResolution=Math.min(e.maxResolution,a.maxResolution),e.minResolution=Math.max(e.minResolution,a.minResolution),l(a.extent)&&l(e.extent)&&(e.extent=oe(e.extent,a.extent));return b};
-R.prototype.Va=function(){return"ready"};function Lt(a){ye.call(this,{code:a,units:"m",extent:Mt,global:!0,worldExtent:Nt})}t(Lt,ye);Lt.prototype.yd=function(a,b){var c=b[1]/6378137;return a/((Math.exp(c)+Math.exp(-c))/2)};var Ot=6378137*Math.PI,Mt=[-Ot,-Ot,Ot,Ot],Nt=[-180,-85,180,85],Je=Va("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".split(" "),function(a){return new Lt(a)});
-function Ke(a,b,c){var d=a.length;c=1<c?c:2;l(b)||(2<c?b=a.slice():b=Array(d));for(var e=0;e<d;e+=c)b[e]=6378137*Math.PI*a[e]/180,b[e+1]=6378137*Math.log(Math.tan(Math.PI*(a[e+1]+90)/360));return b}function Le(a,b,c){var d=a.length;c=1<c?c:2;l(b)||(2<c?b=a.slice():b=Array(d));for(var e=0;e<d;e+=c)b[e]=180*a[e]/(6378137*Math.PI),b[e+1]=360*Math.atan(Math.exp(a[e+1]/6378137))/Math.PI-90;return b};function Pt(a,b){ye.call(this,{code:a,units:"degrees",extent:Qt,axisOrientation:b,global:!0,worldExtent:Qt})}t(Pt,ye);Pt.prototype.yd=function(a){return a};var Qt=[-180,-90,180,90],Me=[new Pt("CRS:84"),new Pt("EPSG:4326","neu"),new Pt("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new Pt("urn:ogc:def:crs:OGC:1.3:CRS84"),new Pt("urn:ogc:def:crs:OGC:2:84"),new Pt("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new Pt("urn:x-ogc:def:crs:EPSG:4326","neu")];function Rt(){Ee(Je);Ee(Me);Ie()};function S(a){P.call(this,a)}t(S,P);function T(a){P.call(this,a)}t(T,P);T.prototype.aa=function(){return this.get("preload")};T.prototype.getPreload=T.prototype.aa;T.prototype.na=function(a){this.set("preload",a)};T.prototype.setPreload=T.prototype.na;T.prototype.ea=function(){return this.get("useInterimTilesOnError")};T.prototype.getUseInterimTilesOnError=T.prototype.ea;T.prototype.sa=function(a){this.set("useInterimTilesOnError",a)};T.prototype.setUseInterimTilesOnError=T.prototype.sa;function V(a){a=l(a)?a:{};var b=Fc(a);delete b.style;P.call(this,b);this.cb=null;this.ea=void 0;this.na(a.style)}t(V,P);V.prototype.ac=function(){return this.cb};V.prototype.Cc=function(){return this.ea};V.prototype.na=function(a){this.cb=l(a)?a:Ni;this.ea=null===a?void 0:Mi(this.cb);this.n()};function St(a,b,c,d,e){this.p={};this.b=a;this.r=b;this.e=c;this.D=d;this.cb=e;this.f=this.a=this.c=this.ma=this.ea=this.aa=null;this.qb=this.bb=this.o=this.X=this.Q=this.F=0;this.Oa=!1;this.g=this.na=0;this.Ib=!1;this.ba=0;this.d="";this.l=this.u=this.Va=this.sa=0;this.ga=this.k=this.i=null;this.q=[];this.Jb=Id()}
-function Tt(a,b,c){if(null!==a.f){b=Qi(b,0,c,2,a.D,a.q);c=a.b;var d=a.Jb,e=c.globalAlpha;1!=a.o&&(c.globalAlpha=e*a.o);var f=a.na;a.Oa&&(f+=a.cb);var g,h;g=0;for(h=b.length;g<h;g+=2){var m=b[g]-a.F,n=b[g+1]-a.Q;a.Ib&&(m=m+.5|0,n=n+.5|0);if(0!==f||1!=a.g){var p=m+a.F,r=n+a.Q;pt(d,p,r,a.g,a.g,f,-p,-r);c.setTransform(d[0],d[1],d[4],d[5],d[12],d[13])}c.drawImage(a.f,a.bb,a.qb,a.ba,a.X,m,n,a.ba,a.X)}0===f&&1==a.g||c.setTransform(1,0,0,1,0,0);1!=a.o&&(c.globalAlpha=e)}}
-function Ut(a,b,c,d){var e=0;if(null!==a.ga&&""!==a.d){null===a.i||Vt(a,a.i);null===a.k||Wt(a,a.k);var f=a.ga,g=a.b,h=a.ma;null===h?(g.font=f.font,g.textAlign=f.textAlign,g.textBaseline=f.textBaseline,a.ma={font:f.font,textAlign:f.textAlign,textBaseline:f.textBaseline}):(h.font!=f.font&&(h.font=g.font=f.font),h.textAlign!=f.textAlign&&(h.textAlign=g.textAlign=f.textAlign),h.textBaseline!=f.textBaseline&&(h.textBaseline=g.textBaseline=f.textBaseline));b=Qi(b,e,c,d,a.D,a.q);for(f=a.b;e<c;e+=d){g=b[e]+
-a.sa;h=b[e+1]+a.Va;if(0!==a.u||1!=a.l){var m=pt(a.Jb,g,h,a.l,a.l,a.u,-g,-h);f.setTransform(m[0],m[1],m[4],m[5],m[12],m[13])}null===a.k||f.strokeText(a.d,g,h);null===a.i||f.fillText(a.d,g,h)}0===a.u&&1==a.l||f.setTransform(1,0,0,1,0,0)}}function Xt(a,b,c,d,e,f){var g=a.b;a=Qi(b,c,d,e,a.D,a.q);g.moveTo(a[0],a[1]);for(b=2;b<a.length;b+=2)g.lineTo(a[b],a[b+1]);f&&g.lineTo(a[0],a[1]);return d}function Yt(a,b,c,d,e){var f=a.b,g,h;g=0;for(h=d.length;g<h;++g)c=Xt(a,b,c,d[g],e,!0),f.closePath();return c}
-k=St.prototype;k.Ic=function(a,b){var c=a.toString(),d=this.p[c];l(d)?d.push(b):this.p[c]=[b]};k.jc=function(a){if(pe(this.e,a.s())){if(null!==this.c||null!==this.a){null===this.c||Vt(this,this.c);null===this.a||Wt(this,this.a);var b;b=a.j;b=null===b?null:Qi(b,0,b.length,a.a,this.D,this.q);var c=b[2]-b[0],d=b[3]-b[1],c=Math.sqrt(c*c+d*d),d=this.b;d.beginPath();d.arc(b[0],b[1],c,0,2*Math.PI);null===this.c||d.fill();null===this.a||d.stroke()}""!==this.d&&Ut(this,a.Dd(),2,2)}};
-k.Wd=function(a,b){var c=a.J();if(null!=c&&pe(this.e,c.s())){var d=b.a;l(d)||(d=0);this.Ic(d,function(a){a.Aa(b.d,b.b);a.$b(b.e);a.Ca(b.c);Zt[c.G()].call(a,c,null)})}};k.Xd=function(a,b){var c=a.e,d,e;d=0;for(e=c.length;d<e;++d){var f=c[d];Zt[f.G()].call(this,f,b)}};k.nc=function(a){var b=a.j;a=a.a;null===this.f||Tt(this,b,b.length);""!==this.d&&Ut(this,b,b.length,a)};k.lc=function(a){var b=a.j;a=a.a;null===this.f||Tt(this,b,b.length);""!==this.d&&Ut(this,b,b.length,a)};
-k.sb=function(a){if(pe(this.e,a.s())){if(null!==this.a){Wt(this,this.a);var b=this.b,c=a.j;b.beginPath();Xt(this,c,0,c.length,a.a,!1);b.stroke()}""!==this.d&&(a=tj(a),Ut(this,a,2,2))}};k.kc=function(a){var b=a.s();if(pe(this.e,b)){if(null!==this.a){Wt(this,this.a);var b=this.b,c=a.j,d=0,e=a.d,f=a.a;b.beginPath();var g,h;g=0;for(h=e.length;g<h;++g)d=Xt(this,c,d,e[g],f,!1);b.stroke()}""!==this.d&&(a=wj(a),Ut(this,a,a.length,2))}};
-k.Ob=function(a){if(pe(this.e,a.s())){if(null!==this.a||null!==this.c){null===this.c||Vt(this,this.c);null===this.a||Wt(this,this.a);var b=this.b;b.beginPath();Yt(this,Mj(a),0,a.d,a.a);null===this.c||b.fill();null===this.a||b.stroke()}""!==this.d&&(a=Nj(a),Ut(this,a,2,2))}};
-k.mc=function(a){if(pe(this.e,a.s())){if(null!==this.a||null!==this.c){null===this.c||Vt(this,this.c);null===this.a||Wt(this,this.a);var b=this.b,c=Rj(a),d=0,e=a.d,f=a.a,g,h;g=0;for(h=e.length;g<h;++g){var m=e[g];b.beginPath();d=Yt(this,c,d,m,f);null===this.c||b.fill();null===this.a||b.stroke()}}""!==this.d&&(a=Sj(a),Ut(this,a,a.length,2))}};function $t(a){var b=Va(xc(a.p),Number);gb(b);var c,d,e,f,g;c=0;for(d=b.length;c<d;++c)for(e=a.p[b[c].toString()],f=0,g=e.length;f<g;++f)e[f](a)}
-function Vt(a,b){var c=a.b,d=a.aa;null===d?(c.fillStyle=b.fillStyle,a.aa={fillStyle:b.fillStyle}):d.fillStyle!=b.fillStyle&&(d.fillStyle=c.fillStyle=b.fillStyle)}
-function Wt(a,b){var c=a.b,d=a.ea;null===d?(c.lineCap=b.lineCap,ug&&c.setLineDash(b.lineDash),c.lineJoin=b.lineJoin,c.lineWidth=b.lineWidth,c.miterLimit=b.miterLimit,c.strokeStyle=b.strokeStyle,a.ea={lineCap:b.lineCap,lineDash:b.lineDash,lineJoin:b.lineJoin,lineWidth:b.lineWidth,miterLimit:b.miterLimit,strokeStyle:b.strokeStyle}):(d.lineCap!=b.lineCap&&(d.lineCap=c.lineCap=b.lineCap),ug&&!ib(d.lineDash,b.lineDash)&&c.setLineDash(d.lineDash=b.lineDash),d.lineJoin!=b.lineJoin&&(d.lineJoin=c.lineJoin=
-b.lineJoin),d.lineWidth!=b.lineWidth&&(d.lineWidth=c.lineWidth=b.lineWidth),d.miterLimit!=b.miterLimit&&(d.miterLimit=c.miterLimit=b.miterLimit),d.strokeStyle!=b.strokeStyle&&(d.strokeStyle=c.strokeStyle=b.strokeStyle))}
-k.Aa=function(a,b){if(null===a)this.c=null;else{var c=a.a;this.c={fillStyle:Jg(null===c?Ei:c)}}if(null===b)this.a=null;else{var c=b.a,d=b.b,e=b.d,f=b.e,g=b.c,h=b.f;this.a={lineCap:l(d)?d:"round",lineDash:null!=e?e:Fi,lineJoin:l(f)?f:"round",lineWidth:this.r*(l(g)?g:1),miterLimit:l(h)?h:10,strokeStyle:Jg(null===c?Gi:c)}}};
-k.$b=function(a){if(null===a)this.f=null;else{var b=a.Pb(),c=a.sc(1),d=a.Wb(),e=a.lb();this.F=b[0];this.Q=b[1];this.X=e[1];this.f=c;this.o=a.r;this.bb=d[0];this.qb=d[1];this.Oa=a.u;this.na=a.g;this.g=a.i;this.Ib=a.D;this.ba=e[0]}};
-k.Ca=function(a){if(null===a)this.d="";else{var b=a.a;null===b?this.i=null:(b=b.a,this.i={fillStyle:Jg(null===b?Ei:b)});var c=a.i;if(null===c)this.k=null;else{var b=c.a,d=c.b,e=c.d,f=c.e,g=c.c,c=c.f;this.k={lineCap:l(d)?d:"round",lineDash:null!=e?e:Fi,lineJoin:l(f)?f:"round",lineWidth:l(g)?g:1,miterLimit:l(c)?c:10,strokeStyle:Jg(null===b?Gi:b)}}var b=a.d,d=a.e,e=a.f,f=a.g,g=a.c,c=a.b,h=a.l;a=a.k;this.ga={font:l(b)?b:"10px sans-serif",textAlign:l(h)?h:"center",textBaseline:l(a)?a:"middle"};this.d=
-l(c)?c:"";this.sa=l(d)?this.r*d:0;this.Va=l(e)?this.r*e:0;this.u=l(f)?f:0;this.l=this.r*(l(g)?g:1)}};var Zt={Point:St.prototype.nc,LineString:St.prototype.sb,Polygon:St.prototype.Ob,MultiPoint:St.prototype.lc,MultiLineString:St.prototype.kc,MultiPolygon:St.prototype.mc,GeometryCollection:St.prototype.Xd,Circle:St.prototype.jc};function au(a,b,c){this.ma=a;this.ba=b;this.e=0;this.resolution=c;this.Q=this.F=null;this.c=[];this.coordinates=[];this.aa=Id();this.a=[];this.ga=[];this.d=Sd();this.ea=Id()}
-function bu(a,b,c,d,e,f){var g=a.coordinates.length,h=a.wd(),m=[b[c],b[c+1]],n=[NaN,NaN],p=!0,r,q,u;for(r=c+e;r<d;r+=e){n[0]=b[r];n[1]=b[r+1];u=h[1];var x=h[2],B=h[3],E=n[0],F=n[1],w=0;E<h[0]?w=w|16:E>x&&(w=w|4);F<u?w|=8:F>B&&(w|=2);0===w&&(w=1);u=w;u!==q?(p&&(a.coordinates[g++]=m[0],a.coordinates[g++]=m[1]),a.coordinates[g++]=n[0],a.coordinates[g++]=n[1],p=!1):1===u?(a.coordinates[g++]=n[0],a.coordinates[g++]=n[1],p=!1):p=!0;m[0]=n[0];m[1]=n[1];q=u}r===c+e&&(a.coordinates[g++]=m[0],a.coordinates[g++]=
-m[1]);f&&(a.coordinates[g++]=b[c],a.coordinates[g++]=b[c+1]);return g}function cu(a,b,c){a.F=[0,b,c,0];a.c.push(a.F);a.Q=[0,b,c,0];a.a.push(a.Q)}
-function du(a,b,c,d,e,f,g,h){var m;qt(d,a.aa)?m=a.ga:(m=Qi(a.coordinates,0,a.coordinates.length,2,d,a.ga),Ld(a.aa,d));d=0;var n=g.length,p=0,r;for(a=a.ea;d<n;){var q=g[d],u,x,B,E;switch(q[0]){case 0:p=q[2];p=na(p).toString();l(v(f,p))?d=q[3]:++d;break;case 1:b.beginPath();++d;break;case 2:p=q[1];r=m[p];var F=m[p+1],w=m[p+2]-r,p=m[p+3]-F;b.arc(r,F,Math.sqrt(w*w+p*p),0,2*Math.PI,!0);++d;break;case 3:b.closePath();++d;break;case 4:p=q[1];r=q[2];u=q[3];B=q[4]*c;var U=q[5]*c,Q=q[6];x=q[7];var ea=q[8],
-Y=q[9],F=q[11],w=q[12],za=q[13],kb=q[14];for(q[10]&&(F+=e);p<r;p+=2){q=m[p]-B;E=m[p+1]-U;za&&(q=q+.5|0,E=E+.5|0);if(1!=w||0!==F){var Aa=q+B,Ab=E+U;pt(a,Aa,Ab,w,w,F,-Aa,-Ab);b.setTransform(a[0],a[1],a[4],a[5],a[12],a[13])}Aa=b.globalAlpha;1!=x&&(b.globalAlpha=Aa*x);b.drawImage(u,ea,Y,kb,Q,q,E,kb*c,Q*c);1!=x&&(b.globalAlpha=Aa);1==w&&0===F||b.setTransform(1,0,0,1,0,0)}++d;break;case 5:p=q[1];r=q[2];B=q[3];U=q[4]*c;Q=q[5]*c;F=q[6];w=q[7]*c;u=q[8];for(x=q[9];p<r;p+=2){q=m[p]+U;E=m[p+1]+Q;if(1!=w||0!==
-F)pt(a,q,E,w,w,F,-q,-E),b.setTransform(a[0],a[1],a[4],a[5],a[12],a[13]);x&&b.strokeText(B,q,E);u&&b.fillText(B,q,E);1==w&&0===F||b.setTransform(1,0,0,1,0,0)}++d;break;case 6:if(l(h)&&(r=q[1],p=q[2],p=h(r,p)))return p;++d;break;case 7:b.fill();++d;break;case 8:p=q[1];r=q[2];b.moveTo(m[p],m[p+1]);for(p+=2;p<r;p+=2)b.lineTo(m[p],m[p+1]);++d;break;case 9:b.fillStyle=q[1];++d;break;case 10:p=l(q[7])?q[7]:!0;r=q[2];b.strokeStyle=q[1];b.lineWidth=p?r*c:r;b.lineCap=q[3];b.lineJoin=q[4];b.miterLimit=q[5];
-ug&&b.setLineDash(q[6]);++d;break;case 11:b.font=q[1];b.textAlign=q[2];b.textBaseline=q[3];++d;break;case 12:b.stroke();++d;break;default:++d}}}function eu(a){var b=a.a;b.reverse();var c,d=b.length,e,f,g=-1;for(c=0;c<d;++c)if(e=b[c],f=e[0],6==f)g=c;else if(0==f){e[3]=c;e=a.a;for(f=c;g<f;){var h=e[g];e[g]=e[f];e[f]=h;++g;--f}g=-1}}function fu(a,b,c){a.F[3]=a.c.length;a.F=null;a.Q[3]=a.a.length;a.Q=null;b=[6,b,c];a.c.push(b);a.a.push(b)}au.prototype.dd=ca;au.prototype.wd=function(){return this.ba};
-au.prototype.s=function(){return this.d};function gu(a,b,c){au.call(this,a,b,c);this.i=this.X=null;this.r=this.q=this.o=this.p=this.k=this.D=this.u=this.l=this.g=this.f=this.b=void 0}t(gu,au);
-gu.prototype.nc=function(a,b){if(null!==this.i){be(this.d,a.s());cu(this,a,b);var c=a.j,d=this.coordinates.length,c=bu(this,c,0,c.length,a.a,!1);this.c.push([4,d,c,this.i,this.b,this.f,this.g,this.l,this.u,this.D,this.k,this.p,this.o,this.q,this.r]);this.a.push([4,d,c,this.X,this.b,this.f,this.g,this.l,this.u,this.D,this.k,this.p,this.o,this.q,this.r]);fu(this,a,b)}};
-gu.prototype.lc=function(a,b){if(null!==this.i){be(this.d,a.s());cu(this,a,b);var c=a.j,d=this.coordinates.length,c=bu(this,c,0,c.length,a.a,!1);this.c.push([4,d,c,this.i,this.b,this.f,this.g,this.l,this.u,this.D,this.k,this.p,this.o,this.q,this.r]);this.a.push([4,d,c,this.X,this.b,this.f,this.g,this.l,this.u,this.D,this.k,this.p,this.o,this.q,this.r]);fu(this,a,b)}};
-gu.prototype.dd=function(){eu(this);this.f=this.b=void 0;this.i=this.X=null;this.r=this.q=this.p=this.k=this.D=this.u=this.l=this.o=this.g=void 0};gu.prototype.$b=function(a){var b=a.Pb(),c=a.lb(),d=a.ue(1),e=a.sc(1),f=a.Wb();this.b=b[0];this.f=b[1];this.X=d;this.i=e;this.g=c[1];this.l=a.r;this.u=f[0];this.D=f[1];this.k=a.u;this.p=a.g;this.o=a.i;this.q=a.D;this.r=c[0]};
-function hu(a,b,c){au.call(this,a,b,c);this.b={hc:void 0,cc:void 0,dc:null,ec:void 0,fc:void 0,gc:void 0,Bd:0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}t(hu,au);function iu(a,b,c,d,e){var f=a.coordinates.length;b=bu(a,b,c,d,e,!1);f=[8,f,b];a.c.push(f);a.a.push(f);return d}k=hu.prototype;k.wd=function(){var a=this.ba;this.e&&(a=Vd(a,this.resolution*(this.e+1)/2));return a};
-function ju(a){var b=a.b,c=b.strokeStyle,d=b.lineCap,e=b.lineDash,f=b.lineJoin,g=b.lineWidth,h=b.miterLimit;b.hc==c&&b.cc==d&&ib(b.dc,e)&&b.ec==f&&b.fc==g&&b.gc==h||(b.Bd!=a.coordinates.length&&(a.c.push([12]),b.Bd=a.coordinates.length),a.c.push([10,c,g,d,f,h,e],[1]),b.hc=c,b.cc=d,b.dc=e,b.ec=f,b.fc=g,b.gc=h)}
-k.sb=function(a,b){var c=this.b,d=c.lineWidth;l(c.strokeStyle)&&l(d)&&(be(this.d,a.s()),ju(this),cu(this,a,b),this.a.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash],[1]),c=a.j,iu(this,c,0,c.length,a.a),this.a.push([12]),fu(this,a,b))};
-k.kc=function(a,b){var c=this.b,d=c.lineWidth;if(l(c.strokeStyle)&&l(d)){be(this.d,a.s());ju(this);cu(this,a,b);this.a.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash],[1]);var c=a.d,d=a.j,e=a.a,f=0,g,h;g=0;for(h=c.length;g<h;++g)f=iu(this,d,f,c[g],e);this.a.push([12]);fu(this,a,b)}};k.dd=function(){this.b.Bd!=this.coordinates.length&&this.c.push([12]);eu(this);this.b=null};
-k.Aa=function(a,b){var c=b.a;this.b.strokeStyle=Jg(null===c?Gi:c);c=b.b;this.b.lineCap=l(c)?c:"round";c=b.d;this.b.lineDash=null===c?Fi:c;c=b.e;this.b.lineJoin=l(c)?c:"round";c=b.c;this.b.lineWidth=l(c)?c:1;c=b.f;this.b.miterLimit=l(c)?c:10;this.e=Math.max(this.e,this.b.lineWidth)};
-function ku(a,b,c){au.call(this,a,b,c);this.b={Ud:void 0,hc:void 0,cc:void 0,dc:null,ec:void 0,fc:void 0,gc:void 0,fillStyle:void 0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}t(ku,au);
-function lu(a,b,c,d,e){var f=a.b,g=[1];a.c.push(g);a.a.push(g);var h,g=0;for(h=d.length;g<h;++g){var m=d[g],n=a.coordinates.length;c=bu(a,b,c,m,e,!0);c=[8,n,c];n=[3];a.c.push(c,n);a.a.push(c,n);c=m}b=[7];a.a.push(b);l(f.fillStyle)&&a.c.push(b);l(f.strokeStyle)&&(f=[12],a.c.push(f),a.a.push(f));return c}k=ku.prototype;
-k.jc=function(a,b){var c=this.b,d=c.strokeStyle;if(l(c.fillStyle)||l(d)){be(this.d,a.s());mu(this);cu(this,a,b);this.a.push([9,Jg(Ei)]);l(c.strokeStyle)&&this.a.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash]);var e=a.j,d=this.coordinates.length;bu(this,e,0,e.length,a.a,!1);e=[1];d=[2,d];this.c.push(e,d);this.a.push(e,d);d=[7];this.a.push(d);l(c.fillStyle)&&this.c.push(d);l(c.strokeStyle)&&(c=[12],this.c.push(c),this.a.push(c));fu(this,a,b)}};
-k.Ob=function(a,b){var c=this.b,d=c.strokeStyle;if(l(c.fillStyle)||l(d))be(this.d,a.s()),mu(this),cu(this,a,b),this.a.push([9,Jg(Ei)]),l(c.strokeStyle)&&this.a.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash]),c=a.d,d=Mj(a),lu(this,d,0,c,a.a),fu(this,a,b)};
-k.mc=function(a,b){var c=this.b,d=c.strokeStyle;if(l(c.fillStyle)||l(d)){be(this.d,a.s());mu(this);cu(this,a,b);this.a.push([9,Jg(Ei)]);l(c.strokeStyle)&&this.a.push([10,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash]);var c=a.d,d=Rj(a),e=a.a,f=0,g,h;g=0;for(h=c.length;g<h;++g)f=lu(this,d,f,c[g],e);fu(this,a,b)}};k.dd=function(){eu(this);this.b=null;var a=this.ma;if(0!==a){var b=this.coordinates,c,d;c=0;for(d=b.length;c<d;++c)b[c]=a*Math.round(b[c]/a)}};
-k.wd=function(){var a=this.ba;this.e&&(a=Vd(a,this.resolution*(this.e+1)/2));return a};
-k.Aa=function(a,b){var c=this.b;if(null===a)c.fillStyle=void 0;else{var d=a.a;c.fillStyle=Jg(null===d?Ei:d)}null===b?(c.strokeStyle=void 0,c.lineCap=void 0,c.lineDash=null,c.lineJoin=void 0,c.lineWidth=void 0,c.miterLimit=void 0):(d=b.a,c.strokeStyle=Jg(null===d?Gi:d),d=b.b,c.lineCap=l(d)?d:"round",d=b.d,c.lineDash=null===d?Fi:d.slice(),d=b.e,c.lineJoin=l(d)?d:"round",d=b.c,c.lineWidth=l(d)?d:1,d=b.f,c.miterLimit=l(d)?d:10,this.e=Math.max(this.e,c.lineWidth))};
-function mu(a){var b=a.b,c=b.fillStyle,d=b.strokeStyle,e=b.lineCap,f=b.lineDash,g=b.lineJoin,h=b.lineWidth,m=b.miterLimit;l(c)&&b.Ud!=c&&(a.c.push([9,c]),b.Ud=b.fillStyle);!l(d)||b.hc==d&&b.cc==e&&b.dc==f&&b.ec==g&&b.fc==h&&b.gc==m||(a.c.push([10,d,h,e,g,m,f]),b.hc=d,b.cc=e,b.dc=f,b.ec=g,b.fc=h,b.gc=m)}function nu(a,b,c){au.call(this,a,b,c);this.u=this.r=this.q=null;this.i="";this.o=this.p=this.k=this.l=0;this.g=this.f=this.b=null}t(nu,au);
-nu.prototype.eb=function(a,b,c,d,e,f){if(""!==this.i&&null!==this.g&&(null!==this.b||null!==this.f)){ce(this.d,a,b,c,d);if(null!==this.b){var g=this.b,h=this.q;if(null===h||h.fillStyle!=g.fillStyle){var m=[9,g.fillStyle];this.c.push(m);this.a.push(m);null===h?this.q={fillStyle:g.fillStyle}:h.fillStyle=g.fillStyle}}null!==this.f&&(g=this.f,h=this.r,null===h||h.lineCap!=g.lineCap||h.lineDash!=g.lineDash||h.lineJoin!=g.lineJoin||h.lineWidth!=g.lineWidth||h.miterLimit!=g.miterLimit||h.strokeStyle!=g.strokeStyle)&&
-(m=[10,g.strokeStyle,g.lineWidth,g.lineCap,g.lineJoin,g.miterLimit,g.lineDash,!1],this.c.push(m),this.a.push(m),null===h?this.r={lineCap:g.lineCap,lineDash:g.lineDash,lineJoin:g.lineJoin,lineWidth:g.lineWidth,miterLimit:g.miterLimit,strokeStyle:g.strokeStyle}:(h.lineCap=g.lineCap,h.lineDash=g.lineDash,h.lineJoin=g.lineJoin,h.lineWidth=g.lineWidth,h.miterLimit=g.miterLimit,h.strokeStyle=g.strokeStyle));g=this.g;h=this.u;if(null===h||h.font!=g.font||h.textAlign!=g.textAlign||h.textBaseline!=g.textBaseline)m=
-[11,g.font,g.textAlign,g.textBaseline],this.c.push(m),this.a.push(m),null===h?this.u={font:g.font,textAlign:g.textAlign,textBaseline:g.textBaseline}:(h.font=g.font,h.textAlign=g.textAlign,h.textBaseline=g.textBaseline);cu(this,e,f);g=this.coordinates.length;a=bu(this,a,b,c,d,!1);a=[5,g,a,this.i,this.l,this.k,this.p,this.o,null!==this.b,null!==this.f];this.c.push(a);this.a.push(a);fu(this,e,f)}};
-nu.prototype.Ca=function(a){if(null===a)this.i="";else{var b=a.a;null===b?this.b=null:(b=b.a,b=Jg(null===b?Ei:b),null===this.b?this.b={fillStyle:b}:this.b.fillStyle=b);var c=a.i;if(null===c)this.f=null;else{var b=c.a,d=c.b,e=c.d,f=c.e,g=c.c,c=c.f,d=l(d)?d:"round",e=null!=e?e.slice():Fi,f=l(f)?f:"round",g=l(g)?g:1,c=l(c)?c:10,b=Jg(null===b?Gi:b);if(null===this.f)this.f={lineCap:d,lineDash:e,lineJoin:f,lineWidth:g,miterLimit:c,strokeStyle:b};else{var h=this.f;h.lineCap=d;h.lineDash=e;h.lineJoin=f;h.lineWidth=
-g;h.miterLimit=c;h.strokeStyle=b}}var m=a.d,b=a.e,d=a.f,e=a.g,g=a.c,c=a.b,f=a.l,h=a.k;a=l(m)?m:"10px sans-serif";f=l(f)?f:"center";h=l(h)?h:"middle";null===this.g?this.g={font:a,textAlign:f,textBaseline:h}:(m=this.g,m.font=a,m.textAlign=f,m.textBaseline=h);this.i=l(c)?c:"";this.l=l(b)?b:0;this.k=l(d)?d:0;this.p=l(e)?e:0;this.o=l(g)?g:1}};function ou(a,b,c){this.f=a;this.c=b;this.e=c;this.a={};this.b=lg(1,1);this.d=Id()}
-function pu(a,b,c,d,e,f,g){var h=Va(xc(a.a),Number);gb(h);a:{var m=a.c,n=m[0],p=m[1],r=m[2],m=m[3],n=Qi([n,p,n,m,r,m,r,p],0,8,2,e);b.save();b.beginPath();b.moveTo(n[0],n[1]);b.lineTo(n[2],n[3]);b.lineTo(n[4],n[5]);b.lineTo(n[6],n[7]);b.closePath();b.clip();for(var q,u,n=0,p=h.length;n<p;++n)for(q=a.a[h[n].toString()],r=0,m=Uj.length;r<m;++r)if(u=q[Uj[r]],l(u)&&pe(c,u.s())&&(u=du(u,b,d,e,f,g,u.c,void 0)))break a;b.restore()}}
-function qu(a,b,c,d,e,f,g,h){var m,n,p,r,q;m=0;for(n=b.length;m<n;++m)for(r in p=a.a[b[m].toString()],p)if(q=p[r],pe(d,q.s())&&(q=du(q,c,1,e,f,g,q.a,h)))return q}function wt(a,b,c,d,e,f,g){var h=a.d;pt(h,.5,.5,1/c,-1/c,-d,-e[0],-e[1]);c=Va(xc(a.a),Number);gb(c,function(a,b){return b-a});var m=a.b;m.clearRect(0,0,1,1);return qu(a,c,m,b,h,d,f,function(a,b){if(0<m.getImageData(0,0,1,1).data[3]){var c=g(a,b);if(c)return c;m.clearRect(0,0,1,1)}})}
-function ru(a){for(var b in a.a){var c=a.a[b],d;for(d in c)c[d].dd()}}function ak(a,b,c){var d=l(b)?b.toString():"0";b=a.a[d];l(b)||(b={},a.a[d]=b);d=b[c];l(d)||(d=new su[c](a.f,a.c,a.e),b[c]=d);return d}ou.prototype.ka=function(){return Bc(this.a)};var su={Image:gu,LineString:hu,Polygon:ku,Text:nu};function tu(a,b){ft.call(this,a,b);this.F=Id()}t(tu,ft);
-tu.prototype.r=function(a,b,c){uu(this,"precompose",c,a,void 0);var d=this.u();if(null!==d){var e=this.q(),f=c.globalAlpha;c.globalAlpha=b.opacity;if(0===a.viewState.rotation){b=e[13];var g=d.width*e[0],h=d.height*e[5];c.drawImage(d,0,0,+d.width,+d.height,Math.round(e[12]),Math.round(b),Math.round(g),Math.round(h))}else c.setTransform(e[0],e[1],e[4],e[5],e[12],e[13]),c.drawImage(d,0,0),c.setTransform(1,0,0,1,0,0);c.globalAlpha=f}uu(this,"postcompose",c,a,void 0)};
-function uu(a,b,c,d,e){var f=a.a;jd(f,b)&&(a=l(e)?e:vu(a,d),a=new St(c,d.pixelRatio,d.extent,a,d.viewState.rotation),f.dispatchEvent(new Pi(b,f,a,null,d,c,null)),$t(a))}function vu(a,b){var c=b.viewState,d=b.pixelRatio;return pt(a.F,d*b.size[0]/2,d*b.size[1]/2,d/c.resolution,-d/c.resolution,-c.rotation,-c.center[0],-c.center[1])}
-var wu=function(){var a=null,b=null;return function(c){if(null===a){a=lg(1,1);b=a.createImageData(1,1);var d=b.data;d[0]=42;d[1]=84;d[2]=126;d[3]=255}var d=a.canvas,e=c[0]<=d.width&&c[1]<=d.height;e||(d.width=c[0],d.height=c[1],d=c[0]-1,c=c[1]-1,a.putImageData(b,d,c),c=a.getImageData(d,c,1,1),e=ib(b.data,c.data));return e}}();function xu(a,b){tu.call(this,a,b);this.c=null;this.e=Id()}t(xu,tu);xu.prototype.f=function(a,b,c,d){var e=this.a;return e.a.ed(b.extent,b.viewState.resolution,b.viewState.rotation,a,b.skippedFeatureUids,function(a){return c.call(d,a,e)})};xu.prototype.u=function(){return null===this.c?null:this.c.d()};xu.prototype.q=function(){return this.e};
-xu.prototype.b=function(a,b){var c=a.pixelRatio,d=a.viewState,e=d.center,f=d.resolution,g=d.rotation,h=this.a.a,m=a.viewHints,n=a.extent;l(b.extent)&&(n=oe(n,b.extent));m[0]||m[1]||ee(n)||(d=h.Vb(n,f,c,d.projection),null!==d&&(m=d.state,0==m?(Wc(d,"change",this.p,!1,this),dr(d)):2==m&&(this.c=d)));if(null!==this.c){var d=this.c,m=d.s(),n=d.b,p=d.f,f=c*n/(f*p);pt(this.e,c*a.size[0]/2,c*a.size[1]/2,f,f,g,p*(m[0]-e[0])/n,p*(e[1]-m[3])/n);it(a.attributions,d.g);jt(a,h)}return!0};function yu(a,b){tu.call(this,a,b);this.c=this.g=null;this.l=!1;this.k=null;this.D=Id();this.o=NaN;this.i=this.e=null}t(yu,tu);yu.prototype.u=function(){return this.g};yu.prototype.q=function(){return this.D};
-yu.prototype.b=function(a,b){var c=a.pixelRatio,d=a.viewState,e=d.projection,f=this.a,g=f.a,h=et(g,e),m=g.Mc(),n=Qb(h.a,d.resolution,0),p=g.pc(n,a.pixelRatio,e),r=h.fa(n),q=r/(p/h.ia(n)),u=d.center,x;r==d.resolution?(u=mt(u,r,a.size),x=ne(u,r,d.rotation,a.size)):x=a.extent;l(b.extent)&&(x=oe(x,b.extent));if(ee(x))return!1;var B=Zs(h,x,r),E=p*(B.d-B.a+1),F=p*(B.c-B.b+1),w,U;null===this.g?(U=lg(E,F),this.g=U.canvas,this.c=[E,F],this.k=U,this.l=!wu(this.c)):(w=this.g,U=this.k,this.c[0]<E||this.c[1]<
-F||this.l&&(this.c[0]>E||this.c[1]>F)?(w.width=E,w.height=F,this.c=[E,F],this.l=!wu(this.c),this.e=null):(E=this.c[0],F=this.c[1],n==this.o&&of(this.e,B)||(this.e=null,n<this.o&&this.k.clearRect(0,0,E,F))));var Q,ea;null===this.e?(E/=p,F/=p,Q=B.a-Math.floor((E-(B.d-B.a+1))/2),ea=B.b-Math.floor((F-(B.c-B.b+1))/2),this.o=n,this.e=new lf(Q,Q+E-1,ea,ea+F-1),this.i=Array(E*F),F=this.e):(F=this.e,E=F.d-F.a+1);w={};w[n]={};var Y=[],za=sa(g.vd,g,w,lt(function(a){return null!==a&&2==a.state},g,c,e)),kb=f.ea();
-l(kb)||(kb=!0);var Aa=Sd(),Ab=new lf(0,0,0,0),tb,Ba,Ia;for(ea=B.a;ea<=B.d;++ea)for(Ia=B.b;Ia<=B.c;++Ia)Ba=g.Rb(n,ea,Ia,c,e),Q=Ba.state,2==Q||4==Q||3==Q&&!kb?w[n][kf(Ba.a)]=Ba:(tb=h.Lc(Ba.a,za,null,Ab,Aa),tb||(Y.push(Ba),tb=h.Sc(Ba.a,Ab,Aa),null===tb||za(n+1,tb)));za=0;for(tb=Y.length;za<tb;++za)Ba=Y[za],ea=p*(Ba.a[1]-F.a),Ia=p*(F.c-Ba.a[2]),U.clearRect(ea,Ia,p,p);Y=Va(xc(w),Number);gb(Y);var yd=g.u,kd=le(Xs(h,[n,F.a,F.c],Aa)),wc,se,Ti,gh,uf,Xl,za=0;for(tb=Y.length;za<tb;++za)if(wc=Y[za],p=g.pc(wc,
-c,e),gh=w[wc],wc==n)for(Ti in gh)Ba=gh[Ti],se=(Ba.a[2]-F.b)*E+(Ba.a[1]-F.a),this.i[se]!=Ba&&(ea=p*(Ba.a[1]-F.a),Ia=p*(F.c-Ba.a[2]),Q=Ba.state,4!=Q&&(3!=Q||kb)&&yd||U.clearRect(ea,Ia,p,p),2==Q&&U.drawImage(Ba.b(),m,m,p,p,ea,Ia,p,p),this.i[se]=Ba);else for(Ti in wc=h.fa(wc)/r,gh)for(Ba=gh[Ti],se=Xs(h,Ba.a,Aa),ea=(se[0]-kd[0])/q,Ia=(kd[1]-se[3])/q,Xl=wc*p,uf=wc*p,Q=Ba.state,4!=Q&&yd||U.clearRect(ea,Ia,Xl,uf),2==Q&&U.drawImage(Ba.b(),m,m,p,p,ea,Ia,Xl,uf),Ba=Ys(h,se,n,Ab),Q=Math.max(Ba.a,F.a),Ia=Math.min(Ba.d,
-F.d),ea=Math.max(Ba.b,F.b),Ba=Math.min(Ba.c,F.c);Q<=Ia;++Q)for(uf=ea;uf<=Ba;++uf)se=(uf-F.b)*E+(Q-F.a),this.i[se]=void 0;kt(a.usedTiles,g,n,B);nt(a,g,h,c,e,x,n,f.aa());ht(a,g);jt(a,g);pt(this.D,c*a.size[0]/2,c*a.size[1]/2,c*q/d.resolution,c*q/d.resolution,d.rotation,(kd[0]-u[0])/q,(u[1]-kd[1])/q);return!0};function zu(a,b){tu.call(this,a,b);this.e=!1;this.i=-1;this.o=NaN;this.l=Sd();this.c=this.k=null;this.g=lg()}t(zu,tu);
-zu.prototype.r=function(a,b,c){var d=vu(this,a);uu(this,"precompose",c,a,d);var e=this.c;if(null!==e&&!e.ka()){var f;jd(this.a,"render")?(this.g.canvas.width=c.canvas.width,this.g.canvas.height=c.canvas.height,f=this.g):f=c;var g=f.globalAlpha;f.globalAlpha=b.opacity;pu(e,f,a.extent,a.pixelRatio,d,a.viewState.rotation,a.skippedFeatureUids);f!=c&&(uu(this,"render",f,a,d),c.drawImage(f.canvas,0,0));f.globalAlpha=g}uu(this,"postcompose",c,a,d)};
-zu.prototype.f=function(a,b,c,d){if(null!==this.c){var e=this.a,f={};return wt(this.c,b.extent,b.viewState.resolution,b.viewState.rotation,a,b.skippedFeatureUids,function(a,b){var m=na(b).toString();if(!(m in f))return f[m]=!0,c.call(d,b,e)})}};zu.prototype.D=function(){gt(this)};
-zu.prototype.b=function(a){function b(a){var b;l(a.d)?b=a.d.call(a,h):l(c.ea)&&(b=(0,c.ea)(a,h));if(null!=b){if(null!=b){var d,e,f=!1;d=0;for(e=b.length;d<e;++d)f=Xj(q,a,b[d],Wj(h,m),a,this.D,this)||f;a=f}else a=!1;this.e=this.e||a}}var c=this.a,d=c.a;it(a.attributions,d.f);jt(a,d);if(!this.e&&(a.viewHints[0]||a.viewHints[1]))return!0;var e=a.extent,f=a.viewState,g=f.projection,h=f.resolution,m=a.pixelRatio;a=c.c;f=c.get("renderOrder");l(f)||(f=Vj);if(!this.e&&this.o==h&&this.i==a&&this.k==f&&Yd(this.l,
-e))return!0;var n=this.l,p=fe(e)/4,r=ge(e)/4;n[0]=e[0]-p;n[1]=e[1]-r;n[2]=e[2]+p;n[3]=e[3]+r;ec(this.c);this.c=null;this.e=!1;var q=new ou(.5*h/m,n,h);d.bd(n,h,g);if(null===f)d.Kc(n,h,b,this);else{var u=[];d.Kc(n,h,function(a){u.push(a)},this);gb(u,f);Ta(u,b,this)}ru(q);this.o=h;this.i=a;this.k=f;this.c=q;return!0};function Au(a,b){st.call(this,0,b);this.g=lg();this.a=this.g.canvas;this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Jf(a,this.a,0);this.c=!0;this.i=Id()}t(Au,st);Au.prototype.Gc=function(a){return a instanceof S?new xu(this,a):a instanceof T?new yu(this,a):a instanceof V?new zu(this,a):null};
-function Bu(a,b,c){var d=a.f,e=a.g;if(jd(d,b)){var f=c.extent,g=c.pixelRatio,h=c.viewState,m=h.resolution,n=h.rotation;pt(a.i,a.a.width/2,a.a.height/2,g/h.resolution,-g/h.resolution,-h.rotation,-h.center[0],-h.center[1]);h=new St(e,g,f,a.i,n);m=new ou(.5*m/g,f,m);d.dispatchEvent(new Pi(b,d,h,m,c,e,null));ru(m);m.ka()||pu(m,e,f,g,a.i,n,{});$t(h);a.k=m}}Au.prototype.G=function(){return"canvas"};
-Au.prototype.hd=function(a){if(null===a)this.c&&(dg(this.a,!1),this.c=!1);else{var b=this.g,c=a.size[0]*a.pixelRatio,d=a.size[1]*a.pixelRatio;this.a.width!=c||this.a.height!=d?(this.a.width=c,this.a.height=d):b.clearRect(0,0,this.a.width,this.a.height);tt(a);Bu(this,"precompose",a);var c=a.layerStatesArray,d=a.viewState.resolution,e,f,g,h;e=0;for(f=c.length;e<f;++e)h=c[e],g=h.layer,g=xt(this,g),h.visible&&d>=h.minResolution&&d<h.maxResolution&&"ready"==h.Lb&&g.b(a,h)&&g.r(a,h,b);Bu(this,"postcompose",
-a);this.c||(dg(this.a,!0),this.c=!0);yt(this,a);a.postRenderFunctions.push(ut)}};function Cu(a,b,c){ft.call(this,a,b);this.target=c}t(Cu,ft);function Du(a,b){var c=Ff("DIV");c.style.position="absolute";Cu.call(this,a,b,c);this.c=null;this.e=Kd()}t(Du,Cu);Du.prototype.f=function(a,b,c,d){var e=this.a;return e.a.ed(b.extent,b.viewState.resolution,b.viewState.rotation,a,b.skippedFeatureUids,function(a){return c.call(d,a,e)})};
-Du.prototype.b=function(a,b){var c=a.viewState,d=c.center,e=c.resolution,f=c.rotation,g=this.c,h=this.a.a,m=a.viewHints,n=a.extent;l(b.extent)&&(n=oe(n,b.extent));m[0]||m[1]||ee(n)||(c=h.Vb(n,e,a.pixelRatio,c.projection),null!==c&&(m=c.state,0==m?(Wc(c,"change",this.p,!1,this),dr(c)):2==m&&(g=c)));null!==g&&(m=g.s(),n=g.b,c=Id(),pt(c,a.size[0]/2,a.size[1]/2,n/e,n/e,f,(m[0]-d[0])/n,(d[1]-m[3])/n),g!=this.c&&(d=g.d(this),d.style.maxWidth="none",d.style.position="absolute",Hf(this.target),this.target.appendChild(d),
-this.c=g),qt(c,this.e)||(pg(this.target,c),Ld(this.e,c)),it(a.attributions,g.g),jt(a,h));return!0};function Eu(a,b){var c=Ff("DIV");c.style.position="absolute";Cu.call(this,a,b,c);this.e=!0;this.i=1;this.g=0;this.c={}}t(Eu,Cu);
-Eu.prototype.b=function(a,b){if(!b.visible)return this.e&&(dg(this.target,!1),this.e=!1),!0;var c=a.pixelRatio,d=a.viewState,e=d.projection,f=this.a,g=f.a,h=et(g,e),m=g.Mc(),n=Qb(h.a,d.resolution,0),p=h.fa(n),r=d.center,q;p==d.resolution?(r=mt(r,p,a.size),q=ne(r,p,d.rotation,a.size)):q=a.extent;l(b.extent)&&(q=oe(q,b.extent));var p=Zs(h,q,p),u={};u[n]={};var x=sa(g.vd,g,u,lt(function(a){return null!==a&&2==a.state},g,c,e)),B=f.ea();l(B)||(B=!0);var E=Sd(),F=new lf(0,0,0,0),w,U,Q,ea;for(Q=p.a;Q<=p.d;++Q)for(ea=
-p.b;ea<=p.c;++ea)w=g.Rb(n,Q,ea,c,e),U=w.state,2==U?u[n][kf(w.a)]=w:4==U||3==U&&!B||(U=h.Lc(w.a,x,null,F,E),U||(w=h.Sc(w.a,F,E),null===w||x(n+1,w)));var Y;if(this.g!=g.c){for(Y in this.c)B=this.c[+Y],Kf(B.target);this.c={};this.g=g.c}E=Va(xc(u),Number);gb(E);var x={},za;Q=0;for(ea=E.length;Q<ea;++Q){Y=E[Q];Y in this.c?B=this.c[Y]:(B=$s(h,r[0],r[1],h.fa(Y),!1,void 0),B=new Fu(h,B),x[Y]=!0,this.c[Y]=B);Y=u[Y];for(za in Y)Gu(B,Y[za],m);Hu(B)}m=Va(xc(this.c),Number);gb(m);Q=Id();za=0;for(E=m.length;za<
-E;++za)if(Y=m[za],B=this.c[Y],Y in u)if(w=B.g,ea=B.f,pt(Q,a.size[0]/2,a.size[1]/2,w/d.resolution,w/d.resolution,d.rotation,(ea[0]-r[0])/w,(r[1]-ea[1])/w),Iu(B,Q),Y in x){for(Y-=1;0<=Y;--Y)if(Y in this.c){If(B.target,this.c[Y].target);break}0>Y&&Jf(this.target,B.target,0)}else a.viewHints[0]||a.viewHints[1]||Ju(B,q,F);else Kf(B.target),delete this.c[Y];b.opacity!=this.i&&(cg(this.target,b.opacity),this.i=b.opacity);b.visible&&!this.e&&(dg(this.target,!0),this.e=!0);kt(a.usedTiles,g,n,p);nt(a,g,h,c,
-e,q,n,f.aa());ht(a,g);jt(a,g);return!0};function Fu(a,b){this.target=Ff("DIV");this.target.style.position="absolute";this.target.style.width="100%";this.target.style.height="100%";this.d=a;this.b=b;this.f=le(Xs(a,b));this.g=a.fa(b[0]);this.c={};this.a=null;this.e=Kd()}
-function Gu(a,b,c){var d=b.a,e=d[0],f=d[1],g=d[2],d=kf(d);if(!(d in a.c)){var e=a.d.ia(e),h=b.b(a),m=h.style;m.maxWidth="none";var n,p;0<c?(n=Ff("DIV"),p=n.style,p.overflow="hidden",p.width=e+"px",p.height=e+"px",m.position="absolute",m.left=-c+"px",m.top=-c+"px",m.width=e+2*c+"px",m.height=e+2*c+"px",n.appendChild(h)):(m.width=e+"px",m.height=e+"px",n=h,p=m);p.position="absolute";p.left=(f-a.b[1])*e+"px";p.top=(a.b[2]-g)*e+"px";null===a.a&&(a.a=document.createDocumentFragment());a.a.appendChild(n);
-a.c[d]=b}}function Hu(a){null!==a.a&&(a.target.appendChild(a.a),a.a=null)}function Ju(a,b,c){var d=Ys(a.d,b,a.b[0],c);b=[];for(var e in a.c)c=a.c[e],d.contains(c.a)||b.push(c);var f,d=0;for(f=b.length;d<f;++d)c=b[d],e=kf(c.a),Kf(c.b(a)),delete a.c[e]}function Iu(a,b){qt(b,a.e)||(pg(a.target,b),Ld(a.e,b))};function Ku(a,b){st.call(this,0,b);this.a=Ff("DIV");this.a.className="ol-unselectable";var c=this.a.style;c.position="absolute";c.width="100%";c.height="100%";Jf(a,this.a,0);this.c=!0}t(Ku,st);Ku.prototype.Gc=function(a){if(a instanceof S)a=new Du(this,a);else if(a instanceof T)a=new Eu(this,a);else return null;return a};Ku.prototype.G=function(){return"dom"};
-Ku.prototype.hd=function(a){if(null===a)this.c&&(dg(this.a,!1),this.c=!1);else{var b;b=function(a,b){Jf(this.a,a,b)};var c=a.layerStatesArray,d,e,f,g;d=0;for(e=c.length;d<e;++d)g=c[d],f=g.layer,f=xt(this,f),b.call(this,f.target,d),"ready"==g.Lb&&f.b(a,g);b=a.layerStates;for(var h in this.b)h in b||(f=this.b[h],Kf(f.target));this.c||(dg(this.a,!0),this.c=!0);tt(a);yt(this,a);a.postRenderFunctions.push(ut)}};function Lu(){}k=Lu.prototype;k.Ic=function(){};k.jc=function(){};k.Wd=function(){};k.Xd=function(){};k.nc=function(){};k.sb=function(){};k.kc=function(){};k.lc=function(){};k.mc=function(){};k.Ob=function(){};k.eb=function(){};k.Aa=function(){};k.$b=function(){};k.Ca=function(){};function Mu(a){this.a=a}function Nu(a){this.a=a}t(Nu,Mu);Nu.prototype.G=function(){return 35632};function Ou(a){this.a=a}t(Ou,Mu);Ou.prototype.G=function(){return 35633};function Pu(){this.a="precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}"}t(Pu,Nu);da(Pu);function Qu(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}t(Qu,Ou);da(Qu);
-function Ru(a,b){this.g=a.getUniformLocation(b,"f");this.d=a.getUniformLocation(b,"g");this.e=a.getUniformLocation(b,"e");this.f=a.getUniformLocation(b,"d");this.b=a.getUniformLocation(b,"h");this.a=a.getAttribLocation(b,"b");this.c=a.getAttribLocation(b,"c")};function Su(){this.a="precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}"}t(Su,Nu);da(Su);function Tu(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}t(Tu,Ou);da(Tu);
-function Uu(a,b){this.d=a.getUniformLocation(b,"f");this.e=a.getUniformLocation(b,"e");this.f=a.getUniformLocation(b,"d");this.b=a.getUniformLocation(b,"g");this.a=a.getAttribLocation(b,"b");this.c=a.getAttribLocation(b,"c")};function Vu(a){this.a=l(a)?a:[]}function Wu(a,b,c){if(b!=c){var d=a.a,e=d.length,f;for(f=0;f<e;f+=2)if(b<=d[f]){d.splice(f,0,b,c);Xu(a);return}d.push(b,c);Xu(a)}}Vu.prototype.clear=function(){this.a.length=0};function Xu(a){a=a.a;var b=a.length,c=0,d;for(d=0;d<b;d+=2)a[d]!=a[d+1]&&(0<c&&a[c-2]<=a[d]&&a[d]<=a[c-1]?a[c-1]=Math.max(a[c-1],a[d+1]):(a[c++]=a[d],a[c++]=a[d+1]));a.length=c}function Yu(a,b){var c=a.a,d=c.length,e;for(e=0;e<d;e+=2)b.call(void 0,c[e],c[e+1])}
-Vu.prototype.ka=function(){return 0===this.a.length};function Zu(a,b,c){var d=a.a,e=d.length,f;for(f=0;f<e;f+=2)if(!(c<d[f]||d[f+1]<b)){if(d[f]>c)break;if(b<d[f])if(c==d[f])break;else if(c<d[f+1]){d[f]=Math.max(d[f],c);break}else d.splice(f,2),f-=2,e-=2;else if(b==d[f])if(c<d[f+1]){d[f]=c;break}else if(c==d[f+1]){d.splice(f,2);break}else d.splice(f,2),f-=2,e-=2;else if(c<d[f+1]){d.splice(f,2,d[f],b,c,d[f+1]);break}else if(c==d[f+1]){d[f+1]=b;break}else d[f+1]=b}Xu(a)};function $u(a,b,c){this.c=l(a)?a:[];this.a=[];this.b=new Vu;a=l(b)?b:this.c.length;a<this.c.length&&Wu(this.b,a,this.c.length);this.d=l(c)?c:35044}$u.prototype.add=function(a){var b=a.length,c;a:{c=this.b.a;var d=c.length,e=-1,f,g,h;for(g=0;g<d;g+=2){h=c[g+1]-c[g];if(h==b){c=c[g];break a}h>b&&(-1==e||h<f)&&(e=c[g],f=h)}c=e}Zu(this.b,c,c+b);for(d=0;d<b;++d)this.c[c+d]=a[d];av(this,b,c);return c};
-$u.prototype.fb=function(){var a=this.b.a,b=a.length,c=0,d;for(d=0;d<b;d+=2)c+=a[d+1]-a[d];return this.c.length-c};function av(a,b,c){var d,e;d=0;for(e=a.a.length;d<e;++d)Wu(a.a[d],c,c+b)}$u.prototype.remove=function(a,b){var c,d;Wu(this.b,b,b+a);c=0;for(d=this.a.length;c<d;++c)Zu(this.a[c],b,b+a)};$u.prototype.set=function(a,b){var c=this.c,d=a.length,e;for(e=0;e<d;++e)c[b+e]=a[e];av(this,d,b)};function bv(a,b){ft.call(this,a,b);this.Q=new $u([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.e=this.Da=null;this.g=void 0;this.u=Id();this.D=Kd();this.ba=new Lg;this.k=this.l=null}t(bv,ft);
-function cv(a,b,c){var d=a.d.d;if(l(a.g)&&a.g==c)d.bindFramebuffer(36160,a.e);else{b.postRenderFunctions.push(ta(function(a,b,c){a.isContextLost()||(a.deleteFramebuffer(b),a.deleteTexture(c))},d,a.e,a.Da));b=d.createTexture();d.bindTexture(3553,b);d.texImage2D(3553,0,6408,c,c,0,6408,5121,null);d.texParameteri(3553,10240,9729);d.texParameteri(3553,10241,9729);var e=d.createFramebuffer();d.bindFramebuffer(36160,e);d.framebufferTexture2D(36160,36064,3553,b,0);a.Da=b;a.e=e;a.g=c}}
-function dv(a,b,c,d){a=a.a;jd(a,b)&&a.dispatchEvent(new Pi(b,a,new Lu,null,d,null,c))}bv.prototype.o=function(){this.e=this.Da=null;this.g=void 0};function ev(a,b){bv.call(this,a,b);this.c=null}t(ev,bv);function fv(a,b){var c=b.d(),d=a.d.d,e=d.createTexture();d.bindTexture(3553,e);d.texImage2D(3553,0,6408,6408,5121,c);d.texParameteri(3553,10242,33071);d.texParameteri(3553,10243,33071);d.texParameteri(3553,10241,9729);d.texParameteri(3553,10240,9729);return e}ev.prototype.f=function(a,b,c,d){var e=this.a;return e.a.ed(b.extent,b.viewState.resolution,b.viewState.rotation,a,b.skippedFeatureUids,function(a){return c.call(d,a,e)})};
-ev.prototype.b=function(a,b){var c=this.d.d,d=a.viewState,e=d.center,f=d.resolution,g=d.rotation,h=this.c,m=this.Da,n=this.a.a,p=a.viewHints,r=a.extent;l(b.extent)&&(r=oe(r,b.extent));p[0]||p[1]||ee(r)||(d=n.Vb(r,f,a.pixelRatio,d.projection),null!==d&&(p=d.state,0==p?(Wc(d,"change",this.p,!1,this),dr(d)):2==p&&(h=d,m=fv(this,d),null===this.Da||a.postRenderFunctions.push(ta(function(a,b){a.isContextLost()||a.deleteTexture(b)},c,this.Da)))));null!==h&&(c=this.d.e.f,gv(this,c.width,c.height,e,f,g,h.s()),
-e=this.u,Md(e),Pd(e,1,-1),Od(e,0,-1),this.c=h,this.Da=m,it(a.attributions,h.g),jt(a,n));return!0};function gv(a,b,c,d,e,f,g){b*=e;c*=e;a=a.D;Md(a);Pd(a,2/b,2/c);Qd(a,-f);Od(a,g[0]-d[0],g[1]-d[1]);Pd(a,(g[2]-g[0])/2,(g[3]-g[1])/2);Od(a,1,1)};function hv(){this.a="precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"}t(hv,Nu);da(hv);function iv(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}"}t(iv,Ou);da(iv);function jv(a,b){this.b=a.getUniformLocation(b,"e");this.d=a.getUniformLocation(b,"d");this.a=a.getAttribLocation(b,"b");this.c=a.getAttribLocation(b,"c")};function kv(a,b){bv.call(this,a,b);this.X=hv.gb();this.ga=iv.gb();this.c=null;this.F=new $u([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.r=this.i=null;this.q=-1}t(kv,bv);kv.prototype.I=function(){var a=this.d.e,b=a.c,c=na(this.F),d=a.a[c];ab(d.Sd.a,d.Hc);b.isContextLost()||b.deleteBuffer(d.buffer);delete a.a[c];kv.K.I.call(this)};kv.prototype.o=function(){kv.K.o.call(this);this.c=null};
-kv.prototype.b=function(a,b){var c=this.d,d=c.e,e=c.d,f=a.viewState,g=f.projection,h=this.a,m=h.a,n=et(m,g),p=Qb(n.a,f.resolution,0),r=n.fa(p),q=m.pc(p,a.pixelRatio,g),u=q/n.ia(p),x=r/u,B=m.Mc(),E=f.center,F;r==f.resolution?(E=mt(E,r,a.size),F=ne(E,r,f.rotation,a.size)):F=a.extent;r=Zs(n,F,r);if(null!==this.i&&pf(this.i,r)&&this.q==m.c)x=this.r;else{var w=[r.d-r.a+1,r.c-r.b+1],w=Math.max(w[0]*q,w[1]*q),U=Math.pow(2,Math.ceil(Math.log(w)/Math.LN2)),w=x*U,Q=n.mb(p),ea=Q[0]+r.a*q*x,x=Q[1]+r.b*q*x,x=
-[ea,x,ea+w,x+w];cv(this,a,U);e.viewport(0,0,U,U);e.clearColor(0,0,0,0);e.clear(16384);e.disable(3042);U=lv(d,this.X,this.ga);d.Fd(U);null===this.c&&(this.c=new jv(e,U));mv(d,this.F);e.enableVertexAttribArray(this.c.a);e.vertexAttribPointer(this.c.a,2,5126,!1,16,0);e.enableVertexAttribArray(this.c.c);e.vertexAttribPointer(this.c.c,2,5126,!1,16,8);e.uniform1i(this.c.b,0);d={};d[p]={};var Y=sa(m.vd,m,d,lt(function(a){return null!==a&&2==a.state&&nv(c.c,a.d())},m,u,g)),za=h.ea();l(za)||(za=!0);var U=
-!0,ea=Sd(),kb=new lf(0,0,0,0),Aa,Ab,tb;for(Ab=r.a;Ab<=r.d;++Ab)for(tb=r.b;tb<=r.c;++tb){Q=m.Rb(p,Ab,tb,u,g);if(l(b.extent)&&(Aa=Xs(n,Q.a,ea),!pe(Aa,b.extent)))continue;Aa=Q.state;if(2==Aa){if(nv(c.c,Q.d())){d[p][kf(Q.a)]=Q;continue}}else if(4==Aa||3==Aa&&!za)continue;U=!1;Aa=n.Lc(Q.a,Y,null,kb,ea);Aa||(Q=n.Sc(Q.a,kb,ea),null===Q||Y(p+1,Q))}Y=Va(xc(d),Number);gb(Y);var za=new Float32Array(4),Ba,Ia,yd,kd,kb=0;for(Ab=Y.length;kb<Ab;++kb)for(Ia in yd=d[Y[kb]],yd)Q=yd[Ia],Aa=Xs(n,Q.a,ea),tb=2*(Aa[2]-Aa[0])/
-w,Ba=2*(Aa[3]-Aa[1])/w,kd=2*(Aa[0]-x[0])/w-1,Aa=2*(Aa[1]-x[1])/w-1,Hd(za,tb,Ba,kd,Aa),e.uniform4fv(this.c.d,za),ov(c,Q,q,B*u),e.drawArrays(5,0,4);U?(this.i=r,this.r=x,this.q=m.c):(this.r=this.i=null,this.q=-1,a.animate=!0)}kt(a.usedTiles,m,p,r);var wc=c.i;nt(a,m,n,u,g,F,p,h.aa(),function(a){var b;(b=2!=a.state||nv(c.c,a.d()))||(b=a.d()in wc.b);b||ot(wc,[a,at(n,a.a),n.fa(a.a[0]),q,B*u])},this);ht(a,m);jt(a,m);e=this.u;Md(e);Od(e,(E[0]-x[0])/(x[2]-x[0]),(E[1]-x[1])/(x[3]-x[1]));0!==f.rotation&&Qd(e,
-f.rotation);Pd(e,a.size[0]*f.resolution/(x[2]-x[0]),a.size[1]*f.resolution/(x[3]-x[1]));Od(e,-.5,-.5);return!0};function pv(){this.b=0;this.d={};this.c=this.a=null}k=pv.prototype;k.clear=function(){this.b=0;this.d={};this.c=this.a=null};function nv(a,b){return a.d.hasOwnProperty(b)}k.forEach=function(a,b){for(var c=this.a;null!==c;)a.call(b,c.Gb,c.$c,this),c=c.Ja};k.get=function(a){a=this.d[a];if(a===this.c)return a.Gb;a===this.a?(this.a=this.a.Ja,this.a.nb=null):(a.Ja.nb=a.nb,a.nb.Ja=a.Ja);a.Ja=null;a.nb=this.c;this.c=this.c.Ja=a;return a.Gb};k.fb=function(){return this.b};
-k.N=function(){var a=Array(this.b),b=0,c;for(c=this.c;null!==c;c=c.nb)a[b++]=c.$c;return a};k.ub=function(){var a=Array(this.b),b=0,c;for(c=this.c;null!==c;c=c.nb)a[b++]=c.Gb;return a};k.pop=function(){var a=this.a;delete this.d[a.$c];null!==a.Ja&&(a.Ja.nb=null);this.a=a.Ja;null===this.a&&(this.c=null);--this.b;return a.Gb};k.set=function(a,b){var c={$c:a,Ja:null,nb:this.c,Gb:b};null===this.c?this.a=c:this.c.Ja=c;this.c=c;this.d[a]=c;++this.b};function qv(a,b){this.f=a;this.c=b;this.a={};this.d={};this.b={};this.e=null;y(this.f,"webglcontextlost",this.Qh,!1,this);y(this.f,"webglcontextrestored",this.Rh,!1,this)}
-function mv(a,b){var c=a.c,d=b.c,e=na(b);if(e in a.a)e=a.a[e],c.bindBuffer(34962,e.buffer),Yu(e.Hc,function(a,b){c.bufferSubData(34962,a,new Float32Array(d.slice(a,b)))}),e.Hc.clear();else{var f=c.createBuffer();c.bindBuffer(34962,f);c.bufferData(34962,new Float32Array(d),b.d);var g=new Vu;b.a.push(g);a.a[e]={Sd:b,buffer:f,Hc:g}}}k=qv.prototype;
-k.I=function(){sc(this.a,function(a){ab(a.Sd.a,a.Hc)});var a=this.c;a.isContextLost()||(sc(this.a,function(b){a.deleteBuffer(b.buffer)}),sc(this.b,function(b){a.deleteProgram(b)}),sc(this.d,function(b){a.deleteShader(b)}))};k.Ph=function(){return this.c};function rv(a,b){var c=na(b);if(c in a.d)return a.d[c];var d=a.c,e=d.createShader(b.G());d.shaderSource(e,b.a);d.compileShader(e);return a.d[c]=e}
-function lv(a,b,c){var d=na(b)+"/"+na(c);if(d in a.b)return a.b[d];var e=a.c,f=e.createProgram();e.attachShader(f,rv(a,b));e.attachShader(f,rv(a,c));e.linkProgram(f);return a.b[d]=f}k.Qh=function(){Cc(this.a);Cc(this.d);Cc(this.b);this.e=null};k.Rh=function(){};k.Fd=function(a){if(a==this.e)return!1;this.c.useProgram(a);this.e=a;return!0};function sv(a,b){st.call(this,0,b);this.a=Ff("CANVAS");this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Jf(a,this.a,0);this.o=0;this.q=lg();this.l=!0;this.d=rg(this.a,{antialias:!0,depth:!1,kf:!0,preserveDrawingBuffer:!1,stencil:!0});this.e=new qv(this.a,this.d);y(this.a,"webglcontextlost",this.kh,!1,this);y(this.a,"webglcontextrestored",this.lh,!1,this);this.c=new pv;this.p=null;this.i=new zt(sa(function(a){var b=a[1];a=a[2];var e=b[0]-this.p[0],b=b[1]-this.p[1];
-return 65536*Math.log(a)+Math.sqrt(e*e+b*b)/a},this),function(a){return a[0].d()});this.r=sa(function(){if(!this.i.ka()){Dt(this.i);var a=At(this.i);ov(this,a[0],a[3],a[4])}},this);this.g=0;tv(this)}t(sv,st);
-function ov(a,b,c,d){var e=a.d,f=b.d();if(nv(a.c,f))a=a.c.get(f),e.bindTexture(3553,a.Da),9729!=a.ge&&(e.texParameteri(3553,10240,9729),a.ge=9729),9729!=a.he&&(e.texParameteri(3553,10240,9729),a.he=9729);else{var g=e.createTexture();e.bindTexture(3553,g);if(0<d){var h=a.q.canvas,m=a.q;a.o!=c?(h.width=c,h.height=c,a.o=c):m.clearRect(0,0,c,c);m.drawImage(b.b(),d,d,c,c,0,0,c,c);e.texImage2D(3553,0,6408,6408,5121,h)}else e.texImage2D(3553,0,6408,6408,5121,b.b());e.texParameteri(3553,10240,9729);e.texParameteri(3553,
-10241,9729);e.texParameteri(3553,10242,33071);e.texParameteri(3553,10243,33071);a.c.set(f,{Da:g,ge:9729,he:9729})}}k=sv.prototype;k.Gc=function(a){return a instanceof S?new ev(this,a):a instanceof T?new kv(this,a):null};function uv(a,b,c){var d=a.f;jd(d,b)&&d.dispatchEvent(new Pi(b,d,new Lu,null,c,null,a.e))}k.I=function(){var a=this.d;a.isContextLost()||this.c.forEach(function(b){null===b||a.deleteTexture(b.Da)});ec(this.e);sv.K.I.call(this)};
-k.jf=function(a,b){for(var c=this.d,d;1024<this.c.fb()-this.g;){d=this.c.a.Gb;if(null===d)if(+this.c.a.$c==b.index)break;else--this.g;else c.deleteTexture(d.Da);this.c.pop()}};k.G=function(){return"webgl"};k.kh=function(a){a.preventDefault();this.c.clear();this.g=0;sc(this.b,function(a){a.o()})};k.lh=function(){tv(this);this.f.O()};function tv(a){a=a.d;a.activeTexture(33984);a.blendFuncSeparate(770,771,1,771);a.disable(2884);a.disable(2929);a.disable(3089);a.disable(2960)}
-k.hd=function(a){var b=this.e,c=this.d;if(c.isContextLost())return!1;if(null===a)return this.l&&(dg(this.a,!1),this.l=!1),!1;this.p=a.focus;this.c.set((-a.index).toString(),null);++this.g;var d=[],e=a.layerStatesArray,f=a.viewState.resolution,g,h,m,n;g=0;for(h=e.length;g<h;++g)n=e[g],n.visible&&f>=n.minResolution&&f<n.maxResolution&&"ready"==n.Lb&&(m=xt(this,n.layer),m.b(a,n)&&d.push(n));g=a.size[0]*a.pixelRatio;h=a.size[1]*a.pixelRatio;if(this.a.width!=g||this.a.height!=h)this.a.width=g,this.a.height=
-h;c.bindFramebuffer(36160,null);c.clearColor(0,0,0,0);c.clear(16384);c.enable(3042);c.viewport(0,0,this.a.width,this.a.height);uv(this,"precompose",a);g=0;for(h=d.length;g<h;++g){n=d[g];c=m=xt(this,n.layer);e=a;f=b;dv(c,"precompose",f,e);mv(f,c.Q);m=f.c;var p=n.brightness||1!=n.contrast||n.hue||1!=n.saturation,r=void 0,q=void 0;p?(r=Pu.gb(),q=Qu.gb()):(r=Su.gb(),q=Tu.gb());r=lv(f,r,q);q=void 0;p?null===c.l?(q=new Ru(m,r),c.l=q):q=c.l:null===c.k?(q=new Uu(m,r),c.k=q):q=c.k;f.Fd(r)&&(m.enableVertexAttribArray(q.a),
-m.vertexAttribPointer(q.a,2,5126,!1,16,0),m.enableVertexAttribArray(q.c),m.vertexAttribPointer(q.c,2,5126,!1,16,8),m.uniform1i(q.b,0));m.uniformMatrix4fv(q.f,!1,c.u);m.uniformMatrix4fv(q.e,!1,c.D);p&&m.uniformMatrix4fv(q.g,!1,Mg(c.ba,n.brightness,n.contrast,n.hue,n.saturation));m.uniform1f(q.d,n.opacity);m.bindTexture(3553,c.Da);m.drawArrays(5,0,4);dv(c,"postcompose",f,e)}this.l||(dg(this.a,!0),this.l=!0);tt(a);1024<this.c.fb()-this.g&&a.postRenderFunctions.push(sa(this.jf,this));this.i.ka()||(a.postRenderFunctions.push(this.r),
-a.animate=!0);uv(this,"postcompose",a);yt(this,a);a.postRenderFunctions.push(ut)};var vv=["canvas","webgl","dom"];
-function W(a){pd.call(this);var b=wv(a);this.Te=l(a.pixelRatio)?a.pixelRatio:sg;this.sd=b.logos;this.o=new xs(this.ri,void 0,this);dc(this,this.o);this.Cc=Id();this.Ve=Id();this.rd=0;this.k=this.Q=this.ba=this.d=null;this.b=Cf("DIV","ol-viewport");this.b.style.position="relative";this.b.style.overflow="hidden";this.b.style.width="100%";this.b.style.height="100%";this.b.style.msTouchAction="none";yg&&(this.b.className="ol-touch");this.Oa=Cf("DIV","ol-overlaycontainer");this.b.appendChild(this.Oa);
-this.r=Cf("DIV","ol-overlaycontainer-stopevent");y(this.r,["click","dblclick","mousedown","touchstart","MSPointerDown",ti,vb?"DOMMouseScroll":"mousewheel"],gc);this.b.appendChild(this.r);a=new li(this);y(a,vc(wi),this.oe,!1,this);dc(this,a);this.ma=b.keyboardEventTarget;this.q=new Hs;y(this.q,"key",this.be,!1,this);dc(this,this.q);a=new Ps(this.b);y(a,"mousewheel",this.be,!1,this);dc(this,a);this.i=b.controls;this.qd=b.deviceOptions;this.g=b.interactions;this.l=b.overlays;this.aa=new b.ti(this.b,
-this);dc(this,this.aa);this.ac=new Cs;dc(this,this.ac);y(this.ac,"resize",this.F,!1,this);this.X=null;this.u=[];this.na=[];this.Kb=new Et(sa(this.Mf,this),sa(this.qg,this));this.ea={};y(this,td("layergroup"),this.$f,!1,this);y(this,td("view"),this.rg,!1,this);y(this,td("size"),this.og,!1,this);y(this,td("target"),this.pg,!1,this);this.L(b.Gi);this.i.forEach(function(a){a.setMap(this)},this);y(this.i,"add",function(a){a.element.setMap(this)},!1,this);y(this.i,"remove",function(a){a.element.setMap(null)},
-!1,this);this.g.forEach(function(a){a.setMap(this)},this);y(this.g,"add",function(a){a.element.setMap(this)},!1,this);y(this.g,"remove",function(a){a.element.setMap(null)},!1,this);this.l.forEach(function(a){a.setMap(this)},this);y(this.l,"add",function(a){a.element.setMap(this)},!1,this);y(this.l,"remove",function(a){a.element.setMap(null)},!1,this)}t(W,pd);k=W.prototype;k.We=function(a){this.i.push(a)};k.Xe=function(a){this.g.push(a)};k.Ye=function(a){this.ib().Ab().push(a)};k.Ze=function(a){this.l.push(a)};
-k.Ea=function(a){this.O();Array.prototype.push.apply(this.u,arguments)};k.I=function(){Kf(this.b);W.K.I.call(this)};k.Cd=function(a,b,c,d,e){if(null!==this.d)return a=this.ta(a),vt(this.aa,a,this.d,b,l(c)?c:null,l(d)?d:dd,l(e)?e:null)};k.qf=function(a){return this.ta(this.xd(a))};k.xd=function(a){if(l(a.changedTouches)){a=a.changedTouches[0];var b=Zf(this.b);return[a.clientX-b.x,a.clientY-b.y]}a=Yf(a,this.b);return[a.x,a.y]};k.rc=function(){return this.get("target")};W.prototype.getTarget=W.prototype.rc;
-k=W.prototype;k.ta=function(a){var b=this.d;if(null===b)return null;a=a.slice();return rt(b.pixelToCoordinateMatrix,a,a)};k.pf=function(){return this.i};k.Gf=function(){return this.l};k.uf=function(){return this.g};k.ib=function(){return this.get("layergroup")};W.prototype.getLayerGroup=W.prototype.ib;W.prototype.Ue=function(){var a=this.ib();if(l(a))return a.Ab()};W.prototype.f=function(a){var b=this.d;if(null===b)return null;a=a.slice(0,2);return rt(b.coordinateToPixelMatrix,a,a)};
-W.prototype.e=function(){return this.get("size")};W.prototype.getSize=W.prototype.e;W.prototype.a=function(){return this.get("view")};W.prototype.getView=W.prototype.a;k=W.prototype;k.Of=function(){return this.b};k.Mf=function(a,b,c,d){var e=this.d;if(!(null!==e&&b in e.wantedTiles&&e.wantedTiles[b][kf(a.a)]))return Infinity;a=c[0]-e.focus[0];c=c[1]-e.focus[1];return 65536*Math.log(d)+Math.sqrt(a*a+c*c)/d};k.be=function(a,b){var c=new ji(b||a.type,this,a);this.oe(c)};
-k.oe=function(a){if(null!==this.d){this.X=a.coordinate;a.frameState=this.d;var b=this.g.a,c;if(!1!==this.dispatchEvent(a))for(c=b.length-1;0<=c&&b[c].La(a);c--);}};
-k.lg=function(){var a=this.d,b=this.Kb;if(!b.ka()){var c=16,d=c,e=0;if(null!==a){var e=a.viewHints,f=this.qd;e[0]&&(c=!1===f.loadTilesWhileAnimating?0:8,d=2);e[1]&&(c=!1===f.loadTilesWhileInteracting?0:8,d=2);e=uc(a.wantedTiles)}c*=e;d*=e;if(b.d<c){Dt(b);c=Math.min(c-b.d,d,b.fb());for(d=0;d<c;++d)e=At(b)[0],Wc(e,"change",b.g,!1,b),0==e.state&&(e.state=1,e.e=[Wc(e.c,"error",e.k,!1,e),Wc(e.c,"load",e.p,!1,e)],e.o(e,e.l));b.d+=c}}b=this.na;c=0;for(d=b.length;c<d;++c)b[c](this,a);b.length=0};k.og=function(){this.O()};
-k.pg=function(){var a=this.rc(),a=l(a)?yf(a):null;Os(this.q);null===a?Kf(this.b):(a.appendChild(this.b),Is(this.q,null===this.ma?a:this.ma));this.F()};k.qg=function(){this.O()};k.sg=function(){this.O()};k.rg=function(){null!==this.Q&&(Yc(this.Q),this.Q=null);var a=this.a();null!=a&&(this.Q=y(a,"propertychange",this.sg,!1,this));this.O()};k.ag=function(){this.O()};k.bg=function(){this.O()};
-k.$f=function(){if(null!==this.k){for(var a=this.k.length,b=0;b<a;++b)Yc(this.k[b]);this.k=null}a=this.ib();null!=a&&(this.k=[y(a,"propertychange",this.bg,!1,this),y(a,"change",this.ag,!1,this)]);this.O()};k.si=function(){var a=this.o;ys(a);a.Vd()};k.O=function(){null!=this.o.T||this.o.start()};k.mi=function(a){if(l(this.i.remove(a)))return a};k.oi=function(a){var b;l(this.g.remove(a))&&(b=a);return b};k.pi=function(a){return this.ib().Ab().remove(a)};k.qi=function(a){if(l(this.l.remove(a)))return a};
-k.ri=function(a){var b,c,d,e=this.e(),f=this.a(),g=null;if(l(e)&&0<e[0]&&0<e[1]&&l(f)&&Ye(f)){var g=cb(f.l),h=this.ib().Oa(),m={};b=0;for(c=h.length;b<c;++b)m[na(h[b].layer)]=h[b];d=Xe(f);g={animate:!1,attributions:{},coordinateToPixelMatrix:this.Cc,extent:null,focus:null===this.X?d.center:this.X,index:this.rd++,layerStates:m,layerStatesArray:h,logos:this.sd,pixelRatio:this.Te,pixelToCoordinateMatrix:this.Ve,postRenderFunctions:[],size:e,skippedFeatureUids:this.ea,tileQueue:this.Kb,time:a,usedTiles:{},
-viewState:d,viewHints:g,wantedTiles:{}}}a=this.u;b=e=0;for(c=a.length;b<c;++b)f=a[b],f(this,g)&&(a[e++]=f);a.length=e;null!==g&&(g.extent=ne(d.center,d.resolution,d.rotation,g.size));this.d=g;this.aa.hd(g);null!==g&&(g.animate&&this.O(),Array.prototype.push.apply(this.na,g.postRenderFunctions),0!==this.u.length||g.viewHints[0]||g.viewHints[1]||this.ba&&ae(g.extent,this.ba)||(this.dispatchEvent(new Rg("moveend",this,g)),this.ba=Wd(g.extent)));this.dispatchEvent(new Rg("postrender",this,g));sr(this.lg,
-this)};k.Je=function(a){this.set("layergroup",a)};W.prototype.setLayerGroup=W.prototype.Je;W.prototype.D=function(a){this.set("size",a)};W.prototype.setSize=W.prototype.D;W.prototype.sa=function(a){this.set("target",a)};W.prototype.setTarget=W.prototype.sa;W.prototype.Va=function(a){this.set("view",a)};W.prototype.setView=W.prototype.Va;W.prototype.cb=function(a){a=na(a).toString();this.ea[a]=!0;this.O()};
-W.prototype.F=function(){var a=this.rc(),a=l(a)?yf(a):null;null===a?this.D(void 0):(a=eg(a),this.D([a.width,a.height]))};W.prototype.Lb=function(a){a=na(a).toString();delete this.ea[a];this.O()};
-function wv(a){var b=null;l(a.keyboardEventTarget)&&(b=ja(a.keyboardEventTarget)?document.getElementById(a.keyboardEventTarget):a.keyboardEventTarget);var c={},d={};if(!l(a.logo)||"boolean"==typeof a.logo&&a.logo)d["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszWWMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvYasvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvXH1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1VkbMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLPVcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqTacrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaarldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+HizeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDnBAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSFhYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJREFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxCBrb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7ahgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCnB3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDgq82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC"]="http://openlayers.org/";
-else{var e=a.logo;ja(e)?d[e]="":ma(e)&&(d[e.src]=e.href)}e=a.layers instanceof R?a.layers:new R({layers:a.layers});c.layergroup=e;c.target=a.target;c.view=l(a.view)?a.view:new z;var e=st,f;l(a.renderer)?ha(a.renderer)?f=a.renderer:ja(a.renderer)&&(f=[a.renderer]):f=vv;var g,h;g=0;for(h=f.length;g<h;++g){var m=f[g];if("canvas"==m){if(vg){e=Au;break}}else if("dom"==m){e=Ku;break}else if("webgl"==m&&Bg){e=sv;break}}var n;l(a.controls)?n=ha(a.controls)?new A(cb(a.controls)):a.controls:n=Hh();f=l(a.deviceOptions)?
-a.deviceOptions:{};var p;l(a.interactions)?p=ha(a.interactions)?new A(cb(a.interactions)):a.interactions:p=Kt();a=l(a.overlays)?ha(a.overlays)?new A(cb(a.overlays)):a.overlays:new A;return{controls:n,deviceOptions:f,interactions:p,keyboardEventTarget:b,logos:d,overlays:a,ti:e,Gi:c}}Rt();function xv(a,b,c,d){this.extent=a;this.height=b;this.a=c;this.value=d}function yv(a,b){return a.extent[0]-b.extent[0]}function zv(a,b){return a.extent[1]-b.extent[1]}function Av(a,b,c,d){a=a.a;for(d=Zd(d);b<c;++b)be(d,a[b].extent);return d}xv.prototype.remove=function(a,b,c){var d=this.a,e=d.length,f,g;if(1==this.height)for(g=0;g<e;++g){if(f=d[g],f.value===b)return Ra.splice.call(d,g,1),!0}else for(g=0;g<e;++g)if(f=d[g],Yd(f.extent,a)){c.push(f);if(f.remove(a,b,c))return!0;c.pop()}return!1};
-function Bv(a){var b=Zd(a.extent);a=a.a;var c,d;c=0;for(d=a.length;c<d;++c)be(b,a[c].extent)}function Cv(a){this.b=Math.max(4,l(a)?a:9);this.d=Math.max(2,Math.ceil(.4*this.b));this.a=new xv(Sd(),1,[],null);this.c={}}function Dv(a,b,c){var d=b.a;a=a.d;var e=d.length;gb(d,c);c=Av(b,0,a);var f=Av(b,e-a,e),g=fe(c)+ge(c)+(fe(f)+ge(f));for(b=a;b<e-a;++b)be(c,d[b].extent),g+=fe(c)+ge(c);for(b=e-a-1;b>=a;--b)be(f,d[b].extent),g+=fe(f)+ge(f);return g}k=Cv.prototype;
-k.clear=function(){var a=this.a;a.extent=Zd(this.a.extent);a.height=1;a.a.length=0;a.value=null;Cc(this.c)};k.forEach=function(a,b){return Ev(this.a,a,b)};function Ev(a,b,c){for(var d=[a],e,f,g;0<d.length;)if(a=d.pop(),e=a.a,1==a.height)for(a=0,f=e.length;a<f;++a){if(g=b.call(c,e[a].value))return g}else d.push.apply(d,e)}function Fv(a,b,c){return Gv(a,b,c,void 0)}
-function Gv(a,b,c,d){a=[a.a];for(var e;0<a.length;)if(e=a.pop(),pe(b,e.extent))if(null===e.a){if(e=c.call(d,e.value))return e}else if(Yd(b,e.extent)){if(e=Ev(e,c,d))return e}else a.push.apply(a,e.a)}function Hv(a){var b=[];a.forEach(function(a){b.push(a)});return b}function Iv(a,b){var c=[];Fv(a,b,function(a){c.push(a)});return c}k.s=function(a){return qe(this.a.extent,a)};function Jv(a,b,c){var d=na(c).toString();Kv(a,b,c,a.a.height-1);a.c[d]=Wd(b)}
-function Kv(a,b,c,d){for(var e=[a.a],f=a.a;null!==f.a&&e.length-1!=d;){var g=Infinity,h=Infinity,f=f.a,m=null,n,p;n=0;for(p=f.length;n<p;++n){var r=f[n],q=de(r.extent),u=r.extent,x=b,B=Math.min(u[0],x[0]),E=Math.min(u[1],x[1]),F=Math.max(u[2],x[2]),u=Math.max(u[3],x[3]),B=(F-B)*(u-E)-q;B<h?(h=B,g=Math.min(q,g),m=r):B==h&&q<g&&(g=q,m=r)}f=m;e.push(f)}d=f;d.a.push(new xv(b,0,null,c));be(d.extent,b);for(c=e.length-1;0<=c;--c)if(e[c].a.length>a.b){g=a;h=e;f=c;d=h[f];p=g;m=d;n=Dv(p,m,yv);p=Dv(p,m,zv);
-n<p&&gb(m.a,yv);m=d;n=g.d;p=m.a.length;q=r=Infinity;B=Sd();E=Sd();F=0;u=void 0;for(u=n;u<=p-n;++u){var B=Av(m,0,u,B),E=Av(m,u,p,E),x=oe(B,E),x=de(x),w=de(B)+de(E);x<r?(r=x,q=Math.min(w,q),F=u):x==r&&w<q&&(q=w,F=u)}m=d.a.splice(F);m=new xv(Sd(),d.height,m,null);Bv(d);Bv(m);f?h[f-1].a.push(m):(h=m,f=d.height+1,m=be(d.extent.slice(),h.extent),g.a=new xv(m,f,[d,h],null))}else break;for(;0<=c;--c)be(e[c].extent,b)}k.ka=function(){return 0===this.a.a.length};
-k.remove=function(a){var b=na(a).toString(),c=this.c[b];delete this.c[b];return Lv(this,c,a)};function Lv(a,b,c){var d=a.a,e=[d];if(b=d.remove(b,c,e))for(c=e.length-1;0<=c;--c)d=e[c],0===d.a.length?0<c?ab(e[c-1].a,d):a.clear():Bv(d);return b}k.update=function(a,b){var c=na(b).toString(),d=this.c[c];ae(d,a)||(Lv(this,d,b),Kv(this,a,b,this.a.height-1),this.c[c]=Wd(a,d))};function Mv(a){a=l(a)?a:{};Ss.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,state:l(a.state)?a.state:void 0});this.b=new Cv;this.d={};this.e={};this.g={};this.o={};l(a.features)&&this.Pa(a.features)}t(Mv,Ss);k=Mv.prototype;k.za=function(a){Nv(this,a);this.n()};
-function Nv(a,b){var c=na(b).toString();a.o[c]=[y(b,"change",a.te,!1,a),y(b,"propertychange",a.te,!1,a)];var d=b.J();null!=d?(d=d.s(),Jv(a.b,d,b)):a.d[c]=b;d=b.T;l(d)?a.e[d.toString()]=b:a.g[c]=b;a.dispatchEvent(new Ov("addfeature",b))}k.oa=function(a){this.Pa(a);this.n()};k.Pa=function(a){var b,c;b=0;for(c=a.length;b<c;++b)Nv(this,a[b])};k.clear=function(){this.b.forEach(this.Md,this);this.b.clear();sc(this.d,this.Md,this);Cc(this.d);this.n()};k.Ha=function(a,b){return this.b.forEach(a,b)};
-function Pv(a,b,c){a.pa([b[0],b[1],b[0],b[1]],function(a){if(a.J().zb(b[0],b[1]))return c.call(void 0,a)})}k.pa=function(a,b,c){return Gv(this.b,a,b,c)};k.Kc=function(a,b,c,d){return this.pa(a,c,d)};k.qa=function(){var a=Hv(this.b);Bc(this.d)||db(a,vc(this.d));return a};k.va=function(a){var b=[];Pv(this,a,function(a){b.push(a)});return b};
-k.Ia=function(a){var b=a[0],c=a[1],d=null,e=[NaN,NaN],f=Infinity,g=[-Infinity,-Infinity,Infinity,Infinity];Fv(this.b,g,function(a){var m=a.J(),n=f;f=m.Fa(b,c,e,f);f<n&&(d=a,a=Math.sqrt(f),g[0]=b-a,g[1]=c-a,g[2]=b+a,g[3]=c+a)});return d};k.s=function(){return this.b.s()};k.ua=function(a){a=this.e[a.toString()];return l(a)?a:null};
-k.te=function(a){a=a.target;var b=na(a).toString(),c=a.J();null!=c?(c=c.s(),b in this.d?(delete this.d[b],Jv(this.b,c,a)):this.b.update(c,a)):b in this.d||(this.b.remove(a),this.d[b]=a);c=a.T;l(c)?(c=c.toString(),b in this.g?(delete this.g[b],this.e[c]=a):this.e[c]!==a&&(Qv(this,a),this.e[c]=a)):b in this.g||(Qv(this,a),this.g[b]=a);this.n()};k.ka=function(){return this.b.ka()&&Bc(this.d)};k.bd=ca;k.Ma=function(a){var b=na(a).toString();b in this.d?delete this.d[b]:this.b.remove(a);this.Md(a);this.n()};
-k.Md=function(a){var b=na(a).toString();Ta(this.o[b],Yc);delete this.o[b];var c=a.T;l(c)?delete this.e[c.toString()]:delete this.g[b];this.dispatchEvent(new Ov("removefeature",a))};function Qv(a,b){for(var c in a.e)if(a.e[c]===b){delete a.e[c];break}}function Ov(a,b){fc.call(this,a);this.feature=b}t(Ov,fc);function Rv(a,b){fc.call(this,a);this.feature=b}t(Rv,fc);
-function Sv(a){is.call(this);this.r=null;this.F=l(a.source)?a.source:null;this.u=l(a.features)?a.features:null;this.ba=l(a.snapTolerance)?a.snapTolerance:12;this.Q=l(a.minPointsPerRing)?a.minPointsPerRing:3;var b=this.o=a.type,c;if("Point"===b||"MultiPoint"===b)c=Tv;else if("LineString"===b||"MultiLineString"===b)c=Uv;else if("Polygon"===b||"MultiPolygon"===b)c=Vv;this.a=c;this.d=this.k=this.i=this.e=this.f=null;this.q=new bk({style:l(a.style)?a.style:Wv()});this.D=a.geometryName;this.X=l(a.condition)?
-a.condition:zi}t(Sv,is);function Wv(){var a=Oi();return function(b){return a[b.J().G()]}}Sv.prototype.setMap=function(a){null===a&&Xv(this);this.q.setMap(a);Sv.K.setMap.call(this,a)};Sv.prototype.La=function(a){var b;b=a.map;if(Mf(document,b.b)&&"none"!=b.b.style.display){var c=b.e();null==c||0>=c[0]||0>=c[1]?b=!1:(b=b.a(),b=l(b)&&Ye(b)?!0:!1)}else b=!1;if(!b)return!0;b=!0;a.type===ui?b=Yv(this,a):a.type===oi&&(b=!1);return Sv.K.La.call(this,a)&&b};
-Sv.prototype.$a=function(a){return this.X(a)?(this.r=a.pixel,!0):!1};
-Sv.prototype.ab=function(a){var b=this.r,c=a.pixel,d=b[0]-c[0],b=b[1]-c[1],c=!0;if(4>=d*d+b*b){Yv(this,a);if(null===this.f)Zv(this,a);else if(this.a===Tv||$v(this,a)){a=Xv(this);var e,d=a.J();this.a===Tv?e=d.H():this.a===Uv?(e=d.H(),e.pop(),d.P(e)):this.a===Vv&&(this.d[0].pop(),this.d[0].push(this.d[0][0]),d.P(this.d),e=d.H());"MultiPoint"===this.o?a.Ba(new Aj([e])):"MultiLineString"===this.o?a.Ba(new uj([e])):"MultiPolygon"===this.o&&a.Ba(new Pj([e]));null===this.u||this.u.push(a);null===this.F||
-this.F.za(a);this.dispatchEvent(new Rv("drawend",a))}else e=a.coordinate,a=this.e.J(),this.a===Uv?(this.f=e.slice(),d=a.H(),d.push(e.slice()),a.P(d)):this.a===Vv&&(this.d[0].push(e.slice()),a.P(this.d)),aw(this);c=!1}return c};
-function Yv(a,b){if(a.a===Tv&&null===a.f)Zv(a,b);else if(null===a.f){var c=b.coordinate.slice();null===a.i?(a.i=new G(new yj(c)),aw(a)):a.i.J().P(c)}else{var c=b.coordinate,d=a.e.J(),e,f;a.a===Tv?(f=d.H(),f[0]=c[0],f[1]=c[1],d.P(f)):(a.a===Uv?e=d.H():a.a===Vv&&(e=a.d[0]),$v(a,b)&&(c=a.f.slice()),a.i.J().P(c),f=e[e.length-1],f[0]=c[0],f[1]=c[1],a.a===Uv?d.P(e):a.a===Vv&&(a.k.J().P(e),d.P(a.d)));aw(a)}return!0}
-function $v(a,b){var c=!1;if(null!==a.e){var d=a.e.J(),e=!1,f=[a.f];a.a===Uv?e=2<d.H().length:a.a===Vv&&(e=d.H()[0].length>a.Q,f=[a.d[0][0],a.d[0][a.d[0].length-2]]);if(e)for(var d=b.map,e=0,g=f.length;e<g;e++){var h=f[e],m=d.f(h),n=b.pixel,c=n[0]-m[0],m=n[1]-m[1];if(c=Math.sqrt(c*c+m*m)<=a.ba){a.f=h;break}}}return c}
-function Zv(a,b){var c=b.coordinate;a.f=c;var d;a.a===Tv?d=new yj(c.slice()):a.a===Uv?d=new H([c.slice(),c.slice()]):a.a===Vv&&(a.k=new G(new H([c.slice(),c.slice()])),a.d=[[c.slice(),c.slice()]],d=new I(a.d));a.e=new G;l(a.D)&&a.e.e(a.D);a.e.Ba(d);aw(a);a.dispatchEvent(new Rv("drawstart",a.e))}function Xv(a){a.f=null;var b=a.e;null!==b&&(a.e=null,a.i=null,a.k=null,a.q.a.clear());return b}
-function aw(a){var b=[];null===a.e||b.push(a.e);null===a.k||b.push(a.k);null===a.i||b.push(a.i);a.q.xc(new A(b))}var Tv="Point",Uv="LineString",Vv="Polygon";function bw(a){is.call(this);this.D=l(a.deleteCondition)?a.deleteCondition:gd(zi,yi);this.u=this.d=null;this.r=[0,0];this.a=new Cv;this.f=l(a.pixelTolerance)?a.pixelTolerance:10;this.q=!1;this.e=null;this.i=new bk({style:l(a.style)?a.style:cw()});this.o={Point:this.Mi,LineString:this.Re,LinearRing:this.Re,Polygon:this.Ni,MultiPoint:this.Ki,MultiLineString:this.Ji,MultiPolygon:this.Li,GeometryCollection:this.Ii};this.k=a.features;this.k.forEach(this.qe,this);y(this.k,"add",this.Vf,!1,this);y(this.k,
-"remove",this.Wf,!1,this)}t(bw,is);k=bw.prototype;k.qe=function(a){var b=a.J();l(this.o[b.G()])&&this.o[b.G()].call(this,a,b);a=this.g;null===a||dw(this,this.r,a)};k.setMap=function(a){this.i.setMap(a);bw.K.setMap.call(this,a)};k.Vf=function(a){this.qe(a.element)};k.Wf=function(a){var b=a.element;a=this.a;var c,d=[];Fv(a,b.J().s(),function(a){b===a.feature&&d.push(a)});for(c=d.length-1;0<=c;--c)a.remove(d[c]);null!==this.d&&0===this.k.xb()&&(this.i.cd(this.d),this.d=null)};
-k.Mi=function(a,b){var c=b.H(),c={feature:a,geometry:b,da:[c,c]};Jv(this.a,b.s(),c)};k.Ki=function(a,b){var c=b.H(),d,e,f;e=0;for(f=c.length;e<f;++e)d=c[e],d={feature:a,geometry:b,depth:[e],index:e,da:[d,d]},Jv(this.a,b.s(),d)};k.Re=function(a,b){var c=b.H(),d,e,f,g;d=0;for(e=c.length-1;d<e;++d)f=c.slice(d,d+2),g={feature:a,geometry:b,index:d,da:f},Jv(this.a,Rd(f),g)};
-k.Ji=function(a,b){var c=b.H(),d,e,f,g,h,m,n;g=0;for(h=c.length;g<h;++g)for(d=c[g],e=0,f=d.length-1;e<f;++e)m=d.slice(e,e+2),n={feature:a,geometry:b,depth:[g],index:e,da:m},Jv(this.a,Rd(m),n)};k.Ni=function(a,b){var c=b.H(),d,e,f,g,h,m,n;g=0;for(h=c.length;g<h;++g)for(d=c[g],e=0,f=d.length-1;e<f;++e)m=d.slice(e,e+2),n={feature:a,geometry:b,depth:[g],index:e,da:m},Jv(this.a,Rd(m),n)};
-k.Li=function(a,b){var c=b.H(),d,e,f,g,h,m,n,p,r,q;m=0;for(n=c.length;m<n;++m)for(p=c[m],g=0,h=p.length;g<h;++g)for(d=p[g],e=0,f=d.length-1;e<f;++e)r=d.slice(e,e+2),q={feature:a,geometry:b,depth:[g,m],index:e,da:r},Jv(this.a,Rd(r),q)};k.Ii=function(a,b){var c,d=b.e;for(c=0;c<d.length;++c)this.o[d[c].G()].call(this,a,d[c])};function ew(a,b){var c=a.d;null===c?(c=new G(new yj(b)),a.d=c,a.i.ke(c)):c.J().P(b)}
-k.$a=function(a){dw(this,a.pixel,a.map);this.e=[];var b=this.d;if(null!==b){a=[];for(var b=b.J().H(),c=Rd([b]),c=Iv(this.a,c),d={},e=0,f=c.length;e<f;++e){var g=c[e],h=g.da;na(g.feature)in d||(d[na(g.feature)]=!0);Ad(h[0],b)?this.e.push([g,0]):Ad(h[1],b)?this.e.push([g,1]):na(h)in this.u&&a.push([g,b])}for(e=a.length-1;0<=e;--e)this.tg.apply(this,a[e])}return null!==this.d};
-k.jb=function(a){a=a.coordinate;for(var b=0,c=this.e.length;b<c;++b){var d=this.e[b],e=d[0],f=e.depth,g=e.geometry,h=g.H(),m=e.da,d=d[1];switch(g.G()){case "Point":h=a;m[0]=m[1]=a;break;case "MultiPoint":h[e.index]=a;m[0]=m[1]=a;break;case "LineString":h[e.index+d]=a;m[d]=a;break;case "MultiLineString":h[f[0]][e.index+d]=a;m[d]=a;break;case "Polygon":h[f[0]][e.index+d]=a;m[d]=a;break;case "MultiPolygon":h[f[1]][f[0]][e.index+d]=a,m[d]=a}g.P(h);ew(this,a)}};
-k.ab=function(){for(var a,b=this.e.length-1;0<=b;--b)a=this.e[b][0],this.a.update(Rd(a.da),a);return!1};
-k.La=function(a){var b,c=a.map.a();cb(c.l)[1]||a.type!=ui||(this.r=a.pixel,dw(this,a.pixel,a.map));if(null!==this.d&&this.q&&this.D(a)){this.d.J();b=this.e;var c={},d=!1,e,f,g,h,m,n,p,r,q;for(m=b.length-1;0<=m;--m)if(g=b[m],r=g[0],h=r.geometry,f=h.H(),q=na(r.feature),e=p=n=void 0,0===g[1]?(p=r,n=r.index):1==g[1]&&(e=r,n=r.index+1),q in c||(c[q]=[e,p,n]),g=c[q],l(e)&&(g[0]=e),l(p)&&(g[1]=p),l(g[0])&&l(g[1])){e=f;d=!1;p=n-1;switch(h.G()){case "MultiLineString":f[r.depth[0]].splice(n,1);d=!0;break;case "LineString":f.splice(n,
-1);d=!0;break;case "MultiPolygon":e=e[r.depth[1]];case "Polygon":e=e[r.depth[0]],4<e.length&&(n==e.length-1&&(n=0),e.splice(n,1),d=!0,0===n&&(e.pop(),e.push(e[0]),p=e.length-1))}d&&(this.a.remove(g[0]),this.a.remove(g[1]),h.P(f),f={depth:r.depth,feature:r.feature,geometry:r.geometry,index:p,da:[g[0].da[0],g[1].da[1]]},Jv(this.a,Rd(f.da),f),fw(this,h,n,r.depth,-1),this.i.cd(this.d),this.d=null)}b=d}return bw.K.La.call(this,a)&&!b};
-function dw(a,b,c){function d(a,b){return Cd(e,wd(e,a.da))-Cd(e,wd(e,b.da))}var e=c.ta(b),f=c.ta([b[0]-a.f,b[1]+a.f]),g=c.ta([b[0]+a.f,b[1]-a.f]),f=Rd([f,g]),f=Iv(a.a,f);if(0<f.length){f.sort(d);var g=f[0].da,h=wd(e,g),m=c.f(h);if(Math.sqrt(Cd(b,m))<=a.f){b=c.f(g[0]);c=c.f(g[1]);b=Cd(m,b);c=Cd(m,c);a.q=Math.sqrt(Math.min(b,c))<=a.f;a.q&&(h=b>c?g[1]:g[0]);ew(a,h);c={};c[na(g)]=!0;b=1;for(m=f.length;b<m;++b)if(h=f[b].da,Ad(g[0],h[0])&&Ad(g[1],h[1])||Ad(g[0],h[1])&&Ad(g[1],h[0]))c[na(h)]=!0;else break;
-a.u=c;return}}null!==a.d&&(a.i.cd(a.d),a.d=null)}
-k.tg=function(a,b){var c=a.da,d=a.feature,e=a.geometry,f=a.depth,g=a.index,h;switch(e.G()){case "MultiLineString":h=e.H();h[f[0]].splice(g+1,0,b);break;case "Polygon":h=e.H();h[f[0]].splice(g+1,0,b);break;case "MultiPolygon":h=e.H();h[f[1]][f[0]].splice(g+1,0,b);break;case "LineString":h=e.H();h.splice(g+1,0,b);break;default:return}e.P(h);h=this.a;h.remove(a);fw(this,e,g,f,1);var m={da:[c[0],b],feature:d,geometry:e,depth:f,index:g};Jv(h,Rd(m.da),m);this.e.push([m,1]);c={da:[b,c[1]],feature:d,geometry:e,
-depth:f,index:g+1};Jv(h,Rd(c.da),c);this.e.push([c,0])};k.kd=ed;function fw(a,b,c,d,e){Fv(a.a,b.s(),function(a){a.geometry===b&&(!l(d)||ib(a.depth,d))&&a.index>c&&(a.index+=e)})}function cw(){var a=Oi();return function(){return a.Point}};function gw(a){jr.call(this);a=l(a)?a:{};this.e=l(a.condition)?a.condition:yi;this.d=l(a.addCondition)?a.addCondition:cd;this.f=l(a.removeCondition)?a.removeCondition:cd;this.i=l(a.toggleCondition)?a.toggleCondition:Ai;var b;if(l(a.layers))if(la(a.layers))b=a.layers;else{var c=a.layers;b=function(a){return Za(c,a)}}else b=dd;this.b=b;this.a=new bk({style:l(a.style)?a.style:hw()});a=this.a.a;y(a,"add",this.gh,!1,this);y(a,"remove",this.ni,!1,this)}t(gw,jr);k=gw.prototype;k.hh=function(){return this.a.a};
-k.La=function(a){if(!this.e(a))return!0;var b=this.d(a),c=this.f(a),d=this.i(a),e=a.map,f=this.a.a;if(b||c||d){var g=[],h=[];e.Cd(a.pixel,function(a){var e=Sa(f.a,a);-1==e?(b||d)&&h.push(a):(c||d)&&g.push(e)},void 0,this.b);for(a=g.length-1;0<=a;--a)f.gd(g[a]);f.je(h)}else a=e.Cd(a.pixel,function(a){return a},void 0,this.b),l(a)&&1==f.xb()&&f.item(0)==a||(0!==f.xb()&&f.clear(),l(a)&&f.push(a));return!1};
-k.setMap=function(a){var b=this.g,c=this.a.a;null===b||c.forEach(b.Lb,b);gw.K.setMap.call(this,a);this.a.setMap(a);null===a||c.forEach(a.cb,a)};function hw(){var a=Oi();db(a.Polygon,a.LineString);db(a.GeometryCollection,a.LineString);return function(b){return a[b.J().G()]}}k.gh=function(a){a=a.element;var b=this.g;null===b||b.cb(a)};k.ni=function(a){a=a.element;var b=this.g;null===b||b.Lb(a)};function X(a){a=l(a)?a:{};V.call(this,a);this.aa=null;y(this,td("gradient"),this.qd,!1,this);this.Kb(l(a.gradient)?a.gradient:iw);var b=jw(l(a.radius)?a.radius:8,l(a.blur)?a.blur:15,l(a.shadow)?a.shadow:250),c=Array(256),d=l(a.weight)?a.weight:"weight",e;ja(d)?e=function(a){return a.get(d)}:e=d;this.na(function(a){a=e(a);a=l(a)?Jb(a,0,1):1;var d=255*a|0,h=c[d];l(h)||(h=[new Li({image:new Rn({opacity:a,src:b})})],c[d]=h);return h});this.set("renderOrder",null);y(this,"render",this.rd,!1,this)}
-t(X,V);var iw=["#00f","#0ff","#0f0","#ff0","#f00"];function jw(a,b,c){var d=a+b+1,e=2*d,e=lg(e,e);e.shadowOffsetX=e.shadowOffsetY=c;e.shadowBlur=b;e.shadowColor="#000";e.beginPath();b=d-c;e.arc(b,b,a,0,2*Math.PI,!0);e.fill();return e.canvas.toDataURL()}X.prototype.sa=function(){return this.get("gradient")};X.prototype.getGradient=X.prototype.sa;
-X.prototype.qd=function(){for(var a=this.sa(),b=lg(1,256),c=b.createLinearGradient(0,0,1,256),d=1/(a.length-1),e=0,f=a.length;e<f;++e)c.addColorStop(e*d,a[e]);b.fillStyle=c;b.fillRect(0,0,1,256);this.aa=b.getImageData(0,0,1,256).data};X.prototype.rd=function(a){a=a.context;var b=a.canvas,b=a.getImageData(0,0,b.width,b.height),c=b.data,d,e,f;d=0;for(e=c.length;d<e;d+=4)if(f=4*c[d+3])c[d]=this.aa[f],c[d+1]=this.aa[f+1],c[d+2]=this.aa[f+2];a.putImageData(b,0,0)};
-X.prototype.Kb=function(a){this.set("gradient",a)};X.prototype.setGradient=X.prototype.Kb;function kw(a){return[a]};function Z(a){pd.call(this);this.r=l(a.insertFirst)?a.insertFirst:!0;this.u=l(a.stopEvent)?a.stopEvent:!0;this.b=Ff("DIV");this.b.style.position="absolute";this.a={Dc:"",ad:"",jd:"",ld:"",visible:!0};this.d=null;y(this,td("element"),this.Uf,!1,this);y(this,td("map"),this.fg,!1,this);y(this,td("offset"),this.hg,!1,this);y(this,td("position"),this.jg,!1,this);y(this,td("positioning"),this.kg,!1,this);l(a.element)&&this.Nd(a.element);this.k(l(a.offset)?a.offset:[0,0]);this.q(l(a.positioning)?a.positioning:
-"top-left");l(a.position)&&this.o(a.position)}t(Z,pd);Z.prototype.i=function(){return this.get("element")};Z.prototype.getElement=Z.prototype.i;Z.prototype.e=function(){return this.get("map")};Z.prototype.getMap=Z.prototype.e;Z.prototype.f=function(){return this.get("offset")};Z.prototype.getOffset=Z.prototype.f;Z.prototype.l=function(){return this.get("position")};Z.prototype.getPosition=Z.prototype.l;Z.prototype.g=function(){return this.get("positioning")};Z.prototype.getPositioning=Z.prototype.g;
-k=Z.prototype;k.Uf=function(){Hf(this.b);var a=this.i();null!=a&&Gf(this.b,a)};k.fg=function(){null!==this.d&&(Kf(this.b),Yc(this.d),this.d=null);var a=this.e();null!=a&&(this.d=y(a,"postrender",this.Wg,!1,this),lw(this),a=this.u?a.r:a.Oa,this.r?Jf(a,this.b,0):Gf(a,this.b))};k.Wg=function(){lw(this)};k.hg=function(){lw(this)};k.jg=function(){lw(this)};k.kg=function(){lw(this)};k.Nd=function(a){this.set("element",a)};Z.prototype.setElement=Z.prototype.Nd;
-Z.prototype.setMap=function(a){this.set("map",a)};Z.prototype.setMap=Z.prototype.setMap;Z.prototype.k=function(a){this.set("offset",a)};Z.prototype.setOffset=Z.prototype.k;Z.prototype.o=function(a){this.set("position",a)};Z.prototype.setPosition=Z.prototype.o;Z.prototype.q=function(a){this.set("positioning",a)};Z.prototype.setPositioning=Z.prototype.q;
-function lw(a){var b=a.e(),c=a.l();if(l(b)&&null!==b.d&&l(c)){var c=b.f(c),d=b.e(),b=a.b.style,e=a.f(),f=a.g(),g=e[0],e=e[1];if("bottom-right"==f||"center-right"==f||"top-right"==f)""!==a.a.ad&&(a.a.ad=b.left=""),g=Math.round(d[0]-c[0]-g)+"px",a.a.jd!=g&&(a.a.jd=b.right=g);else{""!==a.a.jd&&(a.a.jd=b.right="");if("bottom-center"==f||"center-center"==f||"top-center"==f)g-=ag(a.b).width/2;g=Math.round(c[0]+g)+"px";a.a.ad!=g&&(a.a.ad=b.left=g)}if("bottom-left"==f||"bottom-center"==f||"bottom-right"==
-f)""!==a.a.ld&&(a.a.ld=b.top=""),c=Math.round(d[1]-c[1]-e)+"px",a.a.Dc!=c&&(a.a.Dc=b.bottom=c);else{""!==a.a.Dc&&(a.a.Dc=b.bottom="");if("center-left"==f||"center-center"==f||"center-right"==f)e-=ag(a.b).height/2;c=Math.round(c[1]+e)+"px";a.a.ld!=c&&(a.a.ld=b.top=c)}a.a.visible||(dg(a.b,!0),a.a.visible=!0)}else a.a.visible&&(dg(a.b,!1),a.a.visible=!1)};function mw(a,b){var c=b||{},d=c.document||document,e=Ff("SCRIPT"),f={Ge:e,Eb:void 0},g=new Rr(nw,f),h=null,m=null!=c.timeout?c.timeout:5E3;0<m&&(h=window.setTimeout(function(){ow(e,!0);var b=new pw(qw,"Timeout reached for loading script "+a);Tr(g);Ur(g,!1,b)},m),f.Eb=h);e.onload=e.onreadystatechange=function(){e.readyState&&"loaded"!=e.readyState&&"complete"!=e.readyState||(ow(e,c.gf||!1,h),Tr(g),Ur(g,!0,null))};e.onerror=function(){ow(e,!0,h);var b=new pw(rw,"Error while loading script "+a);Tr(g);
-Ur(g,!1,b)};zf(e,{type:"text/javascript",charset:"UTF-8",src:a});sw(d).appendChild(e);return g}function sw(a){var b=a.getElementsByTagName("HEAD");return b&&0!=b.length?b[0]:a.documentElement}function nw(){if(this&&this.Ge){var a=this.Ge;a&&"SCRIPT"==a.tagName&&ow(a,!0,this.Eb)}}function ow(a,b,c){null!=c&&ba.clearTimeout(c);a.onload=ca;a.onerror=ca;a.onreadystatechange=ca;b&&window.setTimeout(function(){Kf(a)},0)}var rw=0,qw=1;
-function pw(a,b){var c="Jsloader error (code #"+a+")";b&&(c+=": "+b);va.call(this,c);this.code=a}t(pw,va);function tw(a,b){this.c=new xn(a);this.a=b?b:"callback";this.Eb=5E3}var uw=0;function vw(a,b,c){var d=b||null;b="_"+(uw++).toString(36)+ua().toString(36);ba._callbacks_||(ba._callbacks_={});var e=a.c.clone();if(d)for(var f in d)if(!d.hasOwnProperty||d.hasOwnProperty(f)){var g=e,h=f,m=d[f];ha(m)||(m=[String(m)]);Qn(g.a,h,m)}c&&(ba._callbacks_[b]=ww(b,c),c=a.a,f="_callbacks_."+b,ha(f)||(f=[String(f)]),Qn(e.a,c,f));a=mw(e.toString(),{timeout:a.Eb,gf:!0});Xr(a,null,xw(b),void 0)}
-tw.prototype.cancel=function(a){a&&(a.hf&&a.hf.cancel(),a.T&&yw(a.T,!1))};function xw(a){return function(){yw(a,!1)}}function ww(a,b){return function(c){yw(a,!0);b.apply(void 0,arguments)}}function yw(a,b){ba._callbacks_[a]&&(b?delete ba._callbacks_[a]:ba._callbacks_[a]=ca)};function zw(a){var b=/\{z\}/g,c=/\{x\}/g,d=/\{y\}/g,e=/\{-y\}/g;return function(f){return null===f?void 0:a.replace(b,f[0].toString()).replace(c,f[1].toString()).replace(d,f[2].toString()).replace(e,function(){return((1<<f[0])-f[2]-1).toString()})}}function Aw(a){return Bw(Va(a,zw))}function Bw(a){return 1===a.length?a[0]:function(b,c,d){return null===b?void 0:a[Kb((b[1]<<b[0])+b[2],a.length)](b,c,d)}}function Cw(){}
-function Dw(a,b){var c=[0,0,0];return function(d,e,f){return null===d?void 0:b(a(d,f,c),e,f)}}function Ew(a){var b=[],c=/\{(\d)-(\d)\}/.exec(a)||/\{([a-z])-([a-z])\}/.exec(a);if(c){var d=c[2].charCodeAt(0),e;for(e=c[1].charCodeAt(0);e<=d;++e)b.push(a.replace(c[0],String.fromCharCode(e)))}else b.push(a);return b};function Fw(a){pv.call(this);this.e=l(a)?a:2048}t(Fw,pv);function Gw(a){return a.fb()>a.e}function Hw(a,b){for(var c,d;Gw(a)&&!(c=a.a.Gb,d=c.a[0].toString(),d in b&&b[d].contains(c.a));)a.pop().Nb()};function Iw(a){dt.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,opaque:a.opaque,projection:a.projection,state:l(a.state)?a.state:void 0,tileGrid:a.tileGrid,tilePixelRatio:a.tilePixelRatio});this.tileUrlFunction=l(a.tileUrlFunction)?a.tileUrlFunction:Cw;this.crossOrigin=l(a.crossOrigin)?a.crossOrigin:null;this.b=new Fw;this.tileLoadFunction=l(a.tileLoadFunction)?a.tileLoadFunction:Jw;this.tileClass=l(a.tileClass)?a.tileClass:gr}t(Iw,dt);function Jw(a,b){a.b().src=b}k=Iw.prototype;
-k.Ed=function(){return Gw(this.b)};k.se=function(a){Hw(this.b,a)};k.Rb=function(a,b,c,d,e){var f=this.hb(a,b,c);if(nv(this.b,f))return this.b.get(f);a=[a,b,c];d=this.tileUrlFunction(a,d,e);d=new this.tileClass(a,l(d)?0:4,l(d)?d:"",this.crossOrigin,this.tileLoadFunction);this.b.set(f,d);return d};k.Ra=function(){return this.tileLoadFunction};k.Sa=function(){return this.tileUrlFunction};k.Ua=function(a){this.b.clear();this.tileLoadFunction=a;this.n()};
-k.ha=function(a){this.b.clear();this.tileUrlFunction=a;this.n()};k.Oe=function(a,b,c){a=this.hb(a,b,c);nv(this.b,a)&&this.b.get(a)};function Kw(a){var b=l(a.extent)?a.extent:Mt,c=bt(b,a.maxZoom,a.tileSize);Vs.call(this,{minZoom:a.minZoom,origin:ke(b,"top-left"),resolutions:c,tileSize:a.tileSize})}t(Kw,Vs);
-Kw.prototype.c=function(a){a=l(a)?a:{};var b=this.minZoom,c=this.maxZoom,d=l(a.wrapX)?a.wrapX:!0,e=null;if(l(a.extent)){var e=Array(c+1),f;for(f=0;f<=c;++f)e[f]=f<b?null:Ys(this,a.extent,f)}return function(a,f,m){f=a[0];if(f<b||c<f)return null;var n=Math.pow(2,f),p=a[1];if(d)p=Kb(p,n);else if(0>p||n<=p)return null;a=a[2];return a<-n||-1<a||null!==e&&!nf(e[f],p,a)?null:gf(f,p,-a-1,m)}};Kw.prototype.Sc=function(a,b){return a[0]<this.maxZoom?mf(2*a[1],2*(a[1]+1),2*a[2],2*(a[2]+1),b):null};
-Kw.prototype.Lc=function(a,b,c,d){d=mf(0,a[1],0,a[2],d);for(a=a[0]-1;a>=this.minZoom;--a)if(d.a=d.d>>=1,d.b=d.c>>=1,b.call(c,a,d))return!0;return!1};function Lw(a){Iw.call(this,{crossOrigin:"anonymous",opaque:!0,projection:Be("EPSG:3857"),state:"loading",tileLoadFunction:a.tileLoadFunction});this.a=l(a.culture)?a.culture:"en-us";var b=new xn((Hb?"https:":"http:")+"//dev.virtualearth.net/REST/v1/Imagery/Metadata/"+a.imagerySet);vw(new tw(b,"jsonp"),{include:"ImageryProviders",key:a.key},sa(this.d,this))}t(Lw,Iw);var Mw=new qf({html:'<a class="ol-attribution-bing-tos" target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'});
-Lw.prototype.d=function(a){if(200!=a.statusCode||"OK"!=a.statusDescription||"ValidCredentials"!=a.authenticationResultCode||1!=a.resourceSets.length||1!=a.resourceSets[0].resources.length)Ts(this,"error");else{var b=a.brandLogoUri,c=a.resourceSets[0].resources[0],d=new Kw({extent:ct(this.i),minZoom:c.zoomMin,maxZoom:c.zoomMax,tileSize:c.imageWidth});this.tileGrid=d;var e=this.a;this.tileUrlFunction=Dw(d.c(),Bw(Va(c.imageUrlSubdomains,function(a){var b=c.imageUrl.replace("{subdomain}",a).replace("{culture}",
-e);return function(a){return null===a?void 0:b.replace("{quadkey}",jf(a))}})));if(c.imageryProviders){var f=Ae(Be("EPSG:4326"),this.i);a=Va(c.imageryProviders,function(a){var b=a.attribution,c={};Ta(a.coverageAreas,function(a){var b=a.zoomMin,e=a.zoomMax;a=a.bbox;a=te([a[1],a[0],a[3],a[2]],f);var g,h;for(g=b;g<=e;++g)h=g.toString(),b=Ys(d,a,g),h in c?c[h].push(b):c[h]=[b]});return new qf({html:b,tileRanges:c})});a.push(Mw);this.f=a}this.q=b;Ts(this,"ready")}};function Nw(a){Mv.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection});this.k=void 0;this.r=l(a.distance)?a.distance:20;this.a=[];this.p=a.source;this.p.t("change",Nw.prototype.u,this)}t(Nw,Mv);Nw.prototype.bd=function(a,b){b!==this.k&&(this.clear(),this.k=b,Ow(this),this.oa(this.a))};Nw.prototype.u=function(){this.clear();Ow(this);this.oa(this.a);this.n()};
-function Ow(a){if(l(a.k)){$a(a.a);for(var b=Sd(),c=a.r*a.k,d=a.p.qa(),e={},f=0,g=d.length;f<g;f++){var h=d[f];yc(e,na(h).toString())||(h=h.J().H(),$d(h,b),Vd(b,c,b),h=Iv(a.p.b,b),h=Ua(h,function(a){a=na(a).toString();return a in e?!1:e[a]=!0}),a.a.push(Pw(h)))}}}function Pw(a){for(var b=a.length,c=[0,0],d=0;d<b;d++){var e=a[d].J().H();vd(c,e)}b=1/b;c[0]*=b;c[1]*=b;c=new G(new yj(c));c.set("features",a);return c};function Qw(a,b,c){if(la(a))c&&(a=sa(a,c));else if(a&&"function"==typeof a.handleEvent)a=sa(a.handleEvent,a);else throw Error("Invalid listener argument");return 2147483647<b?-1:ba.setTimeout(a,b||0)};function Rw(){}Rw.prototype.a=null;function Sw(a){var b;(b=a.a)||(b={},Tw(a)&&(b[0]=!0,b[1]=!0),b=a.a=b);return b};var Uw;function Vw(){}t(Vw,Rw);function Ww(a){return(a=Tw(a))?new ActiveXObject(a):new XMLHttpRequest}function Tw(a){if(!a.c&&"undefined"==typeof XMLHttpRequest&&"undefined"!=typeof ActiveXObject){for(var b=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],c=0;c<b.length;c++){var d=b[c];try{return new ActiveXObject(d),a.c=d}catch(e){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return a.c}Uw=new Vw;function Xw(a){hd.call(this);this.u=new on;this.l=a||null;this.a=!1;this.i=this.M=null;this.e=this.D=this.o="";this.c=this.p=this.d=this.k=!1;this.g=0;this.b=null;this.f=Yw;this.q=this.Q=!1}t(Xw,hd);var Yw="",Zw=/^https?$/i,$w=["POST","PUT"];
-function ax(a,b){if(a.M)throw Error("[goog.net.XhrIo] Object is active with another request="+a.o+"; newUri="+b);a.o=b;a.e="";a.D="GET";a.k=!1;a.a=!0;a.M=a.l?Ww(a.l):Ww(Uw);a.i=a.l?Sw(a.l):Sw(Uw);a.M.onreadystatechange=sa(a.r,a);try{a.p=!0,a.M.open("GET",String(b),!0),a.p=!1}catch(c){bx(a,c);return}var d=a.u.clone(),e=Xa(d.N()),f=ba.FormData&&!1;!Za($w,"GET")||e||f||d.set("Content-Type","application/x-www-form-urlencoded;charset=utf-8");d.forEach(function(a,b){this.M.setRequestHeader(b,a)},a);a.f&&
-(a.M.responseType=a.f);"withCredentials"in a.M&&(a.M.withCredentials=a.Q);try{cx(a),0<a.g&&(a.q=dx(a.M),a.q?(a.M.timeout=a.g,a.M.ontimeout=sa(a.Eb,a)):a.b=Qw(a.Eb,a.g,a)),a.d=!0,a.M.send(""),a.d=!1}catch(g){bx(a,g)}}function dx(a){return ub&&Eb(9)&&ka(a.timeout)&&l(a.ontimeout)}function Ya(a){return"content-type"==a.toLowerCase()}
-Xw.prototype.Eb=function(){"undefined"!=typeof aa&&this.M&&(this.e="Timed out after "+this.g+"ms, aborting",this.dispatchEvent("timeout"),this.M&&this.a&&(this.a=!1,this.c=!0,this.M.abort(),this.c=!1,this.dispatchEvent("complete"),this.dispatchEvent("abort"),ex(this)))};function bx(a,b){a.a=!1;a.M&&(a.c=!0,a.M.abort(),a.c=!1);a.e=b;fx(a);ex(a)}function fx(a){a.k||(a.k=!0,a.dispatchEvent("complete"),a.dispatchEvent("error"))}
-Xw.prototype.I=function(){this.M&&(this.a&&(this.a=!1,this.c=!0,this.M.abort(),this.c=!1),ex(this,!0));Xw.K.I.call(this)};Xw.prototype.r=function(){this.qb||(this.p||this.d||this.c?gx(this):this.F())};Xw.prototype.F=function(){gx(this)};
-function gx(a){if(a.a&&"undefined"!=typeof aa&&(!a.i[1]||4!=hx(a)||2!=ix(a)))if(a.d&&4==hx(a))Qw(a.r,0,a);else if(a.dispatchEvent("readystatechange"),4==hx(a)){a.a=!1;try{if(jx(a))a.dispatchEvent("complete"),a.dispatchEvent("success");else{var b;try{b=2<hx(a)?a.M.statusText:""}catch(c){b=""}a.e=b+" ["+ix(a)+"]";fx(a)}}finally{ex(a)}}}function ex(a,b){if(a.M){cx(a);var c=a.M,d=a.i[0]?ca:null;a.M=null;a.i=null;b||a.dispatchEvent("ready");try{c.onreadystatechange=d}catch(e){}}}
-function cx(a){a.M&&a.q&&(a.M.ontimeout=null);ka(a.b)&&(ba.clearTimeout(a.b),a.b=null)}function jx(a){var b=ix(a),c;a:switch(b){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:c=!0;break a;default:c=!1}if(!c){if(b=0===b)a=sn(String(a.o))[1]||null,!a&&self.location&&(a=self.location.protocol,a=a.substr(0,a.length-1)),b=!Zw.test(a?a.toLowerCase():"");c=b}return c}function hx(a){return a.M?a.M.readyState:0}function ix(a){try{return 2<hx(a)?a.M.status:-1}catch(b){return-1}}
-function kx(a){try{return a.M?a.M.responseText:""}catch(b){return""}}function lx(a){try{if(!a.M)return null;if("response"in a.M)return a.M.response;switch(a.f){case Yw:case "text":return a.M.responseText;case "arraybuffer":if("mozResponseArrayBuffer"in a.M)return a.M.mozResponseArrayBuffer}return null}catch(b){return null}};function mx(a){Mv.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection});this.format=a.format}t(mx,Mv);
-function nx(a,b,c,d){var e=new Xw;e.f="binary"==a.format.G()&&tg?"arraybuffer":"text";y(e,"complete",function(a){a=a.target;if(jx(a)){var b=this.format.G(),e;if("binary"==b&&tg)e=lx(a);else if("json"==b)e=kx(a);else if("text"==b)e=kx(a);else if("xml"==b){if(!ub)try{e=a.M?a.M.responseXML:null}catch(m){e=null}null!=e||(e=Sk(kx(a)))}null!=e?c.call(d,this.a(e)):Ts(this,"error")}else Ts(this,"error");ec(a)},!1,a);ax(e,b)}mx.prototype.a=function(a){return this.format.la(a,{featureProjection:this.i})};function $(a){mx.call(this,{attributions:a.attributions,format:a.format,logo:a.logo,projection:a.projection});l(a.arrayBuffer)&&this.Pa(this.a(a.arrayBuffer));l(a.doc)&&this.Pa(this.a(a.doc));l(a.node)&&this.Pa(this.a(a.node));l(a.object)&&this.Pa(this.a(a.object));l(a.text)&&this.Pa(this.a(a.text));if(l(a.url)||l(a.urls))if(Ts(this,"loading"),l(a.url)&&nx(this,a.url,this.k,this),l(a.urls)){a=a.urls;var b,c;b=0;for(c=a.length;b<c;++b)nx(this,a[b],this.k,this)}}t($,mx);
-$.prototype.k=function(a){this.Pa(a);Ts(this,"ready")};function ox(a){a=l(a)?a:{};$.call(this,{attributions:a.attributions,extent:a.extent,format:new nk({defaultDataProjection:a.defaultProjection}),logo:a.logo,object:a.object,projection:a.projection,text:a.text,url:a.url,urls:a.urls})}t(ox,$);function px(a){a=l(a)?a:{};$.call(this,{attributions:a.attributions,doc:a.doc,extent:a.extent,format:new zm,logo:a.logo,node:a.node,projection:a.projection,text:a.text,url:a.url,urls:a.urls})}t(px,$);function qx(a){a=l(a)?a:{};$.call(this,{format:new kn({altitudeMode:a.altitudeMode}),projection:a.projection,text:a.text,url:a.url,urls:a.urls})}t(qx,$);function rx(a){Ss.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state});this.k=l(a.resolutions)?a.resolutions:null}t(rx,Ss);function sx(a,b){if(null!==a.k){var c=Qb(a.k,b,0);b=a.k[c]}return b};function tx(a){rx.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions,state:l(a.state)?a.state:void 0});this.o=a.canvasFunction;this.g=null;this.p=0;this.r=l(a.ratio)?a.ratio:1.5}t(tx,rx);tx.prototype.Vb=function(a,b,c,d){b=sx(this,b);var e=this.g;if(null!==e&&this.p==this.c&&e.b==b&&e.f==c&&Yd(e.s(),a))return e;a=a.slice();re(a,this.r);d=this.o(a,b,c,[fe(a)/b*c,ge(a)/b*c],d);null===d||(e=new er(a,b,c,this.f,d));this.g=e;this.p=this.c;return e};function ux(a){rx.call(this,{projection:a.projection,resolutions:a.resolutions});this.r=l(a.crossOrigin)?a.crossOrigin:null;this.b=l(a.displayDpi)?a.displayDpi:96;this.a=l(a.params)?a.params:{};var b;l(a.url)?b=hr(a.url,this.a,sa(this.F,this)):b=ir;this.p=b;this.u=l(a.hidpi)?a.hidpi:!0;this.o=l(a.metersPerUnit)?a.metersPerUnit:1;this.e=l(a.ratio)?a.ratio:1;this.X=l(a.useOverlay)?a.useOverlay:!1;this.d=null;this.g=0}t(ux,rx);ux.prototype.D=function(){return this.a};
-ux.prototype.Vb=function(a,b,c,d){b=sx(this,b);c=this.u?c:1;var e=this.d;if(null!==e&&this.g==this.c&&e.b==b&&e.f==c&&Yd(e.s(),a))return e;1!=this.e&&(a=a.slice(),re(a,this.e));d=this.p(a,[fe(a)/b*c,ge(a)/b*c],d);l(d)?e=new cr(a,b,c,this.f,d,this.r):e=null;this.d=e;this.g=this.c;return e};ux.prototype.Q=function(a){Hc(this.a,a);this.n()};
-ux.prototype.F=function(a,b,c,d){var e;e=this.o;var f=fe(c),g=ge(c),h=d[0],m=d[1],n=.0254/this.b;e=m*f>h*g?f*e/(h*n):g*e/(m*n);c=je(c);d={OPERATION:this.X?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol.source.ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:this.b,SETDISPLAYWIDTH:Math.round(d[0]),SETDISPLAYHEIGHT:Math.round(d[1]),SETVIEWSCALE:e,SETVIEWCENTERX:c[0],SETVIEWCENTERY:c[1]};Hc(d,b);return un(wn([a],d))};function vx(a){var b=l(a.attributions)?a.attributions:null,c=l(a.crossOrigin)?a.crossOrigin:null,d=a.imageExtent,e=(d[3]-d[1])/a.imageSize[1],f=a.url,g=Be(a.projection);rx.call(this,{attributions:b,logo:a.logo,projection:g,resolutions:[e]});this.a=new cr(d,e,1,b,f,c)}t(vx,rx);vx.prototype.Vb=function(a){return pe(a,this.a.s())?this.a:null};function wx(a){this.b=a.source;this.u=null!=a.style?Mi(a.style):Ni;this.D=Id();this.a=lg();this.d=[0,0];this.e=null;tx.call(this,{attributions:a.attributions,canvasFunction:sa(this.ef,this),logo:a.logo,projection:a.projection,ratio:a.ratio,resolutions:a.resolutions,state:this.b.l});y(this.b,"change",this.oh,void 0,this)}t(wx,tx);k=wx.prototype;
-k.ef=function(a,b,c,d){var e=new ou(.5*b/c,a,b),f=!1;this.b.Kc(a,b,function(a){var d;if(!(d=f))if(d=this.u(a,b),null!=d){var m,n,p=!1;m=0;for(n=d.length;m<n;++m)p=Xj(e,a,d[m],Wj(b,c),a,this.nh,this)||p;d=p}else d=!1;f=d},this);ru(e);if(f)return null;this.d[0]!=d[0]||this.d[1]!=d[1]?(this.a.canvas.width=d[0],this.a.canvas.height=d[1],this.d[0]=d[0],this.d[1]=d[1]):this.a.clearRect(0,0,d[0],d[1]);d=xx(this,je(a),b,c,d);pu(e,this.a,a,c,d,0,{});this.e=e;return this.a.canvas};
-k.ed=function(a,b,c,d,e,f){if(null!==this.e){var g={};return wt(this.e,a,b,0,d,e,function(a,b){var c=na(b).toString();if(!(c in g))return g[c]=!0,f(b)})}};k.mh=function(){return this.b};function xx(a,b,c,d,e){return pt(a.D,e[0]/2,e[1]/2,d/c,-d/c,0,-b[0],-b[1])}k.nh=function(){this.n()};k.oh=function(){Ts(this,this.b.l)};function yx(a){a=l(a)?a:{};rx.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions});this.u=l(a.crossOrigin)?a.crossOrigin:null;this.b=a.url;this.a=a.params;this.e=!0;zx(this);this.r=a.serverType;this.D=l(a.hidpi)?a.hidpi:!0;this.d=null;this.g=[0,0];this.o=0;this.p=l(a.ratio)?a.ratio:1.5}t(yx,rx);var Ax=[101,101];k=yx.prototype;
-k.ph=function(a,b,c,d){if(l(this.b)){var e=ne(a,b,0,Ax),f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:v(this.a,"LAYERS")};Hc(f,this.a,d);d=Math.floor((e[3]-a[1])/b);f[this.e?"I":"X"]=Math.floor((a[0]-e[0])/b);f[this.e?"J":"Y"]=d;return Bx(this,e,Ax,1,Be(c),f)}};k.qh=function(){return this.a};
-k.Vb=function(a,b,c,d){if(!l(this.b))return null;b=sx(this,b);1==c||this.D&&l(this.r)||(c=1);var e=this.d;if(null!==e&&this.o==this.c&&e.b==b&&e.f==c&&Yd(e.s(),a))return e;e={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Hc(e,this.a);a=a.slice();var f=(a[0]+a[2])/2,g=(a[1]+a[3])/2;if(1!=this.p){var h=this.p*fe(a)/2,m=this.p*ge(a)/2;a[0]=f-h;a[1]=g-m;a[2]=f+h;a[3]=g+m}var h=b/c,m=Math.ceil(fe(a)/h),n=Math.ceil(ge(a)/h);a[0]=f-h*m/2;a[2]=f+h*m/2;a[1]=g-h*n/2;a[3]=
-g+h*n/2;this.g[0]=m;this.g[1]=n;d=Bx(this,a,this.g,c,d,e);this.d=new cr(a,b,c,this.f,d,this.u);this.o=this.c;return this.d};
-function Bx(a,b,c,d,e,f){f[a.e?"CRS":"SRS"]=e.a;"STYLES"in a.a||(f.STYLES=new String(""));if(1!=d)switch(a.r){case "geoserver":f.FORMAT_OPTIONS="dpi:"+(90*d+.5|0);break;case "mapserver":f.MAP_RESOLUTION=90*d;break;case "carmentaserver":case "qgis":f.DPI=90*d}f.WIDTH=c[0];f.HEIGHT=c[1];c=e.b;var g;a.e&&"ne"==c.substr(0,2)?g=[b[1],b[0],b[3],b[2]]:g=b;f.BBOX=g.join(",");return un(wn([a.b],f))}k.rh=function(){return this.b};k.sh=function(a){a!=this.b&&(this.b=a,this.d=null,this.n())};
-k.th=function(a){Hc(this.a,a);zx(this);this.d=null;this.n()};function zx(a){a.e=0<=Na(v(a.a,"VERSION","1.3.0"),"1.3")};function Cx(a){a=l(a)?a:{};$.call(this,{attributions:a.attributions,doc:a.doc,format:new Vn({extractStyles:a.extractStyles,defaultStyle:a.defaultStyle}),logo:a.logo,node:a.node,projection:a.projection,text:a.text,url:a.url,urls:a.urls})}t(Cx,$);function Dx(a){var b=l(a.projection)?a.projection:"EPSG:3857",c=new Kw({extent:ct(b),maxZoom:a.maxZoom,tileSize:a.tileSize});Iw.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,projection:b,tileGrid:c,tileLoadFunction:a.tileLoadFunction,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:Cw});this.d=c.c({wrapX:a.wrapX});l(a.tileUrlFunction)?this.ha(a.tileUrlFunction):l(a.urls)?this.ha(Aw(a.urls)):l(a.url)&&this.a(a.url)}t(Dx,Iw);
-Dx.prototype.ha=function(a){Dx.K.ha.call(this,Dw(this.d,a))};Dx.prototype.a=function(a){this.ha(Aw(Ew(a)))};function Ex(a){a=l(a)?a:{};var b=Hb?"https:":"http:";Dx.call(this,{attributions:l(a.attributions)?a.attributions:Fx,crossOrigin:l(a.crossOrigin)?a.crossOrigin:"anonymous",opaque:!0,maxZoom:l(a.maxZoom)?a.maxZoom:19,tileLoadFunction:a.tileLoadFunction,url:l(a.url)?a.url:b+"//{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png"})}t(Ex,Dx);
-var Gx=new qf({html:'Data &copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="http://www.openstreetmap.org/copyright">ODbL</a>'}),Hx=new qf({html:'Tiles &copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA</a>'}),Fx=[Hx,Gx];function Ix(a){a=l(a)?a:{};var b=Jx[a.layer];Dx.call(this,{attributions:b.attributions,crossOrigin:"anonymous",logo:"//developer.mapquest.com/content/osm/mq_logo.png",maxZoom:b.maxZoom,opaque:!0,tileLoadFunction:a.tileLoadFunction,url:(Hb?"https:":"http:")+"//otile{1-4}-s.mqcdn.com/tiles/1.0.0/"+a.layer+"/{z}/{x}/{y}.jpg"})}t(Ix,Dx);
-var Kx=new qf({html:'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a>'}),Jx={osm:{maxZoom:28,attributions:[Kx,Gx]},sat:{maxZoom:18,attributions:[Kx,new qf({html:"Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency"})]},hyb:{maxZoom:18,attributions:[Kx,Gx]}};function Lx(a){a=l(a)?a:{};$.call(this,{attributions:a.attributions,doc:a.doc,format:new xp,logo:a.logo,node:a.node,projection:a.projection,reprojectTo:a.reprojectTo,text:a.text,url:a.url,urls:a.urls})}t(Lx,$);function Mx(a){mx.call(this,{attributions:a.attributions,format:a.format,logo:a.logo,projection:a.projection});this.p=new Cv;this.r=a.loader;this.u=l(a.strategy)?a.strategy:kw;this.k={}}t(Mx,mx);Mx.prototype.Pa=function(a){var b=[],c,d;c=0;for(d=a.length;c<d;++c){var e=a[c],f=e.T;l(f)?f in this.k||(b.push(e),this.k[f]=!0):b.push(e)}Mx.K.Pa.call(this,b)};Mx.prototype.clear=function(){Cc(this.k);this.p.clear();Mx.K.clear.call(this)};
-Mx.prototype.bd=function(a,b,c){var d=this.p;a=this.u(a,b);var e,f;e=0;for(f=a.length;e<f;++e){var g=a[e];Fv(d,g,function(a){return Yd(a.extent,g)})||(this.r.call(this,g,b,c),Jv(d,g,{extent:g.slice()}))}};var Nx={terrain:{Ga:"jpg",opaque:!0},"terrain-background":{Ga:"jpg",opaque:!0},"terrain-labels":{Ga:"png",opaque:!1},"terrain-lines":{Ga:"png",opaque:!1},"toner-background":{Ga:"png",opaque:!0},toner:{Ga:"png",opaque:!0},"toner-hybrid":{Ga:"png",opaque:!1},"toner-labels":{Ga:"png",opaque:!1},"toner-lines":{Ga:"png",opaque:!1},"toner-lite":{Ga:"png",opaque:!0},watercolor:{Ga:"jpg",opaque:!0}},Ox={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:3,maxZoom:16}};
-function Px(a){var b=a.layer.indexOf("-"),c=Nx[a.layer],d=Hb?"https:":"http:";Dx.call(this,{attributions:Qx,crossOrigin:"anonymous",maxZoom:Ox[-1==b?a.layer:a.layer.slice(0,b)].maxZoom,opaque:c.opaque,tileLoadFunction:a.tileLoadFunction,url:l(a.url)?a.url:d+"//{a-d}.tile.stamen.com/"+a.layer+"/{z}/{x}/{y}."+c.Ga})}t(Px,Dx);var Qx=[new qf({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>.'}),Gx];function Rx(a,b){fr.call(this,a,2);this.e=b.ia(a[0]);this.c={}}t(Rx,fr);Rx.prototype.b=function(a){a=l(a)?na(a):-1;if(a in this.c)return this.c[a];var b=this.e,c=lg(b,b);c.strokeStyle="black";c.strokeRect(.5,.5,b+.5,b+.5);c.fillStyle="black";c.textAlign="center";c.textBaseline="middle";c.font="24px sans-serif";c.fillText(kf(this.a),b/2,b/2);return this.c[a]=c.canvas};function Sx(a){dt.call(this,{opaque:!1,projection:a.projection,tileGrid:a.tileGrid});this.a=new Fw}t(Sx,dt);Sx.prototype.Ed=function(){return Gw(this.a)};
-Sx.prototype.se=function(a){Hw(this.a,a)};Sx.prototype.Rb=function(a,b,c){var d=this.hb(a,b,c);if(nv(this.a,d))return this.a.get(d);a=new Rx([a,b,c],this.tileGrid);this.a.set(d,a);return a};function Tx(a){Iw.call(this,{crossOrigin:a.crossOrigin,projection:Be("EPSG:3857"),state:"loading",tileLoadFunction:a.tileLoadFunction});vw(new tw(a.url),void 0,sa(this.a,this))}t(Tx,Iw);
-Tx.prototype.a=function(a){var b=Be("EPSG:4326"),c=this.i,d;l(a.bounds)&&(d=te(a.bounds,Ae(b,c)));var e=a.minzoom||0,f=a.maxzoom||22;this.tileGrid=c=new Kw({extent:ct(c),maxZoom:f,minZoom:e});this.tileUrlFunction=Dw(c.c({extent:d}),Aw(a.tiles));if(l(a.attribution)){b=l(d)?d:b.s();d={};for(var g;e<=f;++e)g=e.toString(),d[g]=[Ys(c,b,e)];this.f=[new qf({html:a.attribution,tileRanges:d})]}Ts(this,"ready")};function Ux(a){mx.call(this,{attributions:a.attributions,format:a.format,logo:a.logo,projection:a.projection});this.r=a.tileGrid;this.k=Cw;this.u=this.r.c();this.p={};l(a.tileUrlFunction)?(this.k=a.tileUrlFunction,this.n()):l(a.urls)?(this.k=Aw(a.urls),this.n()):l(a.url)&&(this.k=Aw(Ew(a.url)),this.n())}t(Ux,mx);Ux.prototype.clear=function(){Cc(this.p)};
-Ux.prototype.Kc=function(a,b,c,d){var e=this.r,f=this.p;b=Qb(e.a,b,0);a=Ys(e,a,b);for(var g,e=a.a;e<=a.d;++e)for(g=a.b;g<=a.c;++g){var h=f[b+"/"+e+"/"+g];if(l(h)){var m,n;m=0;for(n=h.length;m<n;++m){var p=c.call(d,h[m]);if(p)return p}}}};Ux.prototype.qa=function(){var a=this.p,b=[],c;for(c in a)db(b,a[c]);return b};
-Ux.prototype.bd=function(a,b,c){var d=this.u,e=this.r,f=this.k,g=this.p;b=Qb(e.a,b,0);a=Ys(e,a,b);var e=[b,0,0],h,m;for(h=a.a;h<=a.d;++h)for(m=a.b;m<=a.c;++m){var n=b+"/"+h+"/"+m;if(!(n in g)){e[0]=b;e[1]=h;e[2]=m;d(e,c,e);var p=f(e,1,c);l(p)&&(g[n]=[],nx(this,p,ta(function(a,b){g[a]=b;Ts(this,"ready")},n),this))}}};function Vx(a){a=l(a)?a:{};var b=l(a.params)?a.params:{};Iw.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,opaque:!v(b,"TRANSPARENT",!0),projection:a.projection,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction,tileUrlFunction:sa(this.xh,this)});var c=a.urls;!l(c)&&l(a.url)&&(c=Ew(a.url));this.e=null!=c?c:[];this.g=l(a.gutter)?a.gutter:0;this.a=b;this.d=!0;this.k=a.serverType;this.o=l(a.hidpi)?a.hidpi:!0;this.p="";Wx(this);this.r=Sd();Xx(this)}t(Vx,Iw);k=Vx.prototype;
-k.uh=function(a,b,c,d){c=Be(c);var e=this.tileGrid;null===e&&(e=et(this,c));b=$s(e,a[0],a[1],b,!1,void 0);if(!(e.a.length<=b[0])){var f=e.fa(b[0]),g=Xs(e,b,this.r),e=e.ia(b[0]),h=this.g;0!==h&&(e+=2*h,g=Vd(g,f*h,g));h={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:v(this.a,"LAYERS")};Hc(h,this.a,d);d=Math.floor((g[3]-a[1])/f);h[this.d?"I":"X"]=Math.floor((a[0]-g[0])/f);h[this.d?"J":"Y"]=d;return Yx(this,b,e,g,1,c,h)}};k.Mc=function(){return this.g};
-k.hb=function(a,b,c){return this.p+Vx.K.hb.call(this,a,b,c)};k.vh=function(){return this.a};
-function Yx(a,b,c,d,e,f,g){var h=a.e;if(0!=h.length){g.WIDTH=c;g.HEIGHT=c;g[a.d?"CRS":"SRS"]=f.a;"STYLES"in a.a||(g.STYLES=new String(""));if(1!=e)switch(a.k){case "geoserver":g.FORMAT_OPTIONS="dpi:"+(90*e+.5|0);break;case "mapserver":g.MAP_RESOLUTION=90*e;break;case "carmentaserver":case "qgis":g.DPI=90*e}c=f.b;a.d&&"ne"==c.substr(0,2)&&(a=d[0],d[0]=d[1],d[1]=a,a=d[2],d[2]=d[3],d[3]=a);g.BBOX=d.join(",");return un(wn([1==h.length?h[0]:h[Kb((b[1]<<b[0])+b[2],h.length)]],g))}}
-k.pc=function(a,b,c){a=Vx.K.pc.call(this,a,b,c);return 1!=b&&this.o&&l(this.k)?a*b+.5|0:a};k.Nf=function(){return this.e};function Wx(a){var b=0,c=[],d,e;d=0;for(e=a.e.length;d<e;++d)c[b++]=a.e[d];for(var f in a.a)c[b++]=f+"-"+a.a[f];a.p=c.join("#")}k.wh=function(a){a=l(a)?Ew(a):null;this.re(a)};k.re=function(a){this.e=null!=a?a:[];Wx(this);this.n()};
-k.xh=function(a,b,c){var d=this.tileGrid;null===d&&(d=et(this,c));if(!(d.a.length<=a[0])){1==b||this.o&&l(this.k)||(b=1);var e=d.fa(a[0]),f=Xs(d,a,this.r),d=d.ia(a[0]),g=this.g;0!==g&&(d+=2*g,f=Vd(f,e*g,f));1!=b&&(d=d*b+.5|0);e={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Hc(e,this.a);return Yx(this,a,d,f,b,c,e)}};k.yh=function(a){Hc(this.a,a);Wx(this);Xx(this);this.n()};function Xx(a){a.d=0<=Na(v(a.a,"VERSION","1.3.0"),"1.3")};function Zx(a){a=l(a)?a:{};$.call(this,{attributions:a.attributions,extent:a.extent,format:new Mp({defaultDataProjection:a.defaultProjection}),logo:a.logo,object:a.object,projection:a.projection,text:a.text,url:a.url})}t(Zx,$);function $x(a){this.d=a.matrixIds;Vs.call(this,{origin:a.origin,origins:a.origins,resolutions:a.resolutions,tileSize:a.tileSize,tileSizes:a.tileSizes})}t($x,Vs);$x.prototype.i=function(){return this.d};function ay(a){function b(a){a="KVP"==e?un(wn([a],g)):a.replace(/\{(\w+?)\}/g,function(a,b){return b in g?g[b]:a});return function(b){if(null!==b){var c={TileMatrix:f.d[b[0]],TileCol:b[1],TileRow:b[2]};Hc(c,h);b=a;return b="KVP"==e?un(wn([b],c)):b.replace(/\{(\w+?)\}/g,function(a,b){return c[b]})}}}var c=l(a.version)?a.version:"1.0.0",d=l(a.format)?a.format:"image/jpeg";this.a=l(a.dimensions)?a.dimensions:{};this.d="";by(this);var e=l(a.requestEncoding)?a.requestEncoding:"KVP",f=a.tileGrid,g={Layer:a.layer,
-style:a.style,Style:a.style,TileMatrixSet:a.matrixSet};"KVP"==e&&Hc(g,{Service:"WMTS",Request:"GetTile",Version:c,Format:d});var h=this.a,c=Cw,d=a.urls;!l(d)&&l(a.url)&&(d=Ew(a.url));l(d)&&(c=Bw(Va(d,b)));var m=Sd(),n=[0,0,0],c=Dw(function(a,b,c){if(f.a.length<=a[0])return null;var d=a[1],e=-a[2]-1,g=Xs(f,a,m),h=b.s();null!==h&&b.f&&(b=Math.ceil(fe(h)/fe(g)),d=Kb(d,b),n[0]=a[0],n[1]=d,n[2]=a[2],g=Xs(f,n,m));return!pe(g,h)||pe(g,h)&&(g[0]==h[2]||g[2]==h[0]||g[1]==h[3]||g[3]==h[1])?null:gf(a[0],d,e,
-c)},c);Iw.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,tileGrid:f,tileLoadFunction:a.tileLoadFunction,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:c})}t(ay,Iw);ay.prototype.e=function(){return this.a};ay.prototype.hb=function(a,b,c){return this.d+ay.K.hb.call(this,a,b,c)};function by(a){var b=0,c=[],d;for(d in a.a)c[b++]=d+"-"+a.a[d];a.d=c.join("/")}ay.prototype.g=function(a){Hc(this.a,a);by(this);this.n()};function cy(a){var b=l(a)?a:b;Vs.call(this,{origin:[0,0],resolutions:b.resolutions})}t(cy,Vs);cy.prototype.c=function(a){a=l(a)?a:{};var b=this.minZoom,c=this.maxZoom,d=null;if(l(a.extent)){var d=Array(c+1),e;for(e=0;e<=c;++e)d[e]=e<b?null:Ys(this,a.extent,e)}return function(a,e,h){e=a[0];if(e<b||c<e)return null;var m=Math.pow(2,e),n=a[1];if(0>n||m<=n)return null;a=a[2];return a<-m||-1<a||null!==d&&!nf(d[e],n,-a-1)?null:gf(e,n,-a-1,h)}};function dy(a){a=l(a)?a:{};var b=a.size,c=b[0],d=b[1],e=[],f=256;switch(l(a.tierSizeCalculation)?a.tierSizeCalculation:"default"){case "default":for(;c>f||d>f;)e.push([Math.ceil(c/f),Math.ceil(d/f)]),f+=f;break;case "truncated":for(;c>f||d>f;)e.push([Math.ceil(c/f),Math.ceil(d/f)]),c>>=1,d>>=1}e.push([1,1]);e.reverse();for(var f=[1],g=[0],d=1,c=e.length;d<c;d++)f.push(1<<d),g.push(e[d-1][0]*e[d-1][1]+g[d-1]);f.reverse();var f=new cy({resolutions:f}),h=a.url,b=Dw(f.c({extent:[0,0,b[0],b[1]]}),function(a){if(null!==
-a){var b=a[0],c=a[1];a=a[2];return h+"TileGroup"+((c+a*e[b][0]+g[b])/256|0)+"/"+b+"-"+c+"-"+a+".jpg"}});Iw.call(this,{attributions:a.attributions,crossOrigin:a.crossOrigin,logo:a.logo,tileClass:ey,tileGrid:f,tileUrlFunction:b})}t(dy,Iw);function ey(a,b,c,d,e){gr.call(this,a,b,c,d,e);this.g={}}t(ey,gr);
-ey.prototype.b=function(a){var b=l(a)?na(a).toString():"";if(b in this.g)return this.g[b];a=ey.K.b.call(this,a);if(2==this.state){if(256==a.width&&256==a.height)return this.g[b]=a;var c=lg(256,256);c.drawImage(a,0,0);return this.g[b]=c.canvas}return a};s("ol.animation.bounce",function(a){var b=a.resolution,c=l(a.start)?a.start:ua(),d=l(a.duration)?a.duration:1E3,e=l(a.easing)?a.easing:cf;return function(a,g){if(g.time<c)return g.animate=!0,g.viewHints[0]+=1,!0;if(g.time<c+d){var h=e((g.time-c)/d),m=b-g.viewState.resolution;g.animate=!0;g.viewState.resolution+=h*m;g.viewHints[0]+=1;return!0}return!1}});s("ol.animation.pan",df);s("ol.animation.rotate",ef);s("ol.animation.zoom",ff);s("ol.Attribution",qf);qf.prototype.getHTML=qf.prototype.b;
-Cg.prototype.element=Cg.prototype.element;s("ol.Collection",A);A.prototype.clear=A.prototype.clear;A.prototype.extend=A.prototype.je;A.prototype.forEach=A.prototype.forEach;A.prototype.getArray=A.prototype.Kg;A.prototype.item=A.prototype.item;A.prototype.getLength=A.prototype.xb;A.prototype.insertAt=A.prototype.Xc;A.prototype.pop=A.prototype.pop;A.prototype.push=A.prototype.push;A.prototype.remove=A.prototype.remove;A.prototype.removeAt=A.prototype.gd;A.prototype.setAt=A.prototype.vi;
-s("ol.coordinate.add",vd);s("ol.coordinate.createStringXY",function(a){return function(b){return Dd(b,a)}});s("ol.coordinate.format",zd);s("ol.coordinate.rotate",Bd);s("ol.coordinate.toStringHDMS",function(a){return l(a)?xd(a[1],"NS")+" "+xd(a[0],"EW"):""});s("ol.coordinate.toStringXY",Dd);s("ol.DeviceOrientation",D);D.prototype.getAlpha=D.prototype.e;D.prototype.getBeta=D.prototype.f;D.prototype.getGamma=D.prototype.g;D.prototype.getHeading=D.prototype.i;D.prototype.getTracking=D.prototype.d;
-D.prototype.setTracking=D.prototype.b;s("ol.easing.easeIn",function(a){return Math.pow(a,3)});s("ol.easing.easeOut",$e);s("ol.easing.inAndOut",af);s("ol.easing.linear",bf);s("ol.easing.upAndDown",cf);s("ol.extent.boundingExtent",Rd);s("ol.extent.buffer",Vd);s("ol.extent.containsCoordinate",function(a,b){return a[0]<=b[0]&&b[0]<=a[2]&&a[1]<=b[1]&&b[1]<=a[3]});s("ol.extent.containsExtent",Yd);s("ol.extent.createEmpty",Sd);s("ol.extent.equals",ae);s("ol.extent.extend",be);
-s("ol.extent.getBottomLeft",he);s("ol.extent.getBottomRight",ie);s("ol.extent.getCenter",je);s("ol.extent.getHeight",ge);s("ol.extent.getSize",function(a){return[a[2]-a[0],a[3]-a[1]]});s("ol.extent.getTopLeft",le);s("ol.extent.getTopRight",me);s("ol.extent.getWidth",fe);s("ol.extent.intersects",pe);s("ol.extent.isEmpty",ee);s("ol.extent.applyTransform",te);s("ol.Feature",G);G.prototype.clone=G.prototype.clone;G.prototype.getGeometry=G.prototype.J;G.prototype.getId=G.prototype.tf;
-G.prototype.getGeometryName=G.prototype.sf;G.prototype.getStyle=G.prototype.Qg;G.prototype.getStyleFunction=G.prototype.Rg;G.prototype.setGeometry=G.prototype.Ba;G.prototype.setStyle=G.prototype.i;G.prototype.setId=G.prototype.b;G.prototype.setGeometryName=G.prototype.e;s("ol.FeatureOverlay",bk);bk.prototype.addFeature=bk.prototype.ke;bk.prototype.getFeatures=bk.prototype.Lg;bk.prototype.removeFeature=bk.prototype.cd;bk.prototype.setFeatures=bk.prototype.xc;bk.prototype.setMap=bk.prototype.setMap;
-bk.prototype.setStyle=bk.prototype.me;bk.prototype.getStyle=bk.prototype.Mg;bk.prototype.getStyleFunction=bk.prototype.Ng;s("ol.Geolocation",N);N.prototype.getAccuracy=N.prototype.Zd;N.prototype.getAccuracyGeometry=N.prototype.o;N.prototype.getAltitude=N.prototype.q;N.prototype.getAltitudeAccuracy=N.prototype.r;N.prototype.getHeading=N.prototype.D;N.prototype.getPosition=N.prototype.F;N.prototype.getProjection=N.prototype.g;N.prototype.getSpeed=N.prototype.u;N.prototype.getTracking=N.prototype.i;
-N.prototype.getTrackingOptions=N.prototype.f;N.prototype.setProjection=N.prototype.l;N.prototype.setTracking=N.prototype.b;N.prototype.setTrackingOptions=N.prototype.k;s("ol.Graticule",Xq);Xq.prototype.getMap=Xq.prototype.Ug;Xq.prototype.getMeridians=Xq.prototype.Cf;Xq.prototype.getParallels=Xq.prototype.Hf;Xq.prototype.setMap=Xq.prototype.setMap;s("ol.has.DEVICE_PIXEL_RATIO",sg);s("ol.has.CANVAS",vg);s("ol.has.DEVICE_ORIENTATION",wg);s("ol.has.GEOLOCATION",xg);s("ol.has.TOUCH",yg);
-s("ol.has.WEBGL",Bg);gr.prototype.getImage=gr.prototype.b;s("ol.Kinetic",ps);s("ol.loadingstrategy.all",function(){return[[-Infinity,-Infinity,Infinity,Infinity]]});s("ol.loadingstrategy.bbox",kw);s("ol.loadingstrategy.createTile",function(a){return function(b,c){var d=Qb(a.a,c,0),e=Ys(a,b,d),f=[],d=[d,0,0];for(d[1]=e.a;d[1]<=e.d;++d[1])for(d[2]=e.b;d[2]<=e.c;++d[2])f.push(Xs(a,d));return f}});s("ol.Map",W);W.prototype.addControl=W.prototype.We;W.prototype.addInteraction=W.prototype.Xe;
-W.prototype.addLayer=W.prototype.Ye;W.prototype.addOverlay=W.prototype.Ze;W.prototype.beforeRender=W.prototype.Ea;W.prototype.forEachFeatureAtPixel=W.prototype.Cd;W.prototype.getEventCoordinate=W.prototype.qf;W.prototype.getEventPixel=W.prototype.xd;W.prototype.getTarget=W.prototype.rc;W.prototype.getCoordinateFromPixel=W.prototype.ta;W.prototype.getControls=W.prototype.pf;W.prototype.getOverlays=W.prototype.Gf;W.prototype.getInteractions=W.prototype.uf;W.prototype.getLayerGroup=W.prototype.ib;
-W.prototype.getLayers=W.prototype.Ue;W.prototype.getPixelFromCoordinate=W.prototype.f;W.prototype.getSize=W.prototype.e;W.prototype.getView=W.prototype.a;W.prototype.getViewport=W.prototype.Of;W.prototype.renderSync=W.prototype.si;W.prototype.render=W.prototype.O;W.prototype.removeControl=W.prototype.mi;W.prototype.removeInteraction=W.prototype.oi;W.prototype.removeLayer=W.prototype.pi;W.prototype.removeOverlay=W.prototype.qi;W.prototype.setLayerGroup=W.prototype.Je;W.prototype.setSize=W.prototype.D;
-W.prototype.setTarget=W.prototype.sa;W.prototype.setView=W.prototype.Va;W.prototype.updateSize=W.prototype.F;ji.prototype.originalEvent=ji.prototype.originalEvent;ji.prototype.pixel=ji.prototype.pixel;ji.prototype.coordinate=ji.prototype.coordinate;ji.prototype.preventDefault=ji.prototype.preventDefault;ji.prototype.stopPropagation=ji.prototype.d;Rg.prototype.map=Rg.prototype.map;Rg.prototype.frameState=Rg.prototype.frameState;md.prototype.key=md.prototype.key;nd.prototype.transform=nd.prototype.d;
-s("ol.Object",pd);pd.prototype.bindTo=pd.prototype.Z;pd.prototype.get=pd.prototype.get;pd.prototype.getKeys=pd.prototype.N;pd.prototype.getProperties=pd.prototype.R;pd.prototype.set=pd.prototype.set;pd.prototype.setProperties=pd.prototype.L;pd.prototype.unbind=pd.prototype.Y;pd.prototype.unbindAll=pd.prototype.$;s("ol.Observable",ld);ld.prototype.dispatchChangeEvent=ld.prototype.n;ld.prototype.getRevision=ld.prototype.A;ld.prototype.on=ld.prototype.t;ld.prototype.once=ld.prototype.B;
-ld.prototype.un=ld.prototype.v;ld.prototype.unByKey=ld.prototype.C;s("ol.inherits",t);s("ol.Overlay",Z);Z.prototype.getElement=Z.prototype.i;Z.prototype.getMap=Z.prototype.e;Z.prototype.getOffset=Z.prototype.f;Z.prototype.getPosition=Z.prototype.l;Z.prototype.getPositioning=Z.prototype.g;Z.prototype.setElement=Z.prototype.Nd;Z.prototype.setMap=Z.prototype.setMap;Z.prototype.setOffset=Z.prototype.k;Z.prototype.setPosition=Z.prototype.o;Z.prototype.setPositioning=Z.prototype.q;
-fr.prototype.getTileCoord=fr.prototype.i;s("ol.View",z);z.prototype.constrainCenter=z.prototype.g;z.prototype.constrainResolution=z.prototype.constrainResolution;z.prototype.constrainRotation=z.prototype.constrainRotation;z.prototype.getCenter=z.prototype.a;z.prototype.calculateExtent=z.prototype.D;z.prototype.getProjection=z.prototype.F;z.prototype.getResolution=z.prototype.b;z.prototype.getResolutionForExtent=z.prototype.i;z.prototype.getRotation=z.prototype.e;z.prototype.getZoom=z.prototype.Rf;
-z.prototype.fitExtent=z.prototype.Yd;z.prototype.fitGeometry=z.prototype.lf;z.prototype.centerOn=z.prototype.ff;z.prototype.rotate=z.prototype.rotate;z.prototype.setCenter=z.prototype.Ka;z.prototype.setResolution=z.prototype.d;z.prototype.setRotation=z.prototype.q;z.prototype.setZoom=z.prototype.Q;s("ol.webgl.Context",qv);qv.prototype.getGL=qv.prototype.Ph;qv.prototype.useProgram=qv.prototype.Fd;s("ol.tilegrid.TileGrid",Vs);Vs.prototype.getMaxZoom=Vs.prototype.Oc;Vs.prototype.getMinZoom=Vs.prototype.Pc;
-Vs.prototype.getOrigin=Vs.prototype.mb;Vs.prototype.getResolution=Vs.prototype.fa;Vs.prototype.getResolutions=Vs.prototype.fd;Vs.prototype.getTileSize=Vs.prototype.ia;s("ol.tilegrid.WMTS",$x);$x.prototype.getMatrixIds=$x.prototype.i;s("ol.tilegrid.XYZ",Kw);s("ol.tilegrid.Zoomify",cy);s("ol.style.Circle",Ki);Ki.prototype.getAnchor=Ki.prototype.Pb;Ki.prototype.getFill=Ki.prototype.zh;Ki.prototype.getImage=Ki.prototype.sc;Ki.prototype.getOrigin=Ki.prototype.Wb;Ki.prototype.getRadius=Ki.prototype.Ah;
-Ki.prototype.getSize=Ki.prototype.lb;Ki.prototype.getStroke=Ki.prototype.Bh;s("ol.style.Fill",Hi);Hi.prototype.getColor=Hi.prototype.c;s("ol.style.Icon",Rn);Rn.prototype.getAnchor=Rn.prototype.Pb;Rn.prototype.getImage=Rn.prototype.sc;Rn.prototype.getOrigin=Rn.prototype.Wb;Rn.prototype.getSrc=Rn.prototype.Ch;Rn.prototype.getSize=Rn.prototype.lb;Ii.prototype.getRotation=Ii.prototype.l;Ii.prototype.getScale=Ii.prototype.k;s("ol.style.Stroke",Ji);Ji.prototype.getColor=Ji.prototype.Dh;
-Ji.prototype.getLineCap=Ji.prototype.xf;Ji.prototype.getLineDash=Ji.prototype.Eh;Ji.prototype.getLineJoin=Ji.prototype.yf;Ji.prototype.getMiterLimit=Ji.prototype.Df;Ji.prototype.getWidth=Ji.prototype.Fh;s("ol.style.Style",Li);Li.prototype.getFill=Li.prototype.Gh;Li.prototype.getImage=Li.prototype.Hh;Li.prototype.getStroke=Li.prototype.Ih;Li.prototype.getText=Li.prototype.Jh;Li.prototype.getZIndex=Li.prototype.Qf;s("ol.style.Text",Un);Un.prototype.getFont=Un.prototype.rf;Un.prototype.getOffsetX=Un.prototype.Ef;
-Un.prototype.getOffsetY=Un.prototype.Ff;Un.prototype.getFill=Un.prototype.Kh;Un.prototype.getRotation=Un.prototype.Lh;Un.prototype.getScale=Un.prototype.Mh;Un.prototype.getStroke=Un.prototype.Nh;Un.prototype.getText=Un.prototype.Oh;Un.prototype.getTextAlign=Un.prototype.Kf;Un.prototype.getTextBaseline=Un.prototype.Lf;s("ol.Sphere",ue);s("ol.source.BingMaps",Lw);s("ol.source.BingMaps.TOS_ATTRIBUTION",Mw);s("ol.source.Cluster",Nw);mx.prototype.readFeatures=mx.prototype.a;s("ol.source.GeoJSON",ox);
-s("ol.source.GPX",px);s("ol.source.IGC",qx);s("ol.source.ImageCanvas",tx);s("ol.source.ImageMapGuide",ux);ux.prototype.getParams=ux.prototype.D;ux.prototype.updateParams=ux.prototype.Q;s("ol.source.ImageStatic",vx);s("ol.source.ImageVector",wx);wx.prototype.getSource=wx.prototype.mh;s("ol.source.ImageWMS",yx);yx.prototype.getGetFeatureInfoUrl=yx.prototype.ph;yx.prototype.getParams=yx.prototype.qh;yx.prototype.getUrl=yx.prototype.rh;yx.prototype.setUrl=yx.prototype.sh;yx.prototype.updateParams=yx.prototype.th;
-s("ol.source.KML",Cx);s("ol.source.MapQuest",Ix);s("ol.source.OSM",Ex);s("ol.source.OSM.DATA_ATTRIBUTION",Gx);s("ol.source.OSM.TILE_ATTRIBUTION",Hx);s("ol.source.OSMXML",Lx);s("ol.source.ServerVector",Mx);Mx.prototype.readFeatures=Mx.prototype.a;Ss.prototype.getAttributions=Ss.prototype.U;Ss.prototype.getLogo=Ss.prototype.S;Ss.prototype.getProjection=Ss.prototype.V;Ss.prototype.getState=Ss.prototype.W;s("ol.source.Stamen",Px);s("ol.source.StaticVector",$);s("ol.source.TileDebug",Sx);
-s("ol.source.TileImage",Iw);Iw.prototype.getTileLoadFunction=Iw.prototype.Ra;Iw.prototype.getTileUrlFunction=Iw.prototype.Sa;Iw.prototype.setTileLoadFunction=Iw.prototype.Ua;Iw.prototype.setTileUrlFunction=Iw.prototype.ha;s("ol.source.TileJSON",Tx);dt.prototype.getTileGrid=dt.prototype.wa;s("ol.source.TileVector",Ux);s("ol.source.TileWMS",Vx);Vx.prototype.getGetFeatureInfoUrl=Vx.prototype.uh;Vx.prototype.getParams=Vx.prototype.vh;Vx.prototype.getUrls=Vx.prototype.Nf;Vx.prototype.setUrl=Vx.prototype.wh;
-Vx.prototype.setUrls=Vx.prototype.re;Vx.prototype.updateParams=Vx.prototype.yh;s("ol.source.TopoJSON",Zx);s("ol.source.Vector",Mv);Mv.prototype.addFeature=Mv.prototype.za;Mv.prototype.addFeatures=Mv.prototype.oa;Mv.prototype.clear=Mv.prototype.clear;Mv.prototype.forEachFeature=Mv.prototype.Ha;Mv.prototype.forEachFeatureInExtent=Mv.prototype.pa;Mv.prototype.getFeatures=Mv.prototype.qa;Mv.prototype.getFeaturesAtCoordinate=Mv.prototype.va;Mv.prototype.getClosestFeatureToCoordinate=Mv.prototype.Ia;
-Mv.prototype.getExtent=Mv.prototype.s;Mv.prototype.getFeatureById=Mv.prototype.ua;Mv.prototype.removeFeature=Mv.prototype.Ma;Ov.prototype.feature=Ov.prototype.feature;s("ol.source.WMTS",ay);ay.prototype.getDimensions=ay.prototype.e;ay.prototype.updateDimensions=ay.prototype.g;s("ol.source.XYZ",Dx);Dx.prototype.setTileUrlFunction=Dx.prototype.ha;Dx.prototype.setUrl=Dx.prototype.a;s("ol.source.Zoomify",dy);Pi.prototype.vectorContext=Pi.prototype.vectorContext;Pi.prototype.frameState=Pi.prototype.frameState;
-Pi.prototype.context=Pi.prototype.context;Pi.prototype.glContext=Pi.prototype.glContext;St.prototype.drawAsync=St.prototype.Ic;St.prototype.drawCircleGeometry=St.prototype.jc;St.prototype.drawFeature=St.prototype.Wd;St.prototype.drawPointGeometry=St.prototype.nc;St.prototype.drawMultiPointGeometry=St.prototype.lc;St.prototype.drawLineStringGeometry=St.prototype.sb;St.prototype.drawMultiLineStringGeometry=St.prototype.kc;St.prototype.drawPolygonGeometry=St.prototype.Ob;
-St.prototype.drawMultiPolygonGeometry=St.prototype.mc;St.prototype.setFillStrokeStyle=St.prototype.Aa;St.prototype.setImageStyle=St.prototype.$b;St.prototype.setTextStyle=St.prototype.Ca;s("ol.proj.common.add",Rt);s("ol.proj.METERS_PER_UNIT",xe);s("ol.proj.Projection",ye);ye.prototype.getCode=ye.prototype.mf;ye.prototype.getExtent=ye.prototype.s;ye.prototype.getUnits=ye.prototype.ih;ye.prototype.getMetersPerUnit=ye.prototype.ae;ye.prototype.getWorldExtent=ye.prototype.Pf;ye.prototype.isGlobal=ye.prototype.ug;
-ye.prototype.setExtent=ye.prototype.jh;ye.prototype.setWorldExtent=ye.prototype.zi;s("ol.proj.addEquivalentProjections",Ee);s("ol.proj.addProjection",Ne);s("ol.proj.addCoordinateTransforms",Pe);s("ol.proj.get",Be);s("ol.proj.getTransform",Se);s("ol.proj.transform",function(a,b,c){return Se(b,c)(a)});s("ol.proj.transformExtent",Ue);s("ol.layer.Heatmap",X);X.prototype.getGradient=X.prototype.sa;X.prototype.setGradient=X.prototype.Kb;s("ol.layer.Image",S);S.prototype.getSource=S.prototype.ma;
-s("ol.layer.Layer",P);P.prototype.getSource=P.prototype.ma;O.prototype.getBrightness=O.prototype.d;O.prototype.getContrast=O.prototype.e;O.prototype.getHue=O.prototype.f;O.prototype.getExtent=O.prototype.s;O.prototype.getMaxResolution=O.prototype.g;O.prototype.getMinResolution=O.prototype.i;O.prototype.getOpacity=O.prototype.k;O.prototype.getSaturation=O.prototype.l;O.prototype.getVisible=O.prototype.b;O.prototype.setBrightness=O.prototype.q;O.prototype.setContrast=O.prototype.r;
-O.prototype.setHue=O.prototype.u;O.prototype.setExtent=O.prototype.o;O.prototype.setMaxResolution=O.prototype.D;O.prototype.setMinResolution=O.prototype.F;O.prototype.setOpacity=O.prototype.Q;O.prototype.setSaturation=O.prototype.X;O.prototype.setVisible=O.prototype.ba;s("ol.layer.Group",R);R.prototype.getLayers=R.prototype.Ab;R.prototype.setLayers=R.prototype.aa;s("ol.layer.Tile",T);T.prototype.getPreload=T.prototype.aa;T.prototype.getSource=T.prototype.ma;T.prototype.setPreload=T.prototype.na;
-T.prototype.getUseInterimTilesOnError=T.prototype.ea;T.prototype.setUseInterimTilesOnError=T.prototype.sa;s("ol.layer.Vector",V);V.prototype.getSource=V.prototype.ma;V.prototype.getStyle=V.prototype.ac;V.prototype.getStyleFunction=V.prototype.Cc;V.prototype.setStyle=V.prototype.na;s("ol.interaction.DoubleClickZoom",nr);s("ol.interaction.DragAndDrop",fs);gs.prototype.features=gs.prototype.features;gs.prototype.file=gs.prototype.file;gs.prototype.projection=gs.prototype.projection;
-ns.prototype.coordinate=ns.prototype.coordinate;s("ol.interaction.DragBox",os);os.prototype.getGeometry=os.prototype.J;s("ol.interaction.DragPan",ss);s("ol.interaction.DragRotateAndZoom",us);s("ol.interaction.DragRotate",vs);s("ol.interaction.DragZoom",ws);Rv.prototype.feature=Rv.prototype.feature;s("ol.interaction.Draw",Sv);s("ol.interaction.defaults",Kt);s("ol.interaction.KeyboardPan",Ft);s("ol.interaction.KeyboardZoom",Gt);s("ol.interaction.Modify",bw);s("ol.interaction.MouseWheelZoom",Ht);
-s("ol.interaction.PinchRotate",It);s("ol.interaction.PinchZoom",Jt);s("ol.interaction.Select",gw);gw.prototype.getFeatures=gw.prototype.hh;gw.prototype.setMap=gw.prototype.setMap;s("ol.geom.Circle",Zi);Zi.prototype.clone=Zi.prototype.clone;Zi.prototype.getCenter=Zi.prototype.Dd;Zi.prototype.getExtent=Zi.prototype.s;Zi.prototype.getRadius=Zi.prototype.pe;Zi.prototype.getType=Zi.prototype.G;Zi.prototype.setCenter=Zi.prototype.Zg;Zi.prototype.setCenterAndRadius=Zi.prototype.He;
-Zi.prototype.setRadius=Zi.prototype.yi;s("ol.geom.Geometry",Di);Di.prototype.clone=Di.prototype.clone;Di.prototype.getClosestPoint=Di.prototype.g;Di.prototype.getExtent=Di.prototype.s;Di.prototype.getType=Di.prototype.G;Di.prototype.applyTransform=Di.prototype.Mb;Di.prototype.transform=Di.prototype.f;s("ol.geom.GeometryCollection",aj);aj.prototype.clone=aj.prototype.clone;aj.prototype.getExtent=aj.prototype.s;aj.prototype.getGeometries=aj.prototype.$d;aj.prototype.getType=aj.prototype.G;
-aj.prototype.setGeometries=aj.prototype.Ie;s("ol.geom.LinearRing",Dj);Dj.prototype.clone=Dj.prototype.clone;Dj.prototype.getArea=Dj.prototype.bh;Dj.prototype.getCoordinates=Dj.prototype.H;Dj.prototype.getType=Dj.prototype.G;Dj.prototype.setCoordinates=Dj.prototype.P;s("ol.geom.LineString",H);H.prototype.appendCoordinate=H.prototype.$e;H.prototype.clone=H.prototype.clone;H.prototype.getCoordinateAtM=H.prototype.$g;H.prototype.getCoordinates=H.prototype.H;H.prototype.getLength=H.prototype.ah;
-H.prototype.getType=H.prototype.G;H.prototype.setCoordinates=H.prototype.P;s("ol.geom.MultiLineString",uj);uj.prototype.appendLineString=uj.prototype.af;uj.prototype.clone=uj.prototype.clone;uj.prototype.getCoordinateAtM=uj.prototype.dh;uj.prototype.getCoordinates=uj.prototype.H;uj.prototype.getLineString=uj.prototype.zf;uj.prototype.getLineStrings=uj.prototype.oc;uj.prototype.getType=uj.prototype.G;uj.prototype.setCoordinates=uj.prototype.P;s("ol.geom.MultiPoint",Aj);Aj.prototype.appendPoint=Aj.prototype.cf;
-Aj.prototype.clone=Aj.prototype.clone;Aj.prototype.getCoordinates=Aj.prototype.H;Aj.prototype.getPoint=Aj.prototype.If;Aj.prototype.getPoints=Aj.prototype.Qc;Aj.prototype.getType=Aj.prototype.G;Aj.prototype.setCoordinates=Aj.prototype.P;s("ol.geom.MultiPolygon",Pj);Pj.prototype.appendPolygon=Pj.prototype.df;Pj.prototype.clone=Pj.prototype.clone;Pj.prototype.getArea=Pj.prototype.eh;Pj.prototype.getCoordinates=Pj.prototype.H;Pj.prototype.getInteriorPoints=Pj.prototype.wf;Pj.prototype.getPolygon=Pj.prototype.Jf;
-Pj.prototype.getPolygons=Pj.prototype.Rc;Pj.prototype.getType=Pj.prototype.G;Pj.prototype.setCoordinates=Pj.prototype.P;s("ol.geom.Point",yj);yj.prototype.clone=yj.prototype.clone;yj.prototype.getCoordinates=yj.prototype.H;yj.prototype.getType=yj.prototype.G;yj.prototype.setCoordinates=yj.prototype.P;s("ol.geom.Polygon",I);I.prototype.appendLinearRing=I.prototype.bf;I.prototype.clone=I.prototype.clone;I.prototype.getArea=I.prototype.fh;I.prototype.getCoordinates=I.prototype.H;
-I.prototype.getInteriorPoint=I.prototype.vf;I.prototype.getLinearRingCount=I.prototype.Bf;I.prototype.getLinearRing=I.prototype.Af;I.prototype.getLinearRings=I.prototype.Nc;I.prototype.getType=I.prototype.G;I.prototype.setCoordinates=I.prototype.P;s("ol.geom.Polygon.circular",Oj);s("ol.geom.SimpleGeometry",Ri);Ri.prototype.getExtent=Ri.prototype.s;Ri.prototype.getFirstCoordinate=Ri.prototype.Xa;Ri.prototype.getLastCoordinate=Ri.prototype.Ya;Ri.prototype.getLayout=Ri.prototype.Za;
-s("ol.format.Feature",dk);s("ol.format.GeoJSON",nk);nk.prototype.readFeature=nk.prototype.ob;nk.prototype.readFeatures=nk.prototype.la;nk.prototype.readGeometry=nk.prototype.uc;nk.prototype.readProjection=nk.prototype.ra;nk.prototype.writeFeature=nk.prototype.md;nk.prototype.writeFeatures=nk.prototype.Hb;nk.prototype.writeGeometry=nk.prototype.Ac;s("ol.format.GML",wl);wl.prototype.readFeatures=wl.prototype.la;wl.prototype.writeFeatures=wl.prototype.Hb;s("ol.format.GPX",zm);
-zm.prototype.readFeature=zm.prototype.ob;zm.prototype.readFeatures=zm.prototype.la;zm.prototype.readProjection=zm.prototype.ra;zm.prototype.writeFeatures=zm.prototype.Hb;s("ol.format.IGC",kn);kn.prototype.readFeature=kn.prototype.ob;kn.prototype.readFeatures=kn.prototype.la;kn.prototype.readProjection=kn.prototype.ra;s("ol.format.KML",Vn);Vn.prototype.readFeature=Vn.prototype.ob;Vn.prototype.readFeatures=Vn.prototype.la;Vn.prototype.readName=Vn.prototype.ii;Vn.prototype.readProjection=Vn.prototype.ra;
-Vn.prototype.writeFeatures=Vn.prototype.Hb;s("ol.format.OSMXML",xp);xp.prototype.readFeatures=xp.prototype.la;xp.prototype.readProjection=xp.prototype.ra;s("ol.format.Polyline",Hp);s("ol.format.Polyline.encodeDeltas",Ip);s("ol.format.Polyline.decodeDeltas",Kp);s("ol.format.Polyline.encodeFloats",Jp);s("ol.format.Polyline.decodeFloats",Lp);Hp.prototype.readFeature=Hp.prototype.ob;Hp.prototype.readFeatures=Hp.prototype.la;Hp.prototype.readGeometry=Hp.prototype.uc;Hp.prototype.readProjection=Hp.prototype.ra;
-Hp.prototype.writeGeometry=Hp.prototype.Ac;s("ol.format.TopoJSON",Mp);Mp.prototype.readFeatures=Mp.prototype.la;Mp.prototype.readProjection=Mp.prototype.ra;s("ol.format.WFS",Sp);Sp.prototype.readFeatures=Sp.prototype.la;Sp.prototype.readTransactionResponse=Sp.prototype.o;Sp.prototype.readFeatureCollectionMetadata=Sp.prototype.k;Sp.prototype.writeGetFeature=Sp.prototype.q;Sp.prototype.writeTransaction=Sp.prototype.r;Sp.prototype.readProjection=Sp.prototype.ra;s("ol.format.WKT",eq);
-eq.prototype.readFeature=eq.prototype.ob;eq.prototype.readFeatures=eq.prototype.la;eq.prototype.readGeometry=eq.prototype.uc;eq.prototype.writeFeature=eq.prototype.md;eq.prototype.writeFeatures=eq.prototype.Hb;eq.prototype.writeGeometry=eq.prototype.Ac;s("ol.format.WMSCapabilities",wq);wq.prototype.read=wq.prototype.a;s("ol.events.condition.altKeyOnly",function(a){a=a.a;return a.c&&!a.i&&!a.e});s("ol.events.condition.altShiftKeysOnly",xi);s("ol.events.condition.always",dd);
-s("ol.events.condition.click",function(a){return a.type==ni});s("ol.events.condition.mouseMove",function(a){return"mousemove"==a.originalEvent.type});s("ol.events.condition.never",cd);s("ol.events.condition.singleClick",yi);s("ol.events.condition.noModifierKeys",zi);s("ol.events.condition.platformModifierKeyOnly",function(a){a=a.a;return!a.c&&a.i&&!a.e});s("ol.events.condition.shiftKeyOnly",Ai);s("ol.events.condition.targetNotEditable",Bi);s("ol.events.condition.mouseOnly",Ci);s("ol.dom.Input",ii);
-ii.prototype.getChecked=ii.prototype.b;ii.prototype.getValue=ii.prototype.d;ii.prototype.setValue=ii.prototype.f;ii.prototype.setChecked=ii.prototype.e;s("ol.control.Attribution",Bh);Bh.prototype.getCollapsible=Bh.prototype.of;Bh.prototype.setCollapsible=Bh.prototype.xi;Bh.prototype.setCollapsed=Bh.prototype.wi;Bh.prototype.getCollapsed=Bh.prototype.nf;s("ol.control.Control",Sg);Sg.prototype.getMap=Sg.prototype.d;Sg.prototype.setMap=Sg.prototype.setMap;s("ol.control.defaults",Hh);
-s("ol.control.FullScreen",Mh);s("ol.control.MousePosition",C);C.prototype.getCoordinateFormat=C.prototype.g;C.prototype.getProjection=C.prototype.k;C.prototype.setMap=C.prototype.setMap;C.prototype.setCoordinateFormat=C.prototype.q;C.prototype.setProjection=C.prototype.o;s("ol.control.Rotate",Dh);s("ol.control.ScaleLine",Ph);Ph.prototype.getUnits=Ph.prototype.k;Ph.prototype.setUnits=Ph.prototype.o;s("ol.control.Zoom",Fh);s("ol.control.ZoomSlider",ci);s("ol.control.ZoomToExtent",gi);
-s("ol.color.asArray",Hg);s("ol.color.asString",Jg);pd.prototype.dispatchChangeEvent=pd.prototype.n;pd.prototype.getRevision=pd.prototype.A;pd.prototype.on=pd.prototype.t;pd.prototype.once=pd.prototype.B;pd.prototype.un=pd.prototype.v;pd.prototype.unByKey=pd.prototype.C;A.prototype.bindTo=A.prototype.Z;A.prototype.get=A.prototype.get;A.prototype.getKeys=A.prototype.N;A.prototype.getProperties=A.prototype.R;A.prototype.set=A.prototype.set;A.prototype.setProperties=A.prototype.L;A.prototype.unbind=A.prototype.Y;
-A.prototype.unbindAll=A.prototype.$;A.prototype.dispatchChangeEvent=A.prototype.n;A.prototype.getRevision=A.prototype.A;A.prototype.on=A.prototype.t;A.prototype.once=A.prototype.B;A.prototype.un=A.prototype.v;A.prototype.unByKey=A.prototype.C;D.prototype.bindTo=D.prototype.Z;D.prototype.get=D.prototype.get;D.prototype.getKeys=D.prototype.N;D.prototype.getProperties=D.prototype.R;D.prototype.set=D.prototype.set;D.prototype.setProperties=D.prototype.L;D.prototype.unbind=D.prototype.Y;
-D.prototype.unbindAll=D.prototype.$;D.prototype.dispatchChangeEvent=D.prototype.n;D.prototype.getRevision=D.prototype.A;D.prototype.on=D.prototype.t;D.prototype.once=D.prototype.B;D.prototype.un=D.prototype.v;D.prototype.unByKey=D.prototype.C;G.prototype.bindTo=G.prototype.Z;G.prototype.get=G.prototype.get;G.prototype.getKeys=G.prototype.N;G.prototype.getProperties=G.prototype.R;G.prototype.set=G.prototype.set;G.prototype.setProperties=G.prototype.L;G.prototype.unbind=G.prototype.Y;
-G.prototype.unbindAll=G.prototype.$;G.prototype.dispatchChangeEvent=G.prototype.n;G.prototype.getRevision=G.prototype.A;G.prototype.on=G.prototype.t;G.prototype.once=G.prototype.B;G.prototype.un=G.prototype.v;G.prototype.unByKey=G.prototype.C;N.prototype.bindTo=N.prototype.Z;N.prototype.get=N.prototype.get;N.prototype.getKeys=N.prototype.N;N.prototype.getProperties=N.prototype.R;N.prototype.set=N.prototype.set;N.prototype.setProperties=N.prototype.L;N.prototype.unbind=N.prototype.Y;
-N.prototype.unbindAll=N.prototype.$;N.prototype.dispatchChangeEvent=N.prototype.n;N.prototype.getRevision=N.prototype.A;N.prototype.on=N.prototype.t;N.prototype.once=N.prototype.B;N.prototype.un=N.prototype.v;N.prototype.unByKey=N.prototype.C;gr.prototype.getTileCoord=gr.prototype.i;W.prototype.bindTo=W.prototype.Z;W.prototype.get=W.prototype.get;W.prototype.getKeys=W.prototype.N;W.prototype.getProperties=W.prototype.R;W.prototype.set=W.prototype.set;W.prototype.setProperties=W.prototype.L;
-W.prototype.unbind=W.prototype.Y;W.prototype.unbindAll=W.prototype.$;W.prototype.dispatchChangeEvent=W.prototype.n;W.prototype.getRevision=W.prototype.A;W.prototype.on=W.prototype.t;W.prototype.once=W.prototype.B;W.prototype.un=W.prototype.v;W.prototype.unByKey=W.prototype.C;ji.prototype.map=ji.prototype.map;ji.prototype.frameState=ji.prototype.frameState;ki.prototype.originalEvent=ki.prototype.originalEvent;ki.prototype.pixel=ki.prototype.pixel;ki.prototype.coordinate=ki.prototype.coordinate;
-ki.prototype.preventDefault=ki.prototype.preventDefault;ki.prototype.stopPropagation=ki.prototype.d;ki.prototype.map=ki.prototype.map;ki.prototype.frameState=ki.prototype.frameState;Z.prototype.bindTo=Z.prototype.Z;Z.prototype.get=Z.prototype.get;Z.prototype.getKeys=Z.prototype.N;Z.prototype.getProperties=Z.prototype.R;Z.prototype.set=Z.prototype.set;Z.prototype.setProperties=Z.prototype.L;Z.prototype.unbind=Z.prototype.Y;Z.prototype.unbindAll=Z.prototype.$;Z.prototype.dispatchChangeEvent=Z.prototype.n;
-Z.prototype.getRevision=Z.prototype.A;Z.prototype.on=Z.prototype.t;Z.prototype.once=Z.prototype.B;Z.prototype.un=Z.prototype.v;Z.prototype.unByKey=Z.prototype.C;z.prototype.bindTo=z.prototype.Z;z.prototype.get=z.prototype.get;z.prototype.getKeys=z.prototype.N;z.prototype.getProperties=z.prototype.R;z.prototype.set=z.prototype.set;z.prototype.setProperties=z.prototype.L;z.prototype.unbind=z.prototype.Y;z.prototype.unbindAll=z.prototype.$;z.prototype.dispatchChangeEvent=z.prototype.n;
-z.prototype.getRevision=z.prototype.A;z.prototype.on=z.prototype.t;z.prototype.once=z.prototype.B;z.prototype.un=z.prototype.v;z.prototype.unByKey=z.prototype.C;$x.prototype.getMaxZoom=$x.prototype.Oc;$x.prototype.getMinZoom=$x.prototype.Pc;$x.prototype.getOrigin=$x.prototype.mb;$x.prototype.getResolution=$x.prototype.fa;$x.prototype.getResolutions=$x.prototype.fd;$x.prototype.getTileSize=$x.prototype.ia;Kw.prototype.getMaxZoom=Kw.prototype.Oc;Kw.prototype.getMinZoom=Kw.prototype.Pc;
-Kw.prototype.getOrigin=Kw.prototype.mb;Kw.prototype.getResolution=Kw.prototype.fa;Kw.prototype.getResolutions=Kw.prototype.fd;Kw.prototype.getTileSize=Kw.prototype.ia;cy.prototype.getMaxZoom=cy.prototype.Oc;cy.prototype.getMinZoom=cy.prototype.Pc;cy.prototype.getOrigin=cy.prototype.mb;cy.prototype.getResolution=cy.prototype.fa;cy.prototype.getResolutions=cy.prototype.fd;cy.prototype.getTileSize=cy.prototype.ia;Ki.prototype.getRotation=Ki.prototype.l;Ki.prototype.getScale=Ki.prototype.k;
-Rn.prototype.getRotation=Rn.prototype.l;Rn.prototype.getScale=Rn.prototype.k;Ss.prototype.dispatchChangeEvent=Ss.prototype.n;Ss.prototype.getRevision=Ss.prototype.A;Ss.prototype.on=Ss.prototype.t;Ss.prototype.once=Ss.prototype.B;Ss.prototype.un=Ss.prototype.v;Ss.prototype.unByKey=Ss.prototype.C;dt.prototype.getAttributions=dt.prototype.U;dt.prototype.getLogo=dt.prototype.S;dt.prototype.getProjection=dt.prototype.V;dt.prototype.getState=dt.prototype.W;dt.prototype.dispatchChangeEvent=dt.prototype.n;
-dt.prototype.getRevision=dt.prototype.A;dt.prototype.on=dt.prototype.t;dt.prototype.once=dt.prototype.B;dt.prototype.un=dt.prototype.v;dt.prototype.unByKey=dt.prototype.C;Iw.prototype.getTileGrid=Iw.prototype.wa;Iw.prototype.getAttributions=Iw.prototype.U;Iw.prototype.getLogo=Iw.prototype.S;Iw.prototype.getProjection=Iw.prototype.V;Iw.prototype.getState=Iw.prototype.W;Iw.prototype.dispatchChangeEvent=Iw.prototype.n;Iw.prototype.getRevision=Iw.prototype.A;Iw.prototype.on=Iw.prototype.t;
-Iw.prototype.once=Iw.prototype.B;Iw.prototype.un=Iw.prototype.v;Iw.prototype.unByKey=Iw.prototype.C;Lw.prototype.getTileLoadFunction=Lw.prototype.Ra;Lw.prototype.getTileUrlFunction=Lw.prototype.Sa;Lw.prototype.setTileLoadFunction=Lw.prototype.Ua;Lw.prototype.setTileUrlFunction=Lw.prototype.ha;Lw.prototype.getTileGrid=Lw.prototype.wa;Lw.prototype.getAttributions=Lw.prototype.U;Lw.prototype.getLogo=Lw.prototype.S;Lw.prototype.getProjection=Lw.prototype.V;Lw.prototype.getState=Lw.prototype.W;
-Lw.prototype.dispatchChangeEvent=Lw.prototype.n;Lw.prototype.getRevision=Lw.prototype.A;Lw.prototype.on=Lw.prototype.t;Lw.prototype.once=Lw.prototype.B;Lw.prototype.un=Lw.prototype.v;Lw.prototype.unByKey=Lw.prototype.C;Mv.prototype.getAttributions=Mv.prototype.U;Mv.prototype.getLogo=Mv.prototype.S;Mv.prototype.getProjection=Mv.prototype.V;Mv.prototype.getState=Mv.prototype.W;Mv.prototype.dispatchChangeEvent=Mv.prototype.n;Mv.prototype.getRevision=Mv.prototype.A;Mv.prototype.on=Mv.prototype.t;
-Mv.prototype.once=Mv.prototype.B;Mv.prototype.un=Mv.prototype.v;Mv.prototype.unByKey=Mv.prototype.C;Nw.prototype.addFeature=Nw.prototype.za;Nw.prototype.addFeatures=Nw.prototype.oa;Nw.prototype.clear=Nw.prototype.clear;Nw.prototype.forEachFeature=Nw.prototype.Ha;Nw.prototype.forEachFeatureInExtent=Nw.prototype.pa;Nw.prototype.getFeatures=Nw.prototype.qa;Nw.prototype.getFeaturesAtCoordinate=Nw.prototype.va;Nw.prototype.getClosestFeatureToCoordinate=Nw.prototype.Ia;Nw.prototype.getExtent=Nw.prototype.s;
-Nw.prototype.getFeatureById=Nw.prototype.ua;Nw.prototype.removeFeature=Nw.prototype.Ma;Nw.prototype.getAttributions=Nw.prototype.U;Nw.prototype.getLogo=Nw.prototype.S;Nw.prototype.getProjection=Nw.prototype.V;Nw.prototype.getState=Nw.prototype.W;Nw.prototype.dispatchChangeEvent=Nw.prototype.n;Nw.prototype.getRevision=Nw.prototype.A;Nw.prototype.on=Nw.prototype.t;Nw.prototype.once=Nw.prototype.B;Nw.prototype.un=Nw.prototype.v;Nw.prototype.unByKey=Nw.prototype.C;mx.prototype.addFeature=mx.prototype.za;
-mx.prototype.addFeatures=mx.prototype.oa;mx.prototype.clear=mx.prototype.clear;mx.prototype.forEachFeature=mx.prototype.Ha;mx.prototype.forEachFeatureInExtent=mx.prototype.pa;mx.prototype.getFeatures=mx.prototype.qa;mx.prototype.getFeaturesAtCoordinate=mx.prototype.va;mx.prototype.getClosestFeatureToCoordinate=mx.prototype.Ia;mx.prototype.getExtent=mx.prototype.s;mx.prototype.getFeatureById=mx.prototype.ua;mx.prototype.removeFeature=mx.prototype.Ma;mx.prototype.getAttributions=mx.prototype.U;
-mx.prototype.getLogo=mx.prototype.S;mx.prototype.getProjection=mx.prototype.V;mx.prototype.getState=mx.prototype.W;mx.prototype.dispatchChangeEvent=mx.prototype.n;mx.prototype.getRevision=mx.prototype.A;mx.prototype.on=mx.prototype.t;mx.prototype.once=mx.prototype.B;mx.prototype.un=mx.prototype.v;mx.prototype.unByKey=mx.prototype.C;$.prototype.readFeatures=$.prototype.a;$.prototype.addFeature=$.prototype.za;$.prototype.addFeatures=$.prototype.oa;$.prototype.clear=$.prototype.clear;
-$.prototype.forEachFeature=$.prototype.Ha;$.prototype.forEachFeatureInExtent=$.prototype.pa;$.prototype.getFeatures=$.prototype.qa;$.prototype.getFeaturesAtCoordinate=$.prototype.va;$.prototype.getClosestFeatureToCoordinate=$.prototype.Ia;$.prototype.getExtent=$.prototype.s;$.prototype.getFeatureById=$.prototype.ua;$.prototype.removeFeature=$.prototype.Ma;$.prototype.getAttributions=$.prototype.U;$.prototype.getLogo=$.prototype.S;$.prototype.getProjection=$.prototype.V;$.prototype.getState=$.prototype.W;
-$.prototype.dispatchChangeEvent=$.prototype.n;$.prototype.getRevision=$.prototype.A;$.prototype.on=$.prototype.t;$.prototype.once=$.prototype.B;$.prototype.un=$.prototype.v;$.prototype.unByKey=$.prototype.C;ox.prototype.readFeatures=ox.prototype.a;ox.prototype.addFeature=ox.prototype.za;ox.prototype.addFeatures=ox.prototype.oa;ox.prototype.clear=ox.prototype.clear;ox.prototype.forEachFeature=ox.prototype.Ha;ox.prototype.forEachFeatureInExtent=ox.prototype.pa;ox.prototype.getFeatures=ox.prototype.qa;
-ox.prototype.getFeaturesAtCoordinate=ox.prototype.va;ox.prototype.getClosestFeatureToCoordinate=ox.prototype.Ia;ox.prototype.getExtent=ox.prototype.s;ox.prototype.getFeatureById=ox.prototype.ua;ox.prototype.removeFeature=ox.prototype.Ma;ox.prototype.getAttributions=ox.prototype.U;ox.prototype.getLogo=ox.prototype.S;ox.prototype.getProjection=ox.prototype.V;ox.prototype.getState=ox.prototype.W;ox.prototype.dispatchChangeEvent=ox.prototype.n;ox.prototype.getRevision=ox.prototype.A;ox.prototype.on=ox.prototype.t;
-ox.prototype.once=ox.prototype.B;ox.prototype.un=ox.prototype.v;ox.prototype.unByKey=ox.prototype.C;px.prototype.readFeatures=px.prototype.a;px.prototype.addFeature=px.prototype.za;px.prototype.addFeatures=px.prototype.oa;px.prototype.clear=px.prototype.clear;px.prototype.forEachFeature=px.prototype.Ha;px.prototype.forEachFeatureInExtent=px.prototype.pa;px.prototype.getFeatures=px.prototype.qa;px.prototype.getFeaturesAtCoordinate=px.prototype.va;px.prototype.getClosestFeatureToCoordinate=px.prototype.Ia;
-px.prototype.getExtent=px.prototype.s;px.prototype.getFeatureById=px.prototype.ua;px.prototype.removeFeature=px.prototype.Ma;px.prototype.getAttributions=px.prototype.U;px.prototype.getLogo=px.prototype.S;px.prototype.getProjection=px.prototype.V;px.prototype.getState=px.prototype.W;px.prototype.dispatchChangeEvent=px.prototype.n;px.prototype.getRevision=px.prototype.A;px.prototype.on=px.prototype.t;px.prototype.once=px.prototype.B;px.prototype.un=px.prototype.v;px.prototype.unByKey=px.prototype.C;
-qx.prototype.readFeatures=qx.prototype.a;qx.prototype.addFeature=qx.prototype.za;qx.prototype.addFeatures=qx.prototype.oa;qx.prototype.clear=qx.prototype.clear;qx.prototype.forEachFeature=qx.prototype.Ha;qx.prototype.forEachFeatureInExtent=qx.prototype.pa;qx.prototype.getFeatures=qx.prototype.qa;qx.prototype.getFeaturesAtCoordinate=qx.prototype.va;qx.prototype.getClosestFeatureToCoordinate=qx.prototype.Ia;qx.prototype.getExtent=qx.prototype.s;qx.prototype.getFeatureById=qx.prototype.ua;
-qx.prototype.removeFeature=qx.prototype.Ma;qx.prototype.getAttributions=qx.prototype.U;qx.prototype.getLogo=qx.prototype.S;qx.prototype.getProjection=qx.prototype.V;qx.prototype.getState=qx.prototype.W;qx.prototype.dispatchChangeEvent=qx.prototype.n;qx.prototype.getRevision=qx.prototype.A;qx.prototype.on=qx.prototype.t;qx.prototype.once=qx.prototype.B;qx.prototype.un=qx.prototype.v;qx.prototype.unByKey=qx.prototype.C;rx.prototype.getAttributions=rx.prototype.U;rx.prototype.getLogo=rx.prototype.S;
-rx.prototype.getProjection=rx.prototype.V;rx.prototype.getState=rx.prototype.W;rx.prototype.dispatchChangeEvent=rx.prototype.n;rx.prototype.getRevision=rx.prototype.A;rx.prototype.on=rx.prototype.t;rx.prototype.once=rx.prototype.B;rx.prototype.un=rx.prototype.v;rx.prototype.unByKey=rx.prototype.C;tx.prototype.getAttributions=tx.prototype.U;tx.prototype.getLogo=tx.prototype.S;tx.prototype.getProjection=tx.prototype.V;tx.prototype.getState=tx.prototype.W;tx.prototype.dispatchChangeEvent=tx.prototype.n;
-tx.prototype.getRevision=tx.prototype.A;tx.prototype.on=tx.prototype.t;tx.prototype.once=tx.prototype.B;tx.prototype.un=tx.prototype.v;tx.prototype.unByKey=tx.prototype.C;ux.prototype.getAttributions=ux.prototype.U;ux.prototype.getLogo=ux.prototype.S;ux.prototype.getProjection=ux.prototype.V;ux.prototype.getState=ux.prototype.W;ux.prototype.dispatchChangeEvent=ux.prototype.n;ux.prototype.getRevision=ux.prototype.A;ux.prototype.on=ux.prototype.t;ux.prototype.once=ux.prototype.B;ux.prototype.un=ux.prototype.v;
-ux.prototype.unByKey=ux.prototype.C;vx.prototype.getAttributions=vx.prototype.U;vx.prototype.getLogo=vx.prototype.S;vx.prototype.getProjection=vx.prototype.V;vx.prototype.getState=vx.prototype.W;vx.prototype.dispatchChangeEvent=vx.prototype.n;vx.prototype.getRevision=vx.prototype.A;vx.prototype.on=vx.prototype.t;vx.prototype.once=vx.prototype.B;vx.prototype.un=vx.prototype.v;vx.prototype.unByKey=vx.prototype.C;wx.prototype.getAttributions=wx.prototype.U;wx.prototype.getLogo=wx.prototype.S;
-wx.prototype.getProjection=wx.prototype.V;wx.prototype.getState=wx.prototype.W;wx.prototype.dispatchChangeEvent=wx.prototype.n;wx.prototype.getRevision=wx.prototype.A;wx.prototype.on=wx.prototype.t;wx.prototype.once=wx.prototype.B;wx.prototype.un=wx.prototype.v;wx.prototype.unByKey=wx.prototype.C;yx.prototype.getAttributions=yx.prototype.U;yx.prototype.getLogo=yx.prototype.S;yx.prototype.getProjection=yx.prototype.V;yx.prototype.getState=yx.prototype.W;yx.prototype.dispatchChangeEvent=yx.prototype.n;
-yx.prototype.getRevision=yx.prototype.A;yx.prototype.on=yx.prototype.t;yx.prototype.once=yx.prototype.B;yx.prototype.un=yx.prototype.v;yx.prototype.unByKey=yx.prototype.C;Cx.prototype.readFeatures=Cx.prototype.a;Cx.prototype.addFeature=Cx.prototype.za;Cx.prototype.addFeatures=Cx.prototype.oa;Cx.prototype.clear=Cx.prototype.clear;Cx.prototype.forEachFeature=Cx.prototype.Ha;Cx.prototype.forEachFeatureInExtent=Cx.prototype.pa;Cx.prototype.getFeatures=Cx.prototype.qa;
-Cx.prototype.getFeaturesAtCoordinate=Cx.prototype.va;Cx.prototype.getClosestFeatureToCoordinate=Cx.prototype.Ia;Cx.prototype.getExtent=Cx.prototype.s;Cx.prototype.getFeatureById=Cx.prototype.ua;Cx.prototype.removeFeature=Cx.prototype.Ma;Cx.prototype.getAttributions=Cx.prototype.U;Cx.prototype.getLogo=Cx.prototype.S;Cx.prototype.getProjection=Cx.prototype.V;Cx.prototype.getState=Cx.prototype.W;Cx.prototype.dispatchChangeEvent=Cx.prototype.n;Cx.prototype.getRevision=Cx.prototype.A;Cx.prototype.on=Cx.prototype.t;
-Cx.prototype.once=Cx.prototype.B;Cx.prototype.un=Cx.prototype.v;Cx.prototype.unByKey=Cx.prototype.C;Dx.prototype.getTileLoadFunction=Dx.prototype.Ra;Dx.prototype.getTileUrlFunction=Dx.prototype.Sa;Dx.prototype.setTileLoadFunction=Dx.prototype.Ua;Dx.prototype.getTileGrid=Dx.prototype.wa;Dx.prototype.getAttributions=Dx.prototype.U;Dx.prototype.getLogo=Dx.prototype.S;Dx.prototype.getProjection=Dx.prototype.V;Dx.prototype.getState=Dx.prototype.W;Dx.prototype.dispatchChangeEvent=Dx.prototype.n;
-Dx.prototype.getRevision=Dx.prototype.A;Dx.prototype.on=Dx.prototype.t;Dx.prototype.once=Dx.prototype.B;Dx.prototype.un=Dx.prototype.v;Dx.prototype.unByKey=Dx.prototype.C;Ix.prototype.setTileUrlFunction=Ix.prototype.ha;Ix.prototype.setUrl=Ix.prototype.a;Ix.prototype.getTileLoadFunction=Ix.prototype.Ra;Ix.prototype.getTileUrlFunction=Ix.prototype.Sa;Ix.prototype.setTileLoadFunction=Ix.prototype.Ua;Ix.prototype.getTileGrid=Ix.prototype.wa;Ix.prototype.getAttributions=Ix.prototype.U;
-Ix.prototype.getLogo=Ix.prototype.S;Ix.prototype.getProjection=Ix.prototype.V;Ix.prototype.getState=Ix.prototype.W;Ix.prototype.dispatchChangeEvent=Ix.prototype.n;Ix.prototype.getRevision=Ix.prototype.A;Ix.prototype.on=Ix.prototype.t;Ix.prototype.once=Ix.prototype.B;Ix.prototype.un=Ix.prototype.v;Ix.prototype.unByKey=Ix.prototype.C;Ex.prototype.setTileUrlFunction=Ex.prototype.ha;Ex.prototype.setUrl=Ex.prototype.a;Ex.prototype.getTileLoadFunction=Ex.prototype.Ra;Ex.prototype.getTileUrlFunction=Ex.prototype.Sa;
-Ex.prototype.setTileLoadFunction=Ex.prototype.Ua;Ex.prototype.getTileGrid=Ex.prototype.wa;Ex.prototype.getAttributions=Ex.prototype.U;Ex.prototype.getLogo=Ex.prototype.S;Ex.prototype.getProjection=Ex.prototype.V;Ex.prototype.getState=Ex.prototype.W;Ex.prototype.dispatchChangeEvent=Ex.prototype.n;Ex.prototype.getRevision=Ex.prototype.A;Ex.prototype.on=Ex.prototype.t;Ex.prototype.once=Ex.prototype.B;Ex.prototype.un=Ex.prototype.v;Ex.prototype.unByKey=Ex.prototype.C;Lx.prototype.readFeatures=Lx.prototype.a;
-Lx.prototype.addFeature=Lx.prototype.za;Lx.prototype.addFeatures=Lx.prototype.oa;Lx.prototype.clear=Lx.prototype.clear;Lx.prototype.forEachFeature=Lx.prototype.Ha;Lx.prototype.forEachFeatureInExtent=Lx.prototype.pa;Lx.prototype.getFeatures=Lx.prototype.qa;Lx.prototype.getFeaturesAtCoordinate=Lx.prototype.va;Lx.prototype.getClosestFeatureToCoordinate=Lx.prototype.Ia;Lx.prototype.getExtent=Lx.prototype.s;Lx.prototype.getFeatureById=Lx.prototype.ua;Lx.prototype.removeFeature=Lx.prototype.Ma;
-Lx.prototype.getAttributions=Lx.prototype.U;Lx.prototype.getLogo=Lx.prototype.S;Lx.prototype.getProjection=Lx.prototype.V;Lx.prototype.getState=Lx.prototype.W;Lx.prototype.dispatchChangeEvent=Lx.prototype.n;Lx.prototype.getRevision=Lx.prototype.A;Lx.prototype.on=Lx.prototype.t;Lx.prototype.once=Lx.prototype.B;Lx.prototype.un=Lx.prototype.v;Lx.prototype.unByKey=Lx.prototype.C;Mx.prototype.addFeature=Mx.prototype.za;Mx.prototype.addFeatures=Mx.prototype.oa;Mx.prototype.forEachFeature=Mx.prototype.Ha;
-Mx.prototype.forEachFeatureInExtent=Mx.prototype.pa;Mx.prototype.getFeatures=Mx.prototype.qa;Mx.prototype.getFeaturesAtCoordinate=Mx.prototype.va;Mx.prototype.getClosestFeatureToCoordinate=Mx.prototype.Ia;Mx.prototype.getExtent=Mx.prototype.s;Mx.prototype.getFeatureById=Mx.prototype.ua;Mx.prototype.removeFeature=Mx.prototype.Ma;Mx.prototype.getAttributions=Mx.prototype.U;Mx.prototype.getLogo=Mx.prototype.S;Mx.prototype.getProjection=Mx.prototype.V;Mx.prototype.getState=Mx.prototype.W;
-Mx.prototype.dispatchChangeEvent=Mx.prototype.n;Mx.prototype.getRevision=Mx.prototype.A;Mx.prototype.on=Mx.prototype.t;Mx.prototype.once=Mx.prototype.B;Mx.prototype.un=Mx.prototype.v;Mx.prototype.unByKey=Mx.prototype.C;Px.prototype.setTileUrlFunction=Px.prototype.ha;Px.prototype.setUrl=Px.prototype.a;Px.prototype.getTileLoadFunction=Px.prototype.Ra;Px.prototype.getTileUrlFunction=Px.prototype.Sa;Px.prototype.setTileLoadFunction=Px.prototype.Ua;Px.prototype.getTileGrid=Px.prototype.wa;
-Px.prototype.getAttributions=Px.prototype.U;Px.prototype.getLogo=Px.prototype.S;Px.prototype.getProjection=Px.prototype.V;Px.prototype.getState=Px.prototype.W;Px.prototype.dispatchChangeEvent=Px.prototype.n;Px.prototype.getRevision=Px.prototype.A;Px.prototype.on=Px.prototype.t;Px.prototype.once=Px.prototype.B;Px.prototype.un=Px.prototype.v;Px.prototype.unByKey=Px.prototype.C;Sx.prototype.getTileGrid=Sx.prototype.wa;Sx.prototype.getAttributions=Sx.prototype.U;Sx.prototype.getLogo=Sx.prototype.S;
-Sx.prototype.getProjection=Sx.prototype.V;Sx.prototype.getState=Sx.prototype.W;Sx.prototype.dispatchChangeEvent=Sx.prototype.n;Sx.prototype.getRevision=Sx.prototype.A;Sx.prototype.on=Sx.prototype.t;Sx.prototype.once=Sx.prototype.B;Sx.prototype.un=Sx.prototype.v;Sx.prototype.unByKey=Sx.prototype.C;Tx.prototype.getTileLoadFunction=Tx.prototype.Ra;Tx.prototype.getTileUrlFunction=Tx.prototype.Sa;Tx.prototype.setTileLoadFunction=Tx.prototype.Ua;Tx.prototype.setTileUrlFunction=Tx.prototype.ha;
-Tx.prototype.getTileGrid=Tx.prototype.wa;Tx.prototype.getAttributions=Tx.prototype.U;Tx.prototype.getLogo=Tx.prototype.S;Tx.prototype.getProjection=Tx.prototype.V;Tx.prototype.getState=Tx.prototype.W;Tx.prototype.dispatchChangeEvent=Tx.prototype.n;Tx.prototype.getRevision=Tx.prototype.A;Tx.prototype.on=Tx.prototype.t;Tx.prototype.once=Tx.prototype.B;Tx.prototype.un=Tx.prototype.v;Tx.prototype.unByKey=Tx.prototype.C;Ux.prototype.readFeatures=Ux.prototype.a;Ux.prototype.getFeaturesAtCoordinate=Ux.prototype.va;
-Ux.prototype.getFeatureById=Ux.prototype.ua;Ux.prototype.getAttributions=Ux.prototype.U;Ux.prototype.getLogo=Ux.prototype.S;Ux.prototype.getProjection=Ux.prototype.V;Ux.prototype.getState=Ux.prototype.W;Ux.prototype.dispatchChangeEvent=Ux.prototype.n;Ux.prototype.getRevision=Ux.prototype.A;Ux.prototype.on=Ux.prototype.t;Ux.prototype.once=Ux.prototype.B;Ux.prototype.un=Ux.prototype.v;Ux.prototype.unByKey=Ux.prototype.C;Vx.prototype.getTileLoadFunction=Vx.prototype.Ra;
-Vx.prototype.getTileUrlFunction=Vx.prototype.Sa;Vx.prototype.setTileLoadFunction=Vx.prototype.Ua;Vx.prototype.setTileUrlFunction=Vx.prototype.ha;Vx.prototype.getTileGrid=Vx.prototype.wa;Vx.prototype.getAttributions=Vx.prototype.U;Vx.prototype.getLogo=Vx.prototype.S;Vx.prototype.getProjection=Vx.prototype.V;Vx.prototype.getState=Vx.prototype.W;Vx.prototype.dispatchChangeEvent=Vx.prototype.n;Vx.prototype.getRevision=Vx.prototype.A;Vx.prototype.on=Vx.prototype.t;Vx.prototype.once=Vx.prototype.B;
-Vx.prototype.un=Vx.prototype.v;Vx.prototype.unByKey=Vx.prototype.C;Zx.prototype.readFeatures=Zx.prototype.a;Zx.prototype.addFeature=Zx.prototype.za;Zx.prototype.addFeatures=Zx.prototype.oa;Zx.prototype.clear=Zx.prototype.clear;Zx.prototype.forEachFeature=Zx.prototype.Ha;Zx.prototype.forEachFeatureInExtent=Zx.prototype.pa;Zx.prototype.getFeatures=Zx.prototype.qa;Zx.prototype.getFeaturesAtCoordinate=Zx.prototype.va;Zx.prototype.getClosestFeatureToCoordinate=Zx.prototype.Ia;Zx.prototype.getExtent=Zx.prototype.s;
-Zx.prototype.getFeatureById=Zx.prototype.ua;Zx.prototype.removeFeature=Zx.prototype.Ma;Zx.prototype.getAttributions=Zx.prototype.U;Zx.prototype.getLogo=Zx.prototype.S;Zx.prototype.getProjection=Zx.prototype.V;Zx.prototype.getState=Zx.prototype.W;Zx.prototype.dispatchChangeEvent=Zx.prototype.n;Zx.prototype.getRevision=Zx.prototype.A;Zx.prototype.on=Zx.prototype.t;Zx.prototype.once=Zx.prototype.B;Zx.prototype.un=Zx.prototype.v;Zx.prototype.unByKey=Zx.prototype.C;ay.prototype.getTileLoadFunction=ay.prototype.Ra;
-ay.prototype.getTileUrlFunction=ay.prototype.Sa;ay.prototype.setTileLoadFunction=ay.prototype.Ua;ay.prototype.setTileUrlFunction=ay.prototype.ha;ay.prototype.getTileGrid=ay.prototype.wa;ay.prototype.getAttributions=ay.prototype.U;ay.prototype.getLogo=ay.prototype.S;ay.prototype.getProjection=ay.prototype.V;ay.prototype.getState=ay.prototype.W;ay.prototype.dispatchChangeEvent=ay.prototype.n;ay.prototype.getRevision=ay.prototype.A;ay.prototype.on=ay.prototype.t;ay.prototype.once=ay.prototype.B;
-ay.prototype.un=ay.prototype.v;ay.prototype.unByKey=ay.prototype.C;dy.prototype.getTileLoadFunction=dy.prototype.Ra;dy.prototype.getTileUrlFunction=dy.prototype.Sa;dy.prototype.setTileLoadFunction=dy.prototype.Ua;dy.prototype.setTileUrlFunction=dy.prototype.ha;dy.prototype.getTileGrid=dy.prototype.wa;dy.prototype.getAttributions=dy.prototype.U;dy.prototype.getLogo=dy.prototype.S;dy.prototype.getProjection=dy.prototype.V;dy.prototype.getState=dy.prototype.W;dy.prototype.dispatchChangeEvent=dy.prototype.n;
-dy.prototype.getRevision=dy.prototype.A;dy.prototype.on=dy.prototype.t;dy.prototype.once=dy.prototype.B;dy.prototype.un=dy.prototype.v;dy.prototype.unByKey=dy.prototype.C;O.prototype.bindTo=O.prototype.Z;O.prototype.get=O.prototype.get;O.prototype.getKeys=O.prototype.N;O.prototype.getProperties=O.prototype.R;O.prototype.set=O.prototype.set;O.prototype.setProperties=O.prototype.L;O.prototype.unbind=O.prototype.Y;O.prototype.unbindAll=O.prototype.$;O.prototype.dispatchChangeEvent=O.prototype.n;
-O.prototype.getRevision=O.prototype.A;O.prototype.on=O.prototype.t;O.prototype.once=O.prototype.B;O.prototype.un=O.prototype.v;O.prototype.unByKey=O.prototype.C;P.prototype.getBrightness=P.prototype.d;P.prototype.getContrast=P.prototype.e;P.prototype.getHue=P.prototype.f;P.prototype.getExtent=P.prototype.s;P.prototype.getMaxResolution=P.prototype.g;P.prototype.getMinResolution=P.prototype.i;P.prototype.getOpacity=P.prototype.k;P.prototype.getSaturation=P.prototype.l;P.prototype.getVisible=P.prototype.b;
-P.prototype.setBrightness=P.prototype.q;P.prototype.setContrast=P.prototype.r;P.prototype.setHue=P.prototype.u;P.prototype.setExtent=P.prototype.o;P.prototype.setMaxResolution=P.prototype.D;P.prototype.setMinResolution=P.prototype.F;P.prototype.setOpacity=P.prototype.Q;P.prototype.setSaturation=P.prototype.X;P.prototype.setVisible=P.prototype.ba;P.prototype.bindTo=P.prototype.Z;P.prototype.get=P.prototype.get;P.prototype.getKeys=P.prototype.N;P.prototype.getProperties=P.prototype.R;
-P.prototype.set=P.prototype.set;P.prototype.setProperties=P.prototype.L;P.prototype.unbind=P.prototype.Y;P.prototype.unbindAll=P.prototype.$;P.prototype.dispatchChangeEvent=P.prototype.n;P.prototype.getRevision=P.prototype.A;P.prototype.on=P.prototype.t;P.prototype.once=P.prototype.B;P.prototype.un=P.prototype.v;P.prototype.unByKey=P.prototype.C;V.prototype.getBrightness=V.prototype.d;V.prototype.getContrast=V.prototype.e;V.prototype.getHue=V.prototype.f;V.prototype.getExtent=V.prototype.s;
-V.prototype.getMaxResolution=V.prototype.g;V.prototype.getMinResolution=V.prototype.i;V.prototype.getOpacity=V.prototype.k;V.prototype.getSaturation=V.prototype.l;V.prototype.getVisible=V.prototype.b;V.prototype.setBrightness=V.prototype.q;V.prototype.setContrast=V.prototype.r;V.prototype.setHue=V.prototype.u;V.prototype.setExtent=V.prototype.o;V.prototype.setMaxResolution=V.prototype.D;V.prototype.setMinResolution=V.prototype.F;V.prototype.setOpacity=V.prototype.Q;V.prototype.setSaturation=V.prototype.X;
-V.prototype.setVisible=V.prototype.ba;V.prototype.bindTo=V.prototype.Z;V.prototype.get=V.prototype.get;V.prototype.getKeys=V.prototype.N;V.prototype.getProperties=V.prototype.R;V.prototype.set=V.prototype.set;V.prototype.setProperties=V.prototype.L;V.prototype.unbind=V.prototype.Y;V.prototype.unbindAll=V.prototype.$;V.prototype.dispatchChangeEvent=V.prototype.n;V.prototype.getRevision=V.prototype.A;V.prototype.on=V.prototype.t;V.prototype.once=V.prototype.B;V.prototype.un=V.prototype.v;
-V.prototype.unByKey=V.prototype.C;X.prototype.getSource=X.prototype.ma;X.prototype.getStyle=X.prototype.ac;X.prototype.getStyleFunction=X.prototype.Cc;X.prototype.setStyle=X.prototype.na;X.prototype.getBrightness=X.prototype.d;X.prototype.getContrast=X.prototype.e;X.prototype.getHue=X.prototype.f;X.prototype.getExtent=X.prototype.s;X.prototype.getMaxResolution=X.prototype.g;X.prototype.getMinResolution=X.prototype.i;X.prototype.getOpacity=X.prototype.k;X.prototype.getSaturation=X.prototype.l;
-X.prototype.getVisible=X.prototype.b;X.prototype.setBrightness=X.prototype.q;X.prototype.setContrast=X.prototype.r;X.prototype.setHue=X.prototype.u;X.prototype.setExtent=X.prototype.o;X.prototype.setMaxResolution=X.prototype.D;X.prototype.setMinResolution=X.prototype.F;X.prototype.setOpacity=X.prototype.Q;X.prototype.setSaturation=X.prototype.X;X.prototype.setVisible=X.prototype.ba;X.prototype.bindTo=X.prototype.Z;X.prototype.get=X.prototype.get;X.prototype.getKeys=X.prototype.N;
-X.prototype.getProperties=X.prototype.R;X.prototype.set=X.prototype.set;X.prototype.setProperties=X.prototype.L;X.prototype.unbind=X.prototype.Y;X.prototype.unbindAll=X.prototype.$;X.prototype.dispatchChangeEvent=X.prototype.n;X.prototype.getRevision=X.prototype.A;X.prototype.on=X.prototype.t;X.prototype.once=X.prototype.B;X.prototype.un=X.prototype.v;X.prototype.unByKey=X.prototype.C;S.prototype.getBrightness=S.prototype.d;S.prototype.getContrast=S.prototype.e;S.prototype.getHue=S.prototype.f;
-S.prototype.getExtent=S.prototype.s;S.prototype.getMaxResolution=S.prototype.g;S.prototype.getMinResolution=S.prototype.i;S.prototype.getOpacity=S.prototype.k;S.prototype.getSaturation=S.prototype.l;S.prototype.getVisible=S.prototype.b;S.prototype.setBrightness=S.prototype.q;S.prototype.setContrast=S.prototype.r;S.prototype.setHue=S.prototype.u;S.prototype.setExtent=S.prototype.o;S.prototype.setMaxResolution=S.prototype.D;S.prototype.setMinResolution=S.prototype.F;S.prototype.setOpacity=S.prototype.Q;
-S.prototype.setSaturation=S.prototype.X;S.prototype.setVisible=S.prototype.ba;S.prototype.bindTo=S.prototype.Z;S.prototype.get=S.prototype.get;S.prototype.getKeys=S.prototype.N;S.prototype.getProperties=S.prototype.R;S.prototype.set=S.prototype.set;S.prototype.setProperties=S.prototype.L;S.prototype.unbind=S.prototype.Y;S.prototype.unbindAll=S.prototype.$;S.prototype.dispatchChangeEvent=S.prototype.n;S.prototype.getRevision=S.prototype.A;S.prototype.on=S.prototype.t;S.prototype.once=S.prototype.B;
-S.prototype.un=S.prototype.v;S.prototype.unByKey=S.prototype.C;R.prototype.getBrightness=R.prototype.d;R.prototype.getContrast=R.prototype.e;R.prototype.getHue=R.prototype.f;R.prototype.getExtent=R.prototype.s;R.prototype.getMaxResolution=R.prototype.g;R.prototype.getMinResolution=R.prototype.i;R.prototype.getOpacity=R.prototype.k;R.prototype.getSaturation=R.prototype.l;R.prototype.getVisible=R.prototype.b;R.prototype.setBrightness=R.prototype.q;R.prototype.setContrast=R.prototype.r;
-R.prototype.setHue=R.prototype.u;R.prototype.setExtent=R.prototype.o;R.prototype.setMaxResolution=R.prototype.D;R.prototype.setMinResolution=R.prototype.F;R.prototype.setOpacity=R.prototype.Q;R.prototype.setSaturation=R.prototype.X;R.prototype.setVisible=R.prototype.ba;R.prototype.bindTo=R.prototype.Z;R.prototype.get=R.prototype.get;R.prototype.getKeys=R.prototype.N;R.prototype.getProperties=R.prototype.R;R.prototype.set=R.prototype.set;R.prototype.setProperties=R.prototype.L;R.prototype.unbind=R.prototype.Y;
-R.prototype.unbindAll=R.prototype.$;R.prototype.dispatchChangeEvent=R.prototype.n;R.prototype.getRevision=R.prototype.A;R.prototype.on=R.prototype.t;R.prototype.once=R.prototype.B;R.prototype.un=R.prototype.v;R.prototype.unByKey=R.prototype.C;T.prototype.getBrightness=T.prototype.d;T.prototype.getContrast=T.prototype.e;T.prototype.getHue=T.prototype.f;T.prototype.getExtent=T.prototype.s;T.prototype.getMaxResolution=T.prototype.g;T.prototype.getMinResolution=T.prototype.i;T.prototype.getOpacity=T.prototype.k;
-T.prototype.getSaturation=T.prototype.l;T.prototype.getVisible=T.prototype.b;T.prototype.setBrightness=T.prototype.q;T.prototype.setContrast=T.prototype.r;T.prototype.setHue=T.prototype.u;T.prototype.setExtent=T.prototype.o;T.prototype.setMaxResolution=T.prototype.D;T.prototype.setMinResolution=T.prototype.F;T.prototype.setOpacity=T.prototype.Q;T.prototype.setSaturation=T.prototype.X;T.prototype.setVisible=T.prototype.ba;T.prototype.bindTo=T.prototype.Z;T.prototype.get=T.prototype.get;
-T.prototype.getKeys=T.prototype.N;T.prototype.getProperties=T.prototype.R;T.prototype.set=T.prototype.set;T.prototype.setProperties=T.prototype.L;T.prototype.unbind=T.prototype.Y;T.prototype.unbindAll=T.prototype.$;T.prototype.dispatchChangeEvent=T.prototype.n;T.prototype.getRevision=T.prototype.A;T.prototype.on=T.prototype.t;T.prototype.once=T.prototype.B;T.prototype.un=T.prototype.v;T.prototype.unByKey=T.prototype.C;jr.prototype.dispatchChangeEvent=jr.prototype.n;jr.prototype.getRevision=jr.prototype.A;
-jr.prototype.on=jr.prototype.t;jr.prototype.once=jr.prototype.B;jr.prototype.un=jr.prototype.v;jr.prototype.unByKey=jr.prototype.C;nr.prototype.dispatchChangeEvent=nr.prototype.n;nr.prototype.getRevision=nr.prototype.A;nr.prototype.on=nr.prototype.t;nr.prototype.once=nr.prototype.B;nr.prototype.un=nr.prototype.v;nr.prototype.unByKey=nr.prototype.C;fs.prototype.dispatchChangeEvent=fs.prototype.n;fs.prototype.getRevision=fs.prototype.A;fs.prototype.on=fs.prototype.t;fs.prototype.once=fs.prototype.B;
-fs.prototype.un=fs.prototype.v;fs.prototype.unByKey=fs.prototype.C;is.prototype.dispatchChangeEvent=is.prototype.n;is.prototype.getRevision=is.prototype.A;is.prototype.on=is.prototype.t;is.prototype.once=is.prototype.B;is.prototype.un=is.prototype.v;is.prototype.unByKey=is.prototype.C;os.prototype.dispatchChangeEvent=os.prototype.n;os.prototype.getRevision=os.prototype.A;os.prototype.on=os.prototype.t;os.prototype.once=os.prototype.B;os.prototype.un=os.prototype.v;os.prototype.unByKey=os.prototype.C;
-ss.prototype.dispatchChangeEvent=ss.prototype.n;ss.prototype.getRevision=ss.prototype.A;ss.prototype.on=ss.prototype.t;ss.prototype.once=ss.prototype.B;ss.prototype.un=ss.prototype.v;ss.prototype.unByKey=ss.prototype.C;us.prototype.dispatchChangeEvent=us.prototype.n;us.prototype.getRevision=us.prototype.A;us.prototype.on=us.prototype.t;us.prototype.once=us.prototype.B;us.prototype.un=us.prototype.v;us.prototype.unByKey=us.prototype.C;vs.prototype.dispatchChangeEvent=vs.prototype.n;
-vs.prototype.getRevision=vs.prototype.A;vs.prototype.on=vs.prototype.t;vs.prototype.once=vs.prototype.B;vs.prototype.un=vs.prototype.v;vs.prototype.unByKey=vs.prototype.C;ws.prototype.getGeometry=ws.prototype.J;ws.prototype.dispatchChangeEvent=ws.prototype.n;ws.prototype.getRevision=ws.prototype.A;ws.prototype.on=ws.prototype.t;ws.prototype.once=ws.prototype.B;ws.prototype.un=ws.prototype.v;ws.prototype.unByKey=ws.prototype.C;Sv.prototype.dispatchChangeEvent=Sv.prototype.n;
-Sv.prototype.getRevision=Sv.prototype.A;Sv.prototype.on=Sv.prototype.t;Sv.prototype.once=Sv.prototype.B;Sv.prototype.un=Sv.prototype.v;Sv.prototype.unByKey=Sv.prototype.C;Ft.prototype.dispatchChangeEvent=Ft.prototype.n;Ft.prototype.getRevision=Ft.prototype.A;Ft.prototype.on=Ft.prototype.t;Ft.prototype.once=Ft.prototype.B;Ft.prototype.un=Ft.prototype.v;Ft.prototype.unByKey=Ft.prototype.C;Gt.prototype.dispatchChangeEvent=Gt.prototype.n;Gt.prototype.getRevision=Gt.prototype.A;Gt.prototype.on=Gt.prototype.t;
-Gt.prototype.once=Gt.prototype.B;Gt.prototype.un=Gt.prototype.v;Gt.prototype.unByKey=Gt.prototype.C;bw.prototype.dispatchChangeEvent=bw.prototype.n;bw.prototype.getRevision=bw.prototype.A;bw.prototype.on=bw.prototype.t;bw.prototype.once=bw.prototype.B;bw.prototype.un=bw.prototype.v;bw.prototype.unByKey=bw.prototype.C;Ht.prototype.dispatchChangeEvent=Ht.prototype.n;Ht.prototype.getRevision=Ht.prototype.A;Ht.prototype.on=Ht.prototype.t;Ht.prototype.once=Ht.prototype.B;Ht.prototype.un=Ht.prototype.v;
-Ht.prototype.unByKey=Ht.prototype.C;It.prototype.dispatchChangeEvent=It.prototype.n;It.prototype.getRevision=It.prototype.A;It.prototype.on=It.prototype.t;It.prototype.once=It.prototype.B;It.prototype.un=It.prototype.v;It.prototype.unByKey=It.prototype.C;Jt.prototype.dispatchChangeEvent=Jt.prototype.n;Jt.prototype.getRevision=Jt.prototype.A;Jt.prototype.on=Jt.prototype.t;Jt.prototype.once=Jt.prototype.B;Jt.prototype.un=Jt.prototype.v;Jt.prototype.unByKey=Jt.prototype.C;
-gw.prototype.dispatchChangeEvent=gw.prototype.n;gw.prototype.getRevision=gw.prototype.A;gw.prototype.on=gw.prototype.t;gw.prototype.once=gw.prototype.B;gw.prototype.un=gw.prototype.v;gw.prototype.unByKey=gw.prototype.C;Di.prototype.dispatchChangeEvent=Di.prototype.n;Di.prototype.getRevision=Di.prototype.A;Di.prototype.on=Di.prototype.t;Di.prototype.once=Di.prototype.B;Di.prototype.un=Di.prototype.v;Di.prototype.unByKey=Di.prototype.C;Ri.prototype.clone=Ri.prototype.clone;
-Ri.prototype.getClosestPoint=Ri.prototype.g;Ri.prototype.getType=Ri.prototype.G;Ri.prototype.transform=Ri.prototype.f;Ri.prototype.dispatchChangeEvent=Ri.prototype.n;Ri.prototype.getRevision=Ri.prototype.A;Ri.prototype.on=Ri.prototype.t;Ri.prototype.once=Ri.prototype.B;Ri.prototype.un=Ri.prototype.v;Ri.prototype.unByKey=Ri.prototype.C;Zi.prototype.getFirstCoordinate=Zi.prototype.Xa;Zi.prototype.getLastCoordinate=Zi.prototype.Ya;Zi.prototype.getLayout=Zi.prototype.Za;Zi.prototype.getClosestPoint=Zi.prototype.g;
-Zi.prototype.transform=Zi.prototype.f;Zi.prototype.dispatchChangeEvent=Zi.prototype.n;Zi.prototype.getRevision=Zi.prototype.A;Zi.prototype.on=Zi.prototype.t;Zi.prototype.once=Zi.prototype.B;Zi.prototype.un=Zi.prototype.v;Zi.prototype.unByKey=Zi.prototype.C;aj.prototype.getClosestPoint=aj.prototype.g;aj.prototype.transform=aj.prototype.f;aj.prototype.dispatchChangeEvent=aj.prototype.n;aj.prototype.getRevision=aj.prototype.A;aj.prototype.on=aj.prototype.t;aj.prototype.once=aj.prototype.B;
-aj.prototype.un=aj.prototype.v;aj.prototype.unByKey=aj.prototype.C;Dj.prototype.getExtent=Dj.prototype.s;Dj.prototype.getFirstCoordinate=Dj.prototype.Xa;Dj.prototype.getLastCoordinate=Dj.prototype.Ya;Dj.prototype.getLayout=Dj.prototype.Za;Dj.prototype.getClosestPoint=Dj.prototype.g;Dj.prototype.transform=Dj.prototype.f;Dj.prototype.dispatchChangeEvent=Dj.prototype.n;Dj.prototype.getRevision=Dj.prototype.A;Dj.prototype.on=Dj.prototype.t;Dj.prototype.once=Dj.prototype.B;Dj.prototype.un=Dj.prototype.v;
-Dj.prototype.unByKey=Dj.prototype.C;H.prototype.getExtent=H.prototype.s;H.prototype.getFirstCoordinate=H.prototype.Xa;H.prototype.getLastCoordinate=H.prototype.Ya;H.prototype.getLayout=H.prototype.Za;H.prototype.getClosestPoint=H.prototype.g;H.prototype.transform=H.prototype.f;H.prototype.dispatchChangeEvent=H.prototype.n;H.prototype.getRevision=H.prototype.A;H.prototype.on=H.prototype.t;H.prototype.once=H.prototype.B;H.prototype.un=H.prototype.v;H.prototype.unByKey=H.prototype.C;
-uj.prototype.getExtent=uj.prototype.s;uj.prototype.getFirstCoordinate=uj.prototype.Xa;uj.prototype.getLastCoordinate=uj.prototype.Ya;uj.prototype.getLayout=uj.prototype.Za;uj.prototype.getClosestPoint=uj.prototype.g;uj.prototype.transform=uj.prototype.f;uj.prototype.dispatchChangeEvent=uj.prototype.n;uj.prototype.getRevision=uj.prototype.A;uj.prototype.on=uj.prototype.t;uj.prototype.once=uj.prototype.B;uj.prototype.un=uj.prototype.v;uj.prototype.unByKey=uj.prototype.C;Aj.prototype.getExtent=Aj.prototype.s;
-Aj.prototype.getFirstCoordinate=Aj.prototype.Xa;Aj.prototype.getLastCoordinate=Aj.prototype.Ya;Aj.prototype.getLayout=Aj.prototype.Za;Aj.prototype.getClosestPoint=Aj.prototype.g;Aj.prototype.transform=Aj.prototype.f;Aj.prototype.dispatchChangeEvent=Aj.prototype.n;Aj.prototype.getRevision=Aj.prototype.A;Aj.prototype.on=Aj.prototype.t;Aj.prototype.once=Aj.prototype.B;Aj.prototype.un=Aj.prototype.v;Aj.prototype.unByKey=Aj.prototype.C;Pj.prototype.getExtent=Pj.prototype.s;
-Pj.prototype.getFirstCoordinate=Pj.prototype.Xa;Pj.prototype.getLastCoordinate=Pj.prototype.Ya;Pj.prototype.getLayout=Pj.prototype.Za;Pj.prototype.getClosestPoint=Pj.prototype.g;Pj.prototype.transform=Pj.prototype.f;Pj.prototype.dispatchChangeEvent=Pj.prototype.n;Pj.prototype.getRevision=Pj.prototype.A;Pj.prototype.on=Pj.prototype.t;Pj.prototype.once=Pj.prototype.B;Pj.prototype.un=Pj.prototype.v;Pj.prototype.unByKey=Pj.prototype.C;yj.prototype.getFirstCoordinate=yj.prototype.Xa;
-yj.prototype.getLastCoordinate=yj.prototype.Ya;yj.prototype.getLayout=yj.prototype.Za;yj.prototype.getClosestPoint=yj.prototype.g;yj.prototype.transform=yj.prototype.f;yj.prototype.dispatchChangeEvent=yj.prototype.n;yj.prototype.getRevision=yj.prototype.A;yj.prototype.on=yj.prototype.t;yj.prototype.once=yj.prototype.B;yj.prototype.un=yj.prototype.v;yj.prototype.unByKey=yj.prototype.C;I.prototype.getExtent=I.prototype.s;I.prototype.getFirstCoordinate=I.prototype.Xa;I.prototype.getLastCoordinate=I.prototype.Ya;
-I.prototype.getLayout=I.prototype.Za;I.prototype.getClosestPoint=I.prototype.g;I.prototype.transform=I.prototype.f;I.prototype.dispatchChangeEvent=I.prototype.n;I.prototype.getRevision=I.prototype.A;I.prototype.on=I.prototype.t;I.prototype.once=I.prototype.B;I.prototype.un=I.prototype.v;I.prototype.unByKey=I.prototype.C;ii.prototype.bindTo=ii.prototype.Z;ii.prototype.get=ii.prototype.get;ii.prototype.getKeys=ii.prototype.N;ii.prototype.getProperties=ii.prototype.R;ii.prototype.set=ii.prototype.set;
-ii.prototype.setProperties=ii.prototype.L;ii.prototype.unbind=ii.prototype.Y;ii.prototype.unbindAll=ii.prototype.$;ii.prototype.dispatchChangeEvent=ii.prototype.n;ii.prototype.getRevision=ii.prototype.A;ii.prototype.on=ii.prototype.t;ii.prototype.once=ii.prototype.B;ii.prototype.un=ii.prototype.v;ii.prototype.unByKey=ii.prototype.C;Sg.prototype.bindTo=Sg.prototype.Z;Sg.prototype.get=Sg.prototype.get;Sg.prototype.getKeys=Sg.prototype.N;Sg.prototype.getProperties=Sg.prototype.R;Sg.prototype.set=Sg.prototype.set;
-Sg.prototype.setProperties=Sg.prototype.L;Sg.prototype.unbind=Sg.prototype.Y;Sg.prototype.unbindAll=Sg.prototype.$;Sg.prototype.dispatchChangeEvent=Sg.prototype.n;Sg.prototype.getRevision=Sg.prototype.A;Sg.prototype.on=Sg.prototype.t;Sg.prototype.once=Sg.prototype.B;Sg.prototype.un=Sg.prototype.v;Sg.prototype.unByKey=Sg.prototype.C;Bh.prototype.getMap=Bh.prototype.d;Bh.prototype.setMap=Bh.prototype.setMap;Bh.prototype.bindTo=Bh.prototype.Z;Bh.prototype.get=Bh.prototype.get;Bh.prototype.getKeys=Bh.prototype.N;
-Bh.prototype.getProperties=Bh.prototype.R;Bh.prototype.set=Bh.prototype.set;Bh.prototype.setProperties=Bh.prototype.L;Bh.prototype.unbind=Bh.prototype.Y;Bh.prototype.unbindAll=Bh.prototype.$;Bh.prototype.dispatchChangeEvent=Bh.prototype.n;Bh.prototype.getRevision=Bh.prototype.A;Bh.prototype.on=Bh.prototype.t;Bh.prototype.once=Bh.prototype.B;Bh.prototype.un=Bh.prototype.v;Bh.prototype.unByKey=Bh.prototype.C;Mh.prototype.getMap=Mh.prototype.d;Mh.prototype.setMap=Mh.prototype.setMap;
-Mh.prototype.bindTo=Mh.prototype.Z;Mh.prototype.get=Mh.prototype.get;Mh.prototype.getKeys=Mh.prototype.N;Mh.prototype.getProperties=Mh.prototype.R;Mh.prototype.set=Mh.prototype.set;Mh.prototype.setProperties=Mh.prototype.L;Mh.prototype.unbind=Mh.prototype.Y;Mh.prototype.unbindAll=Mh.prototype.$;Mh.prototype.dispatchChangeEvent=Mh.prototype.n;Mh.prototype.getRevision=Mh.prototype.A;Mh.prototype.on=Mh.prototype.t;Mh.prototype.once=Mh.prototype.B;Mh.prototype.un=Mh.prototype.v;Mh.prototype.unByKey=Mh.prototype.C;
-C.prototype.getMap=C.prototype.d;C.prototype.bindTo=C.prototype.Z;C.prototype.get=C.prototype.get;C.prototype.getKeys=C.prototype.N;C.prototype.getProperties=C.prototype.R;C.prototype.set=C.prototype.set;C.prototype.setProperties=C.prototype.L;C.prototype.unbind=C.prototype.Y;C.prototype.unbindAll=C.prototype.$;C.prototype.dispatchChangeEvent=C.prototype.n;C.prototype.getRevision=C.prototype.A;C.prototype.on=C.prototype.t;C.prototype.once=C.prototype.B;C.prototype.un=C.prototype.v;
-C.prototype.unByKey=C.prototype.C;Dh.prototype.getMap=Dh.prototype.d;Dh.prototype.setMap=Dh.prototype.setMap;Dh.prototype.bindTo=Dh.prototype.Z;Dh.prototype.get=Dh.prototype.get;Dh.prototype.getKeys=Dh.prototype.N;Dh.prototype.getProperties=Dh.prototype.R;Dh.prototype.set=Dh.prototype.set;Dh.prototype.setProperties=Dh.prototype.L;Dh.prototype.unbind=Dh.prototype.Y;Dh.prototype.unbindAll=Dh.prototype.$;Dh.prototype.dispatchChangeEvent=Dh.prototype.n;Dh.prototype.getRevision=Dh.prototype.A;
-Dh.prototype.on=Dh.prototype.t;Dh.prototype.once=Dh.prototype.B;Dh.prototype.un=Dh.prototype.v;Dh.prototype.unByKey=Dh.prototype.C;Ph.prototype.getMap=Ph.prototype.d;Ph.prototype.setMap=Ph.prototype.setMap;Ph.prototype.bindTo=Ph.prototype.Z;Ph.prototype.get=Ph.prototype.get;Ph.prototype.getKeys=Ph.prototype.N;Ph.prototype.getProperties=Ph.prototype.R;Ph.prototype.set=Ph.prototype.set;Ph.prototype.setProperties=Ph.prototype.L;Ph.prototype.unbind=Ph.prototype.Y;Ph.prototype.unbindAll=Ph.prototype.$;
-Ph.prototype.dispatchChangeEvent=Ph.prototype.n;Ph.prototype.getRevision=Ph.prototype.A;Ph.prototype.on=Ph.prototype.t;Ph.prototype.once=Ph.prototype.B;Ph.prototype.un=Ph.prototype.v;Ph.prototype.unByKey=Ph.prototype.C;Fh.prototype.getMap=Fh.prototype.d;Fh.prototype.setMap=Fh.prototype.setMap;Fh.prototype.bindTo=Fh.prototype.Z;Fh.prototype.get=Fh.prototype.get;Fh.prototype.getKeys=Fh.prototype.N;Fh.prototype.getProperties=Fh.prototype.R;Fh.prototype.set=Fh.prototype.set;
-Fh.prototype.setProperties=Fh.prototype.L;Fh.prototype.unbind=Fh.prototype.Y;Fh.prototype.unbindAll=Fh.prototype.$;Fh.prototype.dispatchChangeEvent=Fh.prototype.n;Fh.prototype.getRevision=Fh.prototype.A;Fh.prototype.on=Fh.prototype.t;Fh.prototype.once=Fh.prototype.B;Fh.prototype.un=Fh.prototype.v;Fh.prototype.unByKey=Fh.prototype.C;ci.prototype.getMap=ci.prototype.d;ci.prototype.bindTo=ci.prototype.Z;ci.prototype.get=ci.prototype.get;ci.prototype.getKeys=ci.prototype.N;
-ci.prototype.getProperties=ci.prototype.R;ci.prototype.set=ci.prototype.set;ci.prototype.setProperties=ci.prototype.L;ci.prototype.unbind=ci.prototype.Y;ci.prototype.unbindAll=ci.prototype.$;ci.prototype.dispatchChangeEvent=ci.prototype.n;ci.prototype.getRevision=ci.prototype.A;ci.prototype.on=ci.prototype.t;ci.prototype.once=ci.prototype.B;ci.prototype.un=ci.prototype.v;ci.prototype.unByKey=ci.prototype.C;gi.prototype.getMap=gi.prototype.d;gi.prototype.setMap=gi.prototype.setMap;
-gi.prototype.bindTo=gi.prototype.Z;gi.prototype.get=gi.prototype.get;gi.prototype.getKeys=gi.prototype.N;gi.prototype.getProperties=gi.prototype.R;gi.prototype.set=gi.prototype.set;gi.prototype.setProperties=gi.prototype.L;gi.prototype.unbind=gi.prototype.Y;gi.prototype.unbindAll=gi.prototype.$;gi.prototype.dispatchChangeEvent=gi.prototype.n;gi.prototype.getRevision=gi.prototype.A;gi.prototype.on=gi.prototype.t;gi.prototype.once=gi.prototype.B;gi.prototype.un=gi.prototype.v;gi.prototype.unByKey=gi.prototype.C;})();
+function yv(b,c){this.e=[];this.p=b;this.j=c||null;this.d=this.a=!1;this.b=void 0;this.i=this.q=this.g=!1;this.f=0;this.c=null;this.o=0}yv.prototype.cancel=function(b){if(this.a)this.b instanceof yv&&this.b.cancel();else{if(this.c){var c=this.c;delete this.c;b?c.cancel(b):(c.o--,0>=c.o&&c.cancel())}this.p?this.p.call(this.j,this):this.i=!0;this.a||(b=new zv,Av(this),Bv(this,!1,b))}};yv.prototype.n=function(b,c){this.g=!1;Bv(this,b,c)};function Bv(b,c,d){b.a=!0;b.b=d;b.d=!c;Cv(b)}
+function Av(b){if(b.a){if(!b.i)throw new Dv;b.i=!1}}function Ev(b,c,d,e){b.e.push([c,d,e]);b.a&&Cv(b)}yv.prototype.then=function(b,c,d){var e,f,g=new kv(function(b,c){e=b;f=c});Ev(this,e,function(b){b instanceof zv?g.cancel():f(b)});return g.then(b,c,d)};bv(yv);function Fv(b){return Wa(b.e,function(b){return ka(b[1])})}
+function Cv(b){if(b.f&&b.a&&Fv(b)){var c=b.f,d=Gv[c];d&&(ba.clearTimeout(d.X),delete Gv[c]);b.f=0}b.c&&(b.c.o--,delete b.c);for(var c=b.b,e=d=!1;b.e.length&&!b.g;){var f=b.e.shift(),g=f[0],h=f[1],f=f[2];if(g=b.d?h:g)try{var k=g.call(f||b.j,c);m(k)&&(b.d=b.d&&(k==c||k instanceof Error),b.b=c=k);cv(c)&&(e=!0,b.g=!0)}catch(n){c=n,b.d=!0,Fv(b)||(d=!0)}}b.b=c;e&&(k=sa(b.n,b,!0),e=sa(b.n,b,!1),c instanceof yv?(Ev(c,k,e),c.q=!0):c.then(k,e));d&&(c=new Hv(c),Gv[c.X]=c,b.f=c.X)}
+function Dv(){xa.call(this)}u(Dv,xa);Dv.prototype.message="Deferred has already fired";Dv.prototype.name="AlreadyCalledError";function zv(){xa.call(this)}u(zv,xa);zv.prototype.message="Deferred was canceled";zv.prototype.name="CanceledError";function Hv(b){this.X=ba.setTimeout(sa(this.c,this),0);this.a=b}Hv.prototype.c=function(){delete Gv[this.X];throw this.a;};var Gv={};function Iv(b,c){m(b.name)?(this.name=b.name,this.code=wb[b.name]):(this.code=b.code,this.name=Jv(b.code));xa.call(this,za("%s %s",this.name,c))}u(Iv,xa);function Jv(b){var c=ub(function(c){return b==c});if(!m(c))throw Error("Invalid code: "+b);return c}var wb={AbortError:3,EncodingError:5,InvalidModificationError:9,InvalidStateError:7,NotFoundError:1,NotReadableError:4,NoModificationAllowedError:6,PathExistsError:12,QuotaExceededError:10,SecurityError:2,SyntaxError:8,TypeMismatchError:11};function Kv(b,c){uc.call(this,b.type,c)}u(Kv,uc);function Lv(){jd.call(this);this.bb=new FileReader;this.bb.onloadstart=sa(this.a,this);this.bb.onprogress=sa(this.a,this);this.bb.onload=sa(this.a,this);this.bb.onabort=sa(this.a,this);this.bb.onerror=sa(this.a,this);this.bb.onloadend=sa(this.a,this)}u(Lv,jd);Lv.prototype.getError=function(){return this.bb.error&&new Iv(this.bb.error,"reading file")};Lv.prototype.a=function(b){this.dispatchEvent(new Kv(b,this))};Lv.prototype.M=function(){Lv.S.M.call(this);delete this.bb};
+function Mv(b){var c=new yv;b.La("loadend",ta(function(b,c){var f=c.bb.result,g=c.getError();null==f||g?(Av(b),Bv(b,!1,g)):(Av(b),Bv(b,!0,f));c.hc()},c,b));return c};function Nv(b){b=m(b)?b:{};ok.call(this,{handleEvent:dd});this.f=m(b.formatConstructors)?b.formatConstructors:[];this.j=m(b.projection)?Ee(b.projection):null;this.e=null;this.d=void 0}u(Nv,ok);Nv.prototype.M=function(){m(this.d)&&Yc(this.d);Nv.S.M.call(this)};Nv.prototype.g=function(b){b=b.a.dataTransfer.files;var c,d,e;c=0;for(d=b.length;c<d;++c){var f=e=b[c],g=new Lv,h=Mv(g);g.bb.readAsText(f,"");Ev(h,ta(this.i,e),null,this)}};
+Nv.prototype.i=function(b,c){var d=this.n,e=this.j;null===e&&(e=d.a().q);var d=this.f,f=[],g,h;g=0;for(h=d.length;g<h;++g){var k=new d[g],n;try{n=k.ja(c)}catch(p){n=null}if(null!==n){var k=k.Ba(c),k=Ve(k,e),q,r;q=0;for(r=n.length;q<r;++q){var s=n[q],v=s.N();null!=v&&v.ma(k);f.push(s)}}}this.dispatchEvent(new Ov(Pv,this,b,f,e))};
+Nv.prototype.setMap=function(b){m(this.d)&&(Yc(this.d),this.d=void 0);null!==this.e&&(tc(this.e),this.e=null);Nv.S.setMap.call(this,b);null!==b&&(this.e=new av(b.b),this.d=z(this.e,"drop",this.g,!1,this))};var Pv="addfeatures";function Ov(b,c,d,e,f){uc.call(this,b,c);this.features=e;this.file=d;this.projection=f}u(Ov,uc);function Qv(b,c){this.x=b;this.y=c}u(Qv,vf);Qv.prototype.clone=function(){return new Qv(this.x,this.y)};Qv.prototype.scale=vf.prototype.scale;Qv.prototype.add=function(b){this.x+=b.x;this.y+=b.y;return this};Qv.prototype.rotate=function(b){var c=Math.cos(b);b=Math.sin(b);var d=this.y*c+this.x*b;this.x=this.x*c-this.y*b;this.y=d;return this};function Rv(b){b=m(b)?b:{};Bk.call(this,{handleDownEvent:Sv,handleDragEvent:Tv,handleUpEvent:Uv});this.i=m(b.condition)?b.condition:yk;this.d=this.f=void 0;this.g=0}u(Rv,Bk);function Tv(b){if(Ak(b)){var c=b.map,d=c.e();b=b.pixel;b=new Qv(b[0]-d[0]/2,d[1]/2-b[1]);d=Math.atan2(b.y,b.x);b=Math.sqrt(b.x*b.x+b.y*b.y);var e=c.a(),f=$e(e);c.render();m(this.f)&&pk(c,e,f.rotation-(d-this.f));this.f=d;m(this.d)&&rk(c,e,f.resolution/b*this.d);m(this.d)&&(this.g=this.d/b);this.d=b}}
+function Uv(b){if(!Ak(b))return!0;b=b.map;var c=b.a();bf(c,-1);var d=$e(c),e=this.g-1,f=d.rotation,f=c.constrainRotation(f,0);pk(b,c,f,void 0,void 0);d=d.resolution;d=c.constrainResolution(d,0,e);rk(b,c,d,void 0,400);this.g=0;return!1}function Sv(b){return Ak(b)&&this.i(b)?(bf(b.map.a(),1),this.d=this.f=void 0,!0):!1};var Vv;
+(function(){var b={Ze:{}};(function(){function c(b,d){if(!(this instanceof c))return new c(b,d);this.$d=Math.max(4,b||9);this.Qe=Math.max(2,Math.ceil(.4*this.$d));d&&this.Og(d);this.clear()}function d(b,c){b.bbox=e(b,0,b.children.length,c)}function e(b,c,d,e){for(var g=[Infinity,Infinity,-Infinity,-Infinity],h;c<d;c++)h=b.children[c],f(g,b.ua?e(h):h.bbox);return g}function f(b,c){b[0]=Math.min(b[0],c[0]);b[1]=Math.min(b[1],c[1]);b[2]=Math.max(b[2],c[2]);b[3]=Math.max(b[3],c[3])}function g(b,c){return b.bbox[0]-
+c.bbox[0]}function h(b,c){return b.bbox[1]-c.bbox[1]}function k(b){return(b[2]-b[0])*(b[3]-b[1])}function n(b){return b[2]-b[0]+(b[3]-b[1])}function p(b,c){return b[0]<=c[0]&&b[1]<=c[1]&&c[2]<=b[2]&&c[3]<=b[3]}function q(b,c){return c[0]<=b[2]&&c[1]<=b[3]&&c[2]>=b[0]&&c[3]>=b[1]}function r(b,c,d,e,f){for(var g=[c,d],h;g.length;)d=g.pop(),c=g.pop(),d-c<=e||(h=c+Math.ceil((d-c)/e/2)*e,s(b,c,d,h,f),g.push(c,h,h,d))}function s(b,c,d,e,f){for(var g,h,k,n,p;d>c;){600<d-c&&(g=d-c+1,h=e-c+1,k=Math.log(g),
+n=.5*Math.exp(2*k/3),p=.5*Math.sqrt(k*n*(g-n)/g)*(0>h-g/2?-1:1),k=Math.max(c,Math.floor(e-h*n/g+p)),h=Math.min(d,Math.floor(e+(g-h)*n/g+p)),s(b,k,h,e,f));g=b[e];h=c;n=d;v(b,c,e);for(0<f(b[d],g)&&v(b,c,d);h<n;){v(b,h,n);h++;for(n--;0>f(b[h],g);)h++;for(;0<f(b[n],g);)n--}0===f(b[c],g)?v(b,c,n):(n++,v(b,n,d));n<=e&&(c=n+1);e<=n&&(d=n-1)}}function v(b,c,d){var e=b[c];b[c]=b[d];b[d]=e}c.prototype={all:function(){return this.Me(this.data,[])},search:function(b){var c=this.data,d=[],e=this.Ca;if(!q(b,c.bbox))return d;
+for(var f=[],g,h,k,n;c;){g=0;for(h=c.children.length;g<h;g++)k=c.children[g],n=c.ua?e(k):k.bbox,q(b,n)&&(c.ua?d.push(k):p(b,n)?this.Me(k,d):f.push(k));c=f.pop()}return d},load:function(b){if(!b||!b.length)return this;if(b.length<this.Qe){for(var c=0,d=b.length;c<d;c++)this.na(b[c]);return this}b=this.Oe(b.slice(),0,b.length-1,0);this.data.children.length?this.data.height===b.height?this.Re(this.data,b):(this.data.height<b.height&&(c=this.data,this.data=b,b=c),this.Pe(b,this.data.height-b.height-1,
+!0)):this.data=b;return this},na:function(b){b&&this.Pe(b,this.data.height-1);return this},clear:function(){this.data={children:[],height:1,bbox:[Infinity,Infinity,-Infinity,-Infinity],ua:!0};return this},remove:function(b){if(!b)return this;for(var c=this.data,d=this.Ca(b),e=[],f=[],g,h,k,n;c||e.length;){c||(c=e.pop(),h=e[e.length-1],g=f.pop(),n=!0);if(c.ua&&(k=c.children.indexOf(b),-1!==k)){c.children.splice(k,1);e.push(c);this.Ng(e);break}n||c.ua||!p(c.bbox,d)?h?(g++,c=h.children[g],n=!1):c=null:
+(e.push(c),f.push(g),g=0,h=c,c=c.children[0])}return this},Ca:function(b){return b},ce:function(b,c){return b[0]-c[0]},de:function(b,c){return b[1]-c[1]},toJSON:function(){return this.data},Me:function(b,c){for(var d=[];b;)b.ua?c.push.apply(c,b.children):d.push.apply(d,b.children),b=d.pop();return c},Oe:function(b,c,e,f){var g=e-c+1,h=this.$d,k;if(g<=h)return k={children:b.slice(c,e+1),height:1,bbox:null,ua:!0},d(k,this.Ca),k;f||(f=Math.ceil(Math.log(g)/Math.log(h)),h=Math.ceil(g/Math.pow(h,f-1)));
+k={children:[],height:f,bbox:null};var g=Math.ceil(g/h),h=g*Math.ceil(Math.sqrt(h)),n,p,q;for(r(b,c,e,h,this.ce);c<=e;c+=h)for(p=Math.min(c+h-1,e),r(b,c,p,g,this.de),n=c;n<=p;n+=g)q=Math.min(n+g-1,p),k.children.push(this.Oe(b,n,q,f-1));d(k,this.Ca);return k},Mg:function(b,c,d,e){for(var f,g,h,n,p,q,r,s;;){e.push(c);if(c.ua||e.length-1===d)break;r=s=Infinity;f=0;for(g=c.children.length;f<g;f++){h=c.children[f];p=k(h.bbox);q=b;var v=h.bbox;q=(Math.max(v[2],q[2])-Math.min(v[0],q[0]))*(Math.max(v[3],
+q[3])-Math.min(v[1],q[1]))-p;q<s?(s=q,r=p<r?p:r,n=h):q===s&&p<r&&(r=p,n=h)}c=n}return c},Pe:function(b,c,d){var e=this.Ca;d=d?b.bbox:e(b);var e=[],g=this.Mg(d,this.data,c,e);g.children.push(b);for(f(g.bbox,d);0<=c;)if(e[c].children.length>this.$d)this.Pg(e,c),c--;else break;this.Jg(d,e,c)},Pg:function(b,c){var e=b[c],f=e.children.length,g=this.Qe;this.Kg(e,g,f);f={children:e.children.splice(this.Lg(e,g,f)),height:e.height};e.ua&&(f.ua=!0);d(e,this.Ca);d(f,this.Ca);c?b[c-1].children.push(f):this.Re(e,
+f)},Re:function(b,c){this.data={children:[b,c],height:b.height+1};d(this.data,this.Ca)},Lg:function(b,c,d){var f,g,h,n,p,q,r;p=q=Infinity;for(f=c;f<=d-c;f++){g=e(b,0,f,this.Ca);h=e(b,f,d,this.Ca);var s=g,v=h;n=Math.max(s[0],v[0]);var ac=Math.max(s[1],v[1]),Sb=Math.min(s[2],v[2]),s=Math.min(s[3],v[3]);n=Math.max(0,Sb-n)*Math.max(0,s-ac);g=k(g)+k(h);n<p?(p=n,r=f,q=g<q?g:q):n===p&&g<q&&(q=g,r=f)}return r},Kg:function(b,c,d){var e=b.ua?this.ce:g,f=b.ua?this.de:h,k=this.Ne(b,c,d,e);c=this.Ne(b,c,d,f);
+k<c&&b.children.sort(e)},Ne:function(b,c,d,g){b.children.sort(g);g=this.Ca;var h=e(b,0,c,g),k=e(b,d-c,d,g),p=n(h)+n(k),q,r;for(q=c;q<d-c;q++)r=b.children[q],f(h,b.ua?g(r):r.bbox),p+=n(h);for(q=d-c-1;q>=c;q--)r=b.children[q],f(k,b.ua?g(r):r.bbox),p+=n(k);return p},Jg:function(b,c,d){for(;0<=d;d--)f(c[d].bbox,b)},Ng:function(b){for(var c=b.length-1,e;0<=c;c--)0===b[c].children.length?0<c?(e=b[c-1].children,e.splice(e.indexOf(b[c]),1)):this.clear():d(b[c],this.Ca)},Og:function(b){var c=["return a"," - b",
+";"];this.ce=new Function("a","b",c.join(b[0]));this.de=new Function("a","b",c.join(b[1]));this.Ca=new Function("a","return [a"+b.join(", a")+"];")}};"function"===typeof define&&define.Ul?define(function(){return c}):"undefined"!==typeof b?b.Ze=c:"undefined"!==typeof self?self.a=c:window.a=c})();Vv=b.Ze})();function Wv(b){this.a=Vv(b);this.c={}}l=Wv.prototype;l.na=function(b,c){var d=[b[0],b[1],b[2],b[3],c];this.a.na(d);Ab(this.c,ma(c).toString(),d)};l.load=function(b,c){for(var d=Array(c.length),e=0,f=c.length;e<f;e++){var g=b[e],h=c[e],g=[g[0],g[1],g[2],g[3],h];d[e]=g;Ab(this.c,ma(h).toString(),g)}this.a.load(d)};l.remove=function(b){b=ma(b).toString();var c=x(this.c,b);zb(this.c,b);return null!==this.a.remove(c)};l.update=function(b,c){this.remove(c);this.na(b,c)};
+function Xv(b){b=b.a.all();return Va(b,function(b){return b[4]})}function Yv(b,c){var d=b.a.search(c);return Va(d,function(b){return b[4]})}l.forEach=function(b,c){return Zv(Xv(this),b,c)};function $v(b,c,d,e){return Zv(Yv(b,c),d,e)}function Zv(b,c,d){for(var e,f=0,g=b.length;f<g&&!(e=c.call(d,b[f]));f++);return e}l.ia=function(){return xb(this.c)};l.clear=function(){this.a.clear();this.c={}};l.D=function(){return this.a.data.bbox};function aw(b){b=m(b)?b:{};qj.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,state:m(b.state)?b.state:void 0});this.b=new Wv;this.d={};this.f={};this.o={};this.i={};m(b.features)&&this.fb(b.features)}u(aw,qj);l=aw.prototype;l.Pa=function(b){var c=ma(b).toString();bw(this,c,b);var d=b.N();null!=d?(d=d.D(),this.b.na(d,b)):this.d[c]=b;cw(this,c,b);this.dispatchEvent(new dw("addfeature",b));this.l()};
+function bw(b,c,d){b.i[c]=[z(d,"change",b.Af,!1,b),z(d,"propertychange",b.Af,!1,b)]}function cw(b,c,d){var e=d.X;m(e)?b.f[e.toString()]=d:b.o[c]=d}l.ya=function(b){this.fb(b);this.l()};l.fb=function(b){var c,d,e,f,g=[],h=[];d=0;for(e=b.length;d<e;d++){f=b[d];c=ma(f).toString();bw(this,c,f);var k=f.N();null!=k?(c=k.D(),g.push(c),h.push(f)):this.d[c]=f}this.b.load(g,h);d=0;for(e=b.length;d<e;d++)f=b[d],c=ma(f).toString(),cw(this,c,f),this.dispatchEvent(new dw("addfeature",f))};
+l.clear=function(b){if(b){for(var c in this.i)Ta(this.i[c],Yc);this.i={};this.f={};this.o={}}else b=this.Xf,this.b.forEach(b,this),ob(this.d,b,this);this.b.clear();this.d={};this.dispatchEvent(new dw("clear"));this.l()};l.Xa=function(b,c){return this.b.forEach(b,c)};function ew(b,c,d){b.ra([c[0],c[1],c[0],c[1]],function(b){if(b.N().Jb(c[0],c[1]))return d.call(void 0,b)})}l.ra=function(b,c,d){return $v(this.b,b,c,d)};l.Db=function(b,c,d,e){return this.ra(b,d,e)};
+l.Fa=function(b,c,d){return this.ra(b,function(e){if(e.N().ha(b)&&(e=c.call(d,e)))return e})};l.va=function(){var b=Xv(this.b);xb(this.d)||db(b,rb(this.d));return b};l.Ia=function(b){var c=[];ew(this,b,function(b){c.push(b)});return c};l.Ya=function(b){var c=b[0],d=b[1],e=null,f=[NaN,NaN],g=Infinity,h=[-Infinity,-Infinity,Infinity,Infinity];$v(this.b,h,function(b){var n=b.N(),p=g;g=n.Va(c,d,f,g);g<p&&(e=b,b=Math.sqrt(g),h[0]=c-b,h[1]=d-b,h[2]=c+b,h[3]=d+b)});return e};l.D=function(){return this.b.D()};
+l.Ha=function(b){b=this.f[b.toString()];return m(b)?b:null};l.Af=function(b){b=b.target;var c=ma(b).toString(),d=b.N();null!=d?(d=d.D(),c in this.d?(delete this.d[c],this.b.na(d,b)):this.b.update(d,b)):c in this.d||(this.b.remove(b),this.d[c]=b);d=b.X;m(d)?(d=d.toString(),c in this.o?(delete this.o[c],this.f[d]=b):this.f[d]!==b&&(fw(this,b),this.f[d]=b)):c in this.o||(fw(this,b),this.o[c]=b);this.l();this.dispatchEvent(new dw("changefeature",b))};l.ia=function(){return this.b.ia()&&xb(this.d)};
+l.Hb=ca;l.$a=function(b){var c=ma(b).toString();c in this.d?delete this.d[c]:this.b.remove(b);this.Xf(b);this.l()};l.Xf=function(b){var c=ma(b).toString();Ta(this.i[c],Yc);delete this.i[c];var d=b.X;m(d)?delete this.f[d.toString()]:delete this.o[c];this.dispatchEvent(new dw("removefeature",b))};function fw(b,c){for(var d in b.f)if(b.f[d]===c){delete b.f[d];break}}function dw(b,c){uc.call(this,b);this.feature=c}u(dw,uc);function gw(b,c){uc.call(this,b);this.feature=c}u(gw,uc);
+function hw(b){Bk.call(this,{handleDownEvent:iw,handleEvent:jw,handleUpEvent:kw});this.Q=null;this.ga=m(b.source)?b.source:null;this.ba=m(b.features)?b.features:null;this.Ab=m(b.snapTolerance)?b.snapTolerance:12;this.Ta=m(b.minPointsPerRing)?b.minPointsPerRing:3;var c=this.F=b.type,d;if("Point"===c||"MultiPoint"===c)d=lw;else if("LineString"===c||"MultiLineString"===c)d=mw;else if("Polygon"===c||"MultiPolygon"===c)d=nw;this.d=d;this.f=this.r=this.j=this.g=this.i=null;this.J=new sp({style:m(b.style)?
+b.style:ow()});this.ca=b.geometryName;this.eb=m(b.condition)?b.condition:xk;z(this,xd("active"),this.ea,!1,this)}u(hw,Bk);function ow(){var b=Xl();return function(c){return b[c.N().H()]}}hw.prototype.setMap=function(b){hw.S.setMap.call(this,b);this.ea()};
+function jw(b){var c;c=b.map;if(Pf(document,c.b)&&"none"!=c.b.style.display){var d=c.e();null==d||0>=d[0]||0>=d[1]?c=!1:(c=c.a(),c=null!==c&&af(c)?!0:!1)}else c=!1;if(!c)return!0;c=!0;b.type===nj?c=pw(this,b):b.type===hj&&(c=!1);return Ck.call(this,b)&&c}function iw(b){return this.eb(b)?(this.Q=b.pixel,!0):!1}
+function kw(b){var c=this.Q,d=b.pixel,e=c[0]-d[0],c=c[1]-d[1],d=!0;4>=e*e+c*c&&(pw(this,b),null===this.i?qw(this,b):this.d===lw||rw(this,b)?this.V():(b=b.coordinate,e=this.g.N(),this.d===mw?(this.i=b.slice(),c=e.K(),c.push(b.slice()),e.U(c)):this.d===nw&&(this.f[0].push(b.slice()),e.U(this.f)),sw(this)),d=!1);return d}
+function pw(b,c){if(b.d===lw&&null===b.i)qw(b,c);else if(null===b.i){var d=c.coordinate.slice();null===b.j?(b.j=new R(new jl(d)),sw(b)):b.j.N().U(d)}else{var d=c.coordinate,e=b.g.N(),f,g;b.d===lw?(g=e.K(),g[0]=d[0],g[1]=d[1],e.U(g)):(b.d===mw?f=e.K():b.d===nw&&(f=b.f[0]),rw(b,c)&&(d=b.i.slice()),b.j.N().U(d),g=f[f.length-1],g[0]=d[0],g[1]=d[1],b.d===mw?e.U(f):b.d===nw&&(b.r.N().U(f),e.U(b.f)));sw(b)}return!0}
+function rw(b,c){var d=!1;if(null!==b.g){var e=b.g.N(),f=!1,g=[b.i];b.d===mw?f=2<e.K().length:b.d===nw&&(f=e.K()[0].length>b.Ta,g=[b.f[0][0],b.f[0][b.f[0].length-2]]);if(f)for(var e=c.map,f=0,h=g.length;f<h;f++){var k=g[f],n=e.f(k),p=c.pixel,d=p[0]-n[0],n=p[1]-n[1];if(d=Math.sqrt(d*d+n*n)<=b.Ab){b.i=k;break}}}return d}
+function qw(b,c){var d=c.coordinate;b.i=d;var e;b.d===lw?e=new jl(d.slice()):b.d===mw?e=new M([d.slice(),d.slice()]):b.d===nw&&(b.r=new R(new M([d.slice(),d.slice()])),b.f=[[d.slice(),d.slice()]],e=new H(b.f));b.g=new R;m(b.ca)&&b.g.e(b.ca);b.g.Ma(e);sw(b);b.dispatchEvent(new gw("drawstart",b.g))}
+hw.prototype.V=function(){var b=tw(this),c,d=b.N();this.d===lw?c=d.K():this.d===mw?(c=d.K(),c.pop(),d.U(c)):this.d===nw&&(this.f[0].pop(),this.f[0].push(this.f[0][0]),d.U(this.f),c=d.K());"MultiPoint"===this.F?b.Ma(new un([c])):"MultiLineString"===this.F?b.Ma(new rn([c])):"MultiPolygon"===this.F&&b.Ma(new vn([c]));null===this.ba||this.ba.push(b);null===this.ga||this.ga.Pa(b);this.dispatchEvent(new gw("drawend",b))};
+function tw(b){b.i=null;var c=b.g;null!==c&&(b.g=null,b.j=null,b.r=null,b.J.a.clear());return c}hw.prototype.q=cd;function sw(b){var c=[];null===b.g||c.push(b.g);null===b.r||c.push(b.r);null===b.j||c.push(b.j);b.J.Mc(new B(c))}hw.prototype.ea=function(){var b=this.n,c=this.a();null!==b&&c||tw(this);this.J.setMap(c?b:null)};var lw="Point",mw="LineString",nw="Polygon";function uw(b){Bk.call(this,{handleDownEvent:vw,handleDragEvent:ww,handleEvent:xw,handleUpEvent:yw});this.ba=m(b.deleteCondition)?b.deleteCondition:id(xk,wk);this.V=this.f=null;this.Q=[0,0];this.d=new Wv;this.i=m(b.pixelTolerance)?b.pixelTolerance:10;this.J=!1;this.g=null;this.j=new sp({style:m(b.style)?b.style:zw()});this.F={Point:this.Fl,LineString:this.jg,LinearRing:this.jg,Polygon:this.Hl,MultiPoint:this.Cl,MultiLineString:this.Bl,MultiPolygon:this.El,GeometryCollection:this.Al};this.r=b.features;
+this.r.forEach(this.wf,this);z(this.r,"add",this.Wh,!1,this);z(this.r,"remove",this.Xh,!1,this)}u(uw,Bk);l=uw.prototype;l.wf=function(b){var c=b.N();m(this.F[c.H()])&&this.F[c.H()].call(this,b,c);b=this.n;null===b||Aw(this,this.Q,b)};l.setMap=function(b){this.j.setMap(b);uw.S.setMap.call(this,b)};l.Wh=function(b){this.wf(b.element)};
+l.Xh=function(b){var c=b.element;b=this.d;var d,e=[];$v(b,c.N().D(),function(b){c===b.feature&&e.push(b)});for(d=e.length-1;0<=d;--d)b.remove(e[d]);null!==this.f&&0===this.r.Ib()&&(this.j.wd(this.f),this.f=null)};l.Fl=function(b,c){var d=c.K(),d={feature:b,geometry:c,fa:[d,d]};this.d.na(c.D(),d)};l.Cl=function(b,c){var d=c.K(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:b,geometry:c,depth:[f],index:f,fa:[e,e]},this.d.na(c.D(),e)};
+l.jg=function(b,c){var d=c.K(),e,f,g,h;e=0;for(f=d.length-1;e<f;++e)g=d.slice(e,e+2),h={feature:b,geometry:c,index:e,fa:g},this.d.na(Td(g),h)};l.Bl=function(b,c){var d=c.K(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,geometry:c,depth:[h],index:f,fa:n},this.d.na(Td(n),p)};
+l.Hl=function(b,c){var d=c.K(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,geometry:c,depth:[h],index:f,fa:n},this.d.na(Td(n),p)};l.El=function(b,c){var d=c.K(),e,f,g,h,k,n,p,q,r,s;n=0;for(p=d.length;n<p;++n)for(q=d[n],h=0,k=q.length;h<k;++h)for(e=q[h],f=0,g=e.length-1;f<g;++f)r=e.slice(f,f+2),s={feature:b,geometry:c,depth:[h,n],index:f,fa:r},this.d.na(Td(r),s)};
+l.Al=function(b,c){var d,e=c.d;for(d=0;d<e.length;++d)this.F[e[d].H()].call(this,b,e[d])};function Bw(b,c){var d=b.f;null===d?(d=new R(new jl(c)),b.f=d,b.j.rf(d)):d.N().U(c)}
+function vw(b){Aw(this,b.pixel,b.map);this.g=[];var c=this.f;if(null!==c){b=[];for(var c=c.N().K(),d=Td([c]),d=Yv(this.d,d),e=0,f=d.length;e<f;++e){var g=d[e],h=g.fa;Cd(h[0],c)?this.g.push([g,0]):Cd(h[1],c)?this.g.push([g,1]):ma(h)in this.V&&b.push([g,c])}for(e=b.length-1;0<=e;--e)this.ui.apply(this,b[e])}return null!==this.f}
+function ww(b){b=b.coordinate;for(var c=0,d=this.g.length;c<d;++c){for(var e=this.g[c],f=e[0],g=f.depth,h=f.geometry,k=h.K(),n=f.fa,e=e[1];b.length<h.s;)b.push(0);switch(h.H()){case "Point":k=b;n[0]=n[1]=b;break;case "MultiPoint":k[f.index]=b;n[0]=n[1]=b;break;case "LineString":k[f.index+e]=b;n[e]=b;break;case "MultiLineString":k[g[0]][f.index+e]=b;n[e]=b;break;case "Polygon":k[g[0]][f.index+e]=b;n[e]=b;break;case "MultiPolygon":k[g[1]][g[0]][f.index+e]=b,n[e]=b}h.U(k);Bw(this,b)}}
+function yw(){for(var b,c=this.g.length-1;0<=c;--c)b=this.g[c][0],this.d.update(Td(b.fa),b);return!1}
+function xw(b){var c,d=b.map.a();cb(d.j)[1]||b.type!=nj||(this.Q=b.pixel,Aw(this,b.pixel,b.map));if(null!==this.f&&this.J&&this.ba(b)){this.f.N();c=this.g;var d={},e=!1,f,g,h,k,n,p,q,r,s;for(n=c.length-1;0<=n;--n)if(h=c[n],r=h[0],k=r.geometry,g=k.K(),s=ma(r.feature),f=q=p=void 0,0===h[1]?(q=r,p=r.index):1==h[1]&&(f=r,p=r.index+1),s in d||(d[s]=[f,q,p]),h=d[s],m(f)&&(h[0]=f),m(q)&&(h[1]=q),m(h[0])&&m(h[1])){f=g;e=!1;q=p-1;switch(k.H()){case "MultiLineString":g[r.depth[0]].splice(p,1);e=!0;break;case "LineString":g.splice(p,
+1);e=!0;break;case "MultiPolygon":f=f[r.depth[1]];case "Polygon":f=f[r.depth[0]],4<f.length&&(p==f.length-1&&(p=0),f.splice(p,1),e=!0,0===p&&(f.pop(),f.push(f[0]),q=f.length-1))}e&&(this.d.remove(h[0]),this.d.remove(h[1]),k.U(g),g={depth:r.depth,feature:r.feature,geometry:r.geometry,index:q,fa:[h[0].fa[0],h[1].fa[1]]},this.d.na(Td(g.fa),g),Cw(this,k,p,r.depth,-1),this.j.wd(this.f),this.f=null)}c=e}return Ck.call(this,b)&&!c}
+function Aw(b,c,d){function e(b,c){return Ed(f,zd(f,b.fa))-Ed(f,zd(f,c.fa))}var f=d.Ga(c),g=d.Ga([c[0]-b.i,c[1]+b.i]),h=d.Ga([c[0]+b.i,c[1]-b.i]),g=Td([g,h]),g=Yv(b.d,g);if(0<g.length){g.sort(e);var h=g[0].fa,k=zd(f,h),n=d.f(k);if(Math.sqrt(Ed(c,n))<=b.i){c=d.f(h[0]);d=d.f(h[1]);c=Ed(n,c);d=Ed(n,d);b.J=Math.sqrt(Math.min(c,d))<=b.i;b.J&&(k=c>d?h[1]:h[0]);Bw(b,k);d={};d[ma(h)]=!0;c=1;for(n=g.length;c<n;++c)if(k=g[c].fa,Cd(h[0],k[0])&&Cd(h[1],k[1])||Cd(h[0],k[1])&&Cd(h[1],k[0]))d[ma(k)]=!0;else break;
+b.V=d;return}}null!==b.f&&(b.j.wd(b.f),b.f=null)}
+l.ui=function(b,c){for(var d=b.fa,e=b.feature,f=b.geometry,g=b.depth,h=b.index,k;c.length<f.s;)c.push(0);switch(f.H()){case "MultiLineString":k=f.K();k[g[0]].splice(h+1,0,c);break;case "Polygon":k=f.K();k[g[0]].splice(h+1,0,c);break;case "MultiPolygon":k=f.K();k[g[1]][g[0]].splice(h+1,0,c);break;case "LineString":k=f.K();k.splice(h+1,0,c);break;default:return}f.U(k);k=this.d;k.remove(b);Cw(this,f,h,g,1);var n={fa:[d[0],c],feature:e,geometry:f,depth:g,index:h};k.na(Td(n.fa),n);this.g.push([n,1]);d=
+{fa:[c,d[1]],feature:e,geometry:f,depth:g,index:h+1};k.na(Td(d.fa),d);this.g.push([d,0])};function Cw(b,c,d,e,f){$v(b.d,c.D(),function(b){b.geometry===c&&(!m(e)||ib(b.depth,e))&&b.index>d&&(b.index+=f)})}function zw(){var b=Xl();return function(){return b.Point}};function Dw(b){ok.call(this,{handleEvent:Ew});b=m(b)?b:{};this.g=m(b.condition)?b.condition:wk;this.f=m(b.addCondition)?b.addCondition:cd;this.p=m(b.removeCondition)?b.removeCondition:cd;this.r=m(b.toggleCondition)?b.toggleCondition:yk;var c;if(m(b.layers))if(ka(b.layers))c=b.layers;else{var d=b.layers;c=function(b){return Za(d,b)}}else c=dd;this.e=c;this.d=new sp({style:m(b.style)?b.style:Fw()});b=this.d.a;z(b,"add",this.i,!1,this);z(b,"remove",this.q,!1,this)}u(Dw,ok);Dw.prototype.j=function(){return this.d.a};
+function Ew(b){if(!this.g(b))return!0;var c=this.f(b),d=this.p(b),e=this.r(b),f=b.map,g=this.d.a;if(c||d||e){var h=[],k=[];f.oe(b.pixel,function(b){-1==Sa.indexOf.call(g.a,b,void 0)?(c||e)&&k.push(b):(d||e)&&h.push(b)},void 0,this.e);for(f=h.length-1;0<=f;--f)g.remove(h[f]);g.qf(k)}else f=f.oe(b.pixel,function(b){return b},void 0,this.e),m(f)&&1==g.Ib()&&g.item(0)==f||(0!==g.Ib()&&g.clear(),m(f)&&g.push(f));return vk(b)}
+Dw.prototype.setMap=function(b){var c=this.n,d=this.d.a;null===c||d.forEach(c.fc,c);Dw.S.setMap.call(this,b);this.d.setMap(b);null===b||d.forEach(b.eb,b)};function Fw(){var b=Xl();db(b.Polygon,b.LineString);db(b.GeometryCollection,b.LineString);return function(c){return b[c.N().H()]}}Dw.prototype.i=function(b){b=b.element;var c=this.n;null===c||c.eb(b)};Dw.prototype.q=function(b){b=b.element;var c=this.n;null===c||c.fc(b)};function $(b){b=m(b)?b:{};L.call(this,b);this.ea=null;z(this,xd("gradient"),this.Wd,!1,this);this.fc(m(b.gradient)?b.gradient:Gw);var c=Hw(m(b.radius)?b.radius:8,m(b.blur)?b.blur:15,m(b.shadow)?b.shadow:250),d=Array(256),e=m(b.weight)?b.weight:"weight",f;ia(e)?f=function(b){return b.get(e)}:f=e;this.oa(function(b){b=f(b);b=m(b)?Yb(b,0,1):1;var e=255*b|0,k=d[e];m(k)||(k=[new Tl({image:new Sj({opacity:b,src:c})})],d[e]=k);return k});this.set("renderOrder",null);z(this,"render",this.Xd,!1,this)}
+u($,L);var Gw=["#00f","#0ff","#0f0","#ff0","#f00"];function Hw(b,c,d){var e=b+c+1,f=2*e,f=Tf(f,f);f.shadowOffsetX=f.shadowOffsetY=d;f.shadowBlur=c;f.shadowColor="#000";f.beginPath();c=e-d;f.arc(c,c,b,0,2*Math.PI,!0);f.fill();return f.canvas.toDataURL()}$.prototype.qa=function(){return this.get("gradient")};$.prototype.getGradient=$.prototype.qa;
+$.prototype.Wd=function(){for(var b=this.qa(),c=Tf(1,256),d=c.createLinearGradient(0,0,1,256),e=1/(b.length-1),f=0,g=b.length;f<g;++f)d.addColorStop(f*e,b[f]);c.fillStyle=d;c.fillRect(0,0,1,256);this.ea=c.getImageData(0,0,1,256).data};$.prototype.Xd=function(b){b=b.context;var c=b.canvas,c=b.getImageData(0,0,c.width,c.height),d=c.data,e,f,g;e=0;for(f=d.length;e<f;e+=4)if(g=4*d[e+3])d[e]=this.ea[g],d[e+1]=this.ea[g+1],d[e+2]=this.ea[g+2];b.putImageData(c,0,0)};
+$.prototype.fc=function(b){this.set("gradient",b)};$.prototype.setGradient=$.prototype.fc;function Iw(b){return[b]};function Jw(b,c){var d=c||{},e=d.document||document,f=If("SCRIPT"),g={$f:f,bc:void 0},h=new yv(Kw,g),k=null,n=null!=d.timeout?d.timeout:5E3;0<n&&(k=window.setTimeout(function(){Lw(f,!0);var c=new Mw(Nw,"Timeout reached for loading script "+b);Av(h);Bv(h,!1,c)},n),g.bc=k);f.onload=f.onreadystatechange=function(){f.readyState&&"loaded"!=f.readyState&&"complete"!=f.readyState||(Lw(f,d.Zg||!1,k),Av(h),Bv(h,!0,null))};f.onerror=function(){Lw(f,!0,k);var c=new Mw(Ow,"Error while loading script "+b);Av(h);
+Bv(h,!1,c)};Cf(f,{type:"text/javascript",charset:"UTF-8",src:b});Pw(e).appendChild(f);return h}function Pw(b){var c=b.getElementsByTagName("HEAD");return c&&0!=c.length?c[0]:b.documentElement}function Kw(){if(this&&this.$f){var b=this.$f;b&&"SCRIPT"==b.tagName&&Lw(b,!0,this.bc)}}function Lw(b,c,d){null!=d&&ba.clearTimeout(d);b.onload=ca;b.onerror=ca;b.onreadystatechange=ca;c&&window.setTimeout(function(){Nf(b)},0)}var Ow=0,Nw=1;
+function Mw(b,c){var d="Jsloader error (code #"+b+")";c&&(d+=": "+c);xa.call(this,d);this.code=b}u(Mw,xa);function Qw(b,c){this.c=new Ah(b);this.a=c?c:"callback";this.bc=5E3}var Rw=0;
+Qw.prototype.send=function(b,c,d,e){b=b||null;e=e||"_"+(Rw++).toString(36)+ua().toString(36);ba._callbacks_||(ba._callbacks_={});var f=this.c.clone();if(b)for(var g in b)if(!b.hasOwnProperty||b.hasOwnProperty(g)){var h=f,k=g,n=b[g];ga(n)||(n=[String(n)]);Uh(h.a,k,n)}c&&(ba._callbacks_[e]=Sw(e,c),c=this.a,g="_callbacks_."+e,ga(g)||(g=[String(g)]),Uh(f.a,c,g));c=Jw(f.toString(),{timeout:this.bc,Zg:!0});Ev(c,null,Tw(e,b,d),void 0);return{X:e,Xe:c}};
+Qw.prototype.cancel=function(b){b&&(b.Xe&&b.Xe.cancel(),b.X&&Uw(b.X,!1))};function Tw(b,c,d){return function(){Uw(b,!1);d&&d(c)}}function Sw(b,c){return function(d){Uw(b,!0);c.apply(void 0,arguments)}}function Uw(b,c){ba._callbacks_[b]&&(c?delete ba._callbacks_[b]:ba._callbacks_[b]=ca)};function Vw(b){var c=/\{z\}/g,d=/\{x\}/g,e=/\{y\}/g,f=/\{-y\}/g;return function(g){return null===g?void 0:b.replace(c,g[0].toString()).replace(d,g[1].toString()).replace(e,g[2].toString()).replace(f,function(){return((1<<g[0])-g[2]-1).toString()})}}function Ww(b){return Xw(Va(b,Vw))}function Xw(b){return 1===b.length?b[0]:function(c,d,e){return null===c?void 0:b[Zb((c[1]<<c[0])+c[2],b.length)](c,d,e)}}function Yw(){}
+function Zw(b,c){var d=[0,0,0];return function(e,f,g){return null===e?void 0:c(b(e,g,d),f,g)}}function $w(b){var c=[],d=/\{(\d)-(\d)\}/.exec(b)||/\{([a-z])-([a-z])\}/.exec(b);if(d){var e=d[2].charCodeAt(0),f;for(f=d[1].charCodeAt(0);f<=e;++f)c.push(b.replace(d[0],String.fromCharCode(f)))}else c.push(b);return c};function ax(b){Io.call(this);this.e=m(b)?b:2048}u(ax,Io);function bx(b){return b.Ub()>b.e}function cx(b,c){for(var d,e;bx(b)&&!(d=b.a.dc,e=d.a[0].toString(),e in c&&c[e].contains(d.a));)b.pop().hc()};function dx(b){Fj.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,opaque:b.opaque,projection:b.projection,state:m(b.state)?b.state:void 0,tileGrid:b.tileGrid,tilePixelRatio:b.tilePixelRatio});this.tileUrlFunction=m(b.tileUrlFunction)?b.tileUrlFunction:Yw;this.crossOrigin=m(b.crossOrigin)?b.crossOrigin:null;this.b=new ax;this.tileLoadFunction=m(b.tileLoadFunction)?b.tileLoadFunction:ex;this.tileClass=m(b.tileClass)?b.tileClass:Yu}u(dx,Fj);function ex(b,c){b.Na().src=c}l=dx.prototype;
+l.zd=function(){return bx(this.b)};l.se=function(b){cx(this.b,b)};l.Fb=function(b,c,d,e,f){var g=this.hb(b,c,d);if(Fo(this.b,g))return this.b.get(g);b=[b,c,d];e=this.tileUrlFunction(b,e,f);e=new this.tileClass(b,m(e)?0:4,m(e)?e:"",this.crossOrigin,this.tileLoadFunction);this.b.set(g,e);return e};l.ib=function(){return this.tileLoadFunction};l.jb=function(){return this.tileUrlFunction};l.ob=function(b){this.b.clear();this.tileLoadFunction=b;this.l()};
+l.pa=function(b){this.b.clear();this.tileUrlFunction=b;this.l()};l.He=function(b,c,d){b=this.hb(b,c,d);Fo(this.b,b)&&this.b.get(b)};function fx(b){var c=m(b.extent)?b.extent:om,d=Dj(c,b.maxZoom,b.tileSize);wj.call(this,{minZoom:b.minZoom,origin:le(c,"top-left"),resolutions:d,tileSize:b.tileSize})}u(fx,wj);
+fx.prototype.Bb=function(b){b=m(b)?b:{};var c=this.minZoom,d=this.maxZoom,e=m(b.wrapX)?b.wrapX:!0,f=null;if(m(b.extent)){var f=Array(d+1),g;for(g=0;g<=d;++g)f[g]=g<c?null:zj(this,b.extent,g)}return function(b,g,n){g=b[0];if(g<c||d<g)return null;var p=Math.pow(2,g),q=b[1];if(e)q=Zb(q,p);else if(0>q||p<=q)return null;b=b[2];return b<-p||-1<b||null!==f&&!qf(f[g],q,b)?null:kf(g,q,-b-1,n)}};fx.prototype.kd=function(b,c){if(b[0]<this.maxZoom){var d=2*b[1],e=2*b[2];return pf(d,d+1,e,e+1,c)}return null};
+fx.prototype.$c=function(b,c,d,e){e=pf(0,b[1],0,b[2],e);for(b=b[0]-1;b>=this.minZoom;--b)if(e.a=e.d>>=1,e.b=e.c>>=1,c.call(d,b,e))return!0;return!1};function gx(b){dx.call(this,{crossOrigin:"anonymous",opaque:!0,projection:Ee("EPSG:3857"),state:"loading",tileLoadFunction:b.tileLoadFunction});this.d=m(b.culture)?b.culture:"en-us";this.a=m(b.maxZoom)?b.maxZoom:-1;var c=new Ah((Wb?"https:":"http:")+"//dev.virtualearth.net/REST/v1/Imagery/Metadata/"+b.imagerySet);(new Qw(c,"jsonp")).send({include:"ImageryProviders",uriScheme:Wb?"https":"http",key:b.key},sa(this.f,this))}u(gx,dx);var hx=new tf({html:'<a class="ol-attribution-bing-tos" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'});
+gx.prototype.f=function(b){if(200!=b.statusCode||"OK"!=b.statusDescription||"ValidCredentials"!=b.authenticationResultCode||1!=b.resourceSets.length||1!=b.resourceSets[0].resources.length)rj(this,"error");else{var c=b.brandLogoUri,d=b.resourceSets[0].resources[0],e=-1==this.a?d.zoomMax:this.a,f=new fx({extent:Ej(this.g),minZoom:d.zoomMin,maxZoom:e,tileSize:d.imageWidth});this.tileGrid=f;var g=this.d;this.tileUrlFunction=Zw(f.Bb(),Xw(Va(d.imageUrlSubdomains,function(b){var c=d.imageUrl.replace("{subdomain}",
+b).replace("{culture}",g);return function(b){return null===b?void 0:c.replace("{quadkey}",mf(b))}})));if(d.imageryProviders){var h=De(Ee("EPSG:4326"),this.g);b=Va(d.imageryProviders,function(b){var c=b.attribution,d={};Ta(b.coverageAreas,function(b){var c=b.zoomMin,g=Math.min(b.zoomMax,e);b=b.bbox;b=we([b[1],b[0],b[3],b[2]],h);var k,n;for(k=c;k<=g;++k)n=k.toString(),c=zj(f,b,k),n in d?d[n].push(c):d[n]=[c]});return new tf({html:c,tileRanges:d})});b.push(hx);this.e=b}this.r=c;rj(this,"ready")}};function ix(b){aw.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection});this.j=void 0;this.q=m(b.distance)?b.distance:20;this.a=[];this.p=b.source;this.p.u("change",ix.prototype.t,this)}u(ix,aw);ix.prototype.Hb=function(b,c,d){c!==this.j&&(this.clear(),this.j=c,this.p.Hb(b,c,d),jx(this),this.ya(this.a))};ix.prototype.t=function(){this.clear();jx(this);this.ya(this.a);this.l()};
+function jx(b){if(m(b.j)){$a(b.a);for(var c=Vd(),d=b.q*b.j,e=b.p.va(),f={},g=0,h=e.length;g<h;g++){var k=e[g];tb(f,ma(k).toString())||(k=k.N().K(),ce(k,c),Yd(c,d,c),k=Yv(b.p.b,c),k=Ua(k,function(b){b=ma(b).toString();return b in f?!1:f[b]=!0}),b.a.push(kx(k)))}}}function kx(b){for(var c=b.length,d=[0,0],e=0;e<c;e++){var f=b[e].N().K();yd(d,f)}c=1/c;d[0]*=c;d[1]*=c;d=new R(new jl(d));d.set("features",b);return d};function lx(b,c,d){if(ka(b))d&&(b=sa(b,d));else if(b&&"function"==typeof b.handleEvent)b=sa(b.handleEvent,b);else throw Error("Invalid listener argument");return 2147483647<c?-1:ba.setTimeout(b,c||0)};function mx(){}mx.prototype.a=null;function nx(b){var c;(c=b.a)||(c={},ox(b)&&(c[0]=!0,c[1]=!0),c=b.a=c);return c};var px;function qx(){}u(qx,mx);function rx(b){return(b=ox(b))?new ActiveXObject(b):new XMLHttpRequest}function ox(b){if(!b.c&&"undefined"==typeof XMLHttpRequest&&"undefined"!=typeof ActiveXObject){for(var c=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],d=0;d<c.length;d++){var e=c[d];try{return new ActiveXObject(e),b.c=e}catch(f){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return b.c}px=new qx;function sx(b){jd.call(this);this.r=new rh;this.i=b||null;this.a=!1;this.o=this.T=null;this.e=this.p="";this.c=this.j=this.d=this.n=!1;this.g=0;this.b=null;this.f=tx;this.q=this.t=!1}u(sx,jd);var tx="",ux=/^https?$/i,vx=["POST","PUT"];l=sx.prototype;
+l.send=function(b,c,d,e){if(this.T)throw Error("[goog.net.XhrIo] Object is active with another request="+this.p+"; newUri="+b);c=c?c.toUpperCase():"GET";this.p=b;this.e="";this.n=!1;this.a=!0;this.T=this.i?rx(this.i):rx(px);this.o=this.i?nx(this.i):nx(px);this.T.onreadystatechange=sa(this.Hf,this);try{this.j=!0,this.T.open(c,String(b),!0),this.j=!1}catch(f){wx(this,f);return}b=d||"";var g=this.r.clone();e&&qh(e,function(b,c){g.set(c,b)});e=Xa(g.I());d=ba.FormData&&b instanceof ba.FormData;!Za(vx,
+c)||e||d||g.set("Content-Type","application/x-www-form-urlencoded;charset=utf-8");g.forEach(function(b,c){this.T.setRequestHeader(c,b)},this);this.f&&(this.T.responseType=this.f);"withCredentials"in this.T&&(this.T.withCredentials=this.t);try{xx(this),0<this.g&&((this.q=yx(this.T))?(this.T.timeout=this.g,this.T.ontimeout=sa(this.bc,this)):this.b=lx(this.bc,this.g,this)),this.d=!0,this.T.send(b),this.d=!1}catch(h){wx(this,h)}};function yx(b){return Hb&&Tb(9)&&ja(b.timeout)&&m(b.ontimeout)}
+function Ya(b){return"content-type"==b.toLowerCase()}l.bc=function(){"undefined"!=typeof aa&&this.T&&(this.e="Timed out after "+this.g+"ms, aborting",this.dispatchEvent("timeout"),this.T&&this.a&&(this.a=!1,this.c=!0,this.T.abort(),this.c=!1,this.dispatchEvent("complete"),this.dispatchEvent("abort"),zx(this)))};function wx(b,c){b.a=!1;b.T&&(b.c=!0,b.T.abort(),b.c=!1);b.e=c;Ax(b);zx(b)}function Ax(b){b.n||(b.n=!0,b.dispatchEvent("complete"),b.dispatchEvent("error"))}
+l.M=function(){this.T&&(this.a&&(this.a=!1,this.c=!0,this.T.abort(),this.c=!1),zx(this,!0));sx.S.M.call(this)};l.Hf=function(){this.Sa||(this.j||this.d||this.c?Bx(this):this.rk())};l.rk=function(){Bx(this)};
+function Bx(b){if(b.a&&"undefined"!=typeof aa&&(!b.o[1]||4!=Cx(b)||2!=Dx(b)))if(b.d&&4==Cx(b))lx(b.Hf,0,b);else if(b.dispatchEvent("readystatechange"),4==Cx(b)){b.a=!1;try{if(Ex(b))b.dispatchEvent("complete"),b.dispatchEvent("success");else{var c;try{c=2<Cx(b)?b.T.statusText:""}catch(d){c=""}b.e=c+" ["+Dx(b)+"]";Ax(b)}}finally{zx(b)}}}function zx(b,c){if(b.T){xx(b);var d=b.T,e=b.o[0]?ca:null;b.T=null;b.o=null;c||b.dispatchEvent("ready");try{d.onreadystatechange=e}catch(f){}}}
+function xx(b){b.T&&b.q&&(b.T.ontimeout=null);ja(b.b)&&(ba.clearTimeout(b.b),b.b=null)}function Ex(b){var c=Dx(b),d;a:switch(c){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:d=!0;break a;default:d=!1}if(!d){if(c=0===c)b=vh(String(b.p))[1]||null,!b&&self.location&&(b=self.location.protocol,b=b.substr(0,b.length-1)),c=!ux.test(b?b.toLowerCase():"");d=c}return d}function Cx(b){return b.T?b.T.readyState:0}function Dx(b){try{return 2<Cx(b)?b.T.status:-1}catch(c){return-1}}
+function Fx(b){try{return b.T?b.T.responseText:""}catch(c){return""}}function Gx(b){try{if(!b.T)return null;if("response"in b.T)return b.T.response;switch(b.f){case tx:case "text":return b.T.responseText;case "arraybuffer":if("mozResponseArrayBuffer"in b.T)return b.T.mozResponseArrayBuffer}return null}catch(c){return null}};function Hx(b){aw.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection});this.format=b.format}u(Hx,aw);
+function Ix(b,c,d,e,f){var g=new sx;g.f="binary"==b.format.H()&&bg?"arraybuffer":"text";z(g,"complete",function(b){b=b.target;if(Ex(b)){var c=this.format.H(),g;if("binary"==c&&bg)g=Gx(b);else if("json"==c)g=Fx(b);else if("text"==c)g=Fx(b);else if("xml"==c){if(!Hb)try{g=b.T?b.T.responseXML:null}catch(p){g=null}null!=g||(g=iq(Fx(b)))}null!=g?d.call(f,this.a(g)):rj(this,"error")}else e.call(f);tc(b)},!1,b);g.send(c)}Hx.prototype.a=function(b){return this.format.ja(b,{featureProjection:this.g})};function Jx(b){Hx.call(this,{attributions:b.attributions,format:b.format,logo:b.logo,projection:b.projection});m(b.arrayBuffer)&&this.fb(this.a(b.arrayBuffer));m(b.doc)&&this.fb(this.a(b.doc));m(b.node)&&this.fb(this.a(b.node));m(b.object)&&this.fb(this.a(b.object));m(b.text)&&this.fb(this.a(b.text));if(m(b.url)||m(b.urls))if(rj(this,"loading"),m(b.url)&&Ix(this,b.url,this.p,this.j,this),m(b.urls)){b=b.urls;var c,d;c=0;for(d=b.length;c<d;++c)Ix(this,b[c],this.p,this.j,this)}}u(Jx,Hx);
+Jx.prototype.j=function(){rj(this,"error")};Jx.prototype.p=function(b){this.fb(b);rj(this,"ready")};function Kx(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,extent:b.extent,format:new Ep({defaultDataProjection:b.defaultProjection}),logo:b.logo,object:b.object,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Kx,Jx);function Lx(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,doc:b.doc,extent:b.extent,format:new Vq,logo:b.logo,node:b.node,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Lx,Jx);function Mx(b){b=m(b)?b:{};Jx.call(this,{format:new Fr({altitudeMode:b.altitudeMode}),projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Mx,Jx);function Nx(b){qj.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state});this.o=m(b.resolutions)?b.resolutions:null}u(Nx,qj);function Ox(b,c){if(null!==b.o){var d=ec(b.o,c,0);c=b.o[d]}return c}function Px(b,c){b.c().src=c};function Qx(b){Nx.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions,state:m(b.state)?b.state:void 0});this.t=b.canvasFunction;this.p=null;this.q=0;this.F=m(b.ratio)?b.ratio:1.5}u(Qx,Nx);Qx.prototype.rc=function(b,c,d,e){c=Ox(this,c);var f=this.p;if(null!==f&&this.q==this.c&&f.resolution==c&&f.e==d&&$d(f.D(),b))return f;b=b.slice();ue(b,this.F);e=this.t(b,c,d,[re(b)/c*d,oe(b)/c*d],e);null===e||(f=new Xu(b,c,d,this.e,e));this.p=f;this.q=this.c;return f};function Rx(b){Nx.call(this,{projection:b.projection,resolutions:b.resolutions});this.q=m(b.crossOrigin)?b.crossOrigin:null;this.b=m(b.displayDpi)?b.displayDpi:96;this.a=m(b.params)?b.params:{};var c;m(b.url)?c=Zu(b.url,this.a,sa(this.Q,this)):c=$u;this.j=c;this.F=m(b.imageLoadFunction)?b.imageLoadFunction:Px;this.t=m(b.hidpi)?b.hidpi:!0;this.p=m(b.metersPerUnit)?b.metersPerUnit:1;this.f=m(b.ratio)?b.ratio:1;this.ba=m(b.useOverlay)?b.useOverlay:!1;this.d=null;this.i=0}u(Rx,Nx);Rx.prototype.J=function(){return this.a};
+Rx.prototype.rc=function(b,c,d,e){c=Ox(this,c);d=this.t?d:1;var f=this.d;if(null!==f&&this.i==this.c&&f.resolution==c&&f.e==d&&$d(f.D(),b))return f;1!=this.f&&(b=b.slice(),ue(b,this.f));e=this.j(b,[re(b)/c*d,oe(b)/c*d],e);m(e)?f=new Wu(b,c,d,this.e,e,this.q,this.F):f=null;this.d=f;this.i=this.c;return f};Rx.prototype.V=function(b){Eb(this.a,b);this.l()};
+Rx.prototype.Q=function(b,c,d,e){var f;f=this.p;var g=re(d),h=oe(d),k=e[0],n=e[1],p=.0254/this.b;f=n*g>k*h?g*f/(k*p):h*f/(n*p);d=ke(d);e={OPERATION:this.ba?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol.source.ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:this.b,SETDISPLAYWIDTH:Math.round(e[0]),SETDISPLAYHEIGHT:Math.round(e[1]),SETVIEWSCALE:f,SETVIEWCENTERX:d[0],SETVIEWCENTERY:d[1]};Eb(e,c);return xh(zh([b],e))};function Sx(b){var c=m(b.attributions)?b.attributions:null,d=b.imageExtent,e,f;m(b.imageSize)&&(e=oe(d)/b.imageSize[1],f=[e]);var g=m(b.crossOrigin)?b.crossOrigin:null,h=m(b.imageLoadFunction)?b.imageLoadFunction:Px;Nx.call(this,{attributions:c,logo:b.logo,projection:Ee(b.projection),resolutions:f});this.a=new Wu(d,e,1,c,b.url,g,h)}u(Sx,Nx);Sx.prototype.rc=function(b){return qe(b,this.a.D())?this.a:null};function Tx(b){this.a=b.source;this.J=Kd();this.b=Tf();this.d=[0,0];this.i=null;Qx.call(this,{attributions:b.attributions,canvasFunction:sa(this.Xg,this),logo:b.logo,projection:b.projection,ratio:b.ratio,resolutions:b.resolutions,state:this.a.n});this.j=null;this.f=void 0;this.yf(b.style);z(this.a,"change",this.Aj,void 0,this)}u(Tx,Qx);l=Tx.prototype;
+l.Xg=function(b,c,d,e,f){var g=new Wm(.5*c/d,b,c);this.a.Hb(b,c,f);var h=!1;this.a.Db(b,c,function(b){var e;if(!(e=h)){var f;m(b.a)?f=b.a.call(b,c):m(this.f)&&(f=this.f(b,c));if(null!=f){var q,r=!1;e=0;for(q=f.length;e<q;++e)r=An(g,b,f[e],zn(c,d),this.zj,this)||r;e=r}else e=!1}h=e},this);Xm(g);if(h)return null;this.d[0]!=e[0]||this.d[1]!=e[1]?(this.b.canvas.width=e[0],this.b.canvas.height=e[1],this.d[0]=e[0],this.d[1]=e[1]):this.b.clearRect(0,0,e[0],e[1]);b=Ux(this,ke(b),c,d,e);$m(g,this.b,d,b,0,
+{});this.i=g;return this.b.canvas};l.yd=function(b,c,d,e,f){if(null!==this.i){var g={};return ck(this.i,b,0,d,e,function(b){var c=ma(b).toString();if(!(c in g))return g[c]=!0,f(b)})}};l.wj=function(){return this.a};l.xj=function(){return this.j};l.yj=function(){return this.f};function Ux(b,c,d,e,f){return Vj(b.J,f[0]/2,f[1]/2,e/d,-e/d,0,-c[0],-c[1])}l.zj=function(){this.l()};l.Aj=function(){rj(this,this.a.n)};l.yf=function(b){this.j=m(b)?b:Wl;this.f=null===b?void 0:Vl(this.j);this.l()};function Vx(b){b=m(b)?b:{};Nx.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions});this.t=m(b.crossOrigin)?b.crossOrigin:null;this.b=b.url;this.J=m(b.imageLoadFunction)?b.imageLoadFunction:Px;this.a=b.params;this.f=!0;Wx(this);this.q=b.serverType;this.F=m(b.hidpi)?b.hidpi:!0;this.d=null;this.i=[0,0];this.p=0;this.j=m(b.ratio)?b.ratio:1.5}u(Vx,Nx);var Xx=[101,101];l=Vx.prototype;
+l.Bj=function(b,c,d,e){if(m(this.b)){var f=ne(b,c,0,Xx),g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:x(this.a,"LAYERS")};Eb(g,this.a,e);e=Math.floor((f[3]-b[1])/c);g[this.f?"I":"X"]=Math.floor((b[0]-f[0])/c);g[this.f?"J":"Y"]=e;return Yx(this,f,Xx,1,Ee(d),g)}};l.Cj=function(){return this.a};
+l.rc=function(b,c,d,e){if(!m(this.b))return null;c=Ox(this,c);1==d||this.F&&m(this.q)||(d=1);var f=this.d;if(null!==f&&this.p==this.c&&f.resolution==c&&f.e==d&&$d(f.D(),b))return f;f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Eb(f,this.a);b=b.slice();var g=(b[0]+b[2])/2,h=(b[1]+b[3])/2;if(1!=this.j){var k=this.j*re(b)/2,n=this.j*oe(b)/2;b[0]=g-k;b[1]=h-n;b[2]=g+k;b[3]=h+n}var k=c/d,n=Math.ceil(re(b)/k),p=Math.ceil(oe(b)/k);b[0]=g-k*n/2;b[2]=g+k*n/2;b[1]=h-k*
+p/2;b[3]=h+k*p/2;this.i[0]=n;this.i[1]=p;e=Yx(this,b,this.i,d,e,f);this.d=new Wu(b,c,d,this.e,e,this.t,this.J);this.p=this.c;return this.d};
+function Yx(b,c,d,e,f,g){g[b.f?"CRS":"SRS"]=f.a;"STYLES"in b.a||(g.STYLES=new String(""));if(1!=e)switch(b.q){case "geoserver":g.FORMAT_OPTIONS="dpi:"+(90*e+.5|0);break;case "mapserver":g.MAP_RESOLUTION=90*e;break;case "carmentaserver":case "qgis":g.DPI=90*e}g.WIDTH=d[0];g.HEIGHT=d[1];d=f.b;var h;b.f&&"ne"==d.substr(0,2)?h=[c[1],c[0],c[3],c[2]]:h=c;g.BBOX=h.join(",");return xh(zh([b.b],g))}l.Dj=function(){return this.b};l.Ej=function(b){b!=this.b&&(this.b=b,this.d=null,this.l())};
+l.Fj=function(b){Eb(this.a,b);Wx(this);this.d=null;this.l()};function Wx(b){b.f=0<=Na(x(b.a,"VERSION","1.3.0"),"1.3")};function Zx(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,doc:b.doc,format:new Kr({extractStyles:b.extractStyles,defaultStyle:b.defaultStyle}),logo:b.logo,node:b.node,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Zx,Jx);function $x(b){var c=m(b.projection)?b.projection:"EPSG:3857",d=new fx({extent:Ej(c),maxZoom:b.maxZoom,tileSize:b.tileSize});dx.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:c,tileGrid:d,tileLoadFunction:b.tileLoadFunction,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:Yw});this.d=d.Bb({wrapX:b.wrapX});m(b.tileUrlFunction)?this.pa(b.tileUrlFunction):m(b.urls)?this.pa(Ww(b.urls)):m(b.url)&&this.a(b.url)}u($x,dx);
+$x.prototype.pa=function(b){$x.S.pa.call(this,Zw(this.d,b))};$x.prototype.a=function(b){this.pa(Ww($w(b)))};function ay(b){b=m(b)?b:{};var c;m(b.attributions)?c=b.attributions:c=[by];var d=Wb?"https:":"http:";$x.call(this,{attributions:c,crossOrigin:m(b.crossOrigin)?b.crossOrigin:"anonymous",opaque:!0,maxZoom:m(b.maxZoom)?b.maxZoom:19,tileLoadFunction:b.tileLoadFunction,url:m(b.url)?b.url:d+"//{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png"})}u(ay,$x);var by=new tf({html:'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'});function cy(b){b=m(b)?b:{};var c=dy[b.layer];$x.call(this,{attributions:c.attributions,crossOrigin:"anonymous",logo:"//developer.mapquest.com/content/osm/mq_logo.png",maxZoom:c.maxZoom,opaque:!0,tileLoadFunction:b.tileLoadFunction,url:(Wb?"https:":"http:")+"//otile{1-4}-s.mqcdn.com/tiles/1.0.0/"+b.layer+"/{z}/{x}/{y}.jpg"})}u(cy,$x);
+var ey=new tf({html:'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'}),dy={osm:{maxZoom:19,attributions:[ey,by]},sat:{maxZoom:18,attributions:[ey,new tf({html:"Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency"})]},hyb:{maxZoom:18,attributions:[ey,by]}};function fy(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,doc:b.doc,format:new ot,logo:b.logo,node:b.node,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(fy,Jx);function gy(b){Hx.call(this,{attributions:b.attributions,format:b.format,logo:b.logo,projection:b.projection});this.p=new Wv;this.q=b.loader;this.t=m(b.strategy)?b.strategy:Iw;this.j={}}u(gy,Hx);gy.prototype.fb=function(b){var c=[],d,e;d=0;for(e=b.length;d<e;++d){var f=b[d],g=f.X;m(g)?g in this.j||(c.push(f),this.j[g]=!0):c.push(f)}gy.S.fb.call(this,c)};gy.prototype.clear=function(){yb(this.j);this.p.clear();gy.S.clear.call(this)};
+gy.prototype.Hb=function(b,c,d){var e=this.p;b=this.t(b,c);var f,g;f=0;for(g=b.length;f<g;++f){var h=b[f];$v(e,h,function(b){return $d(b.extent,h)})||(this.q.call(this,h,c,d),e.na(h,{extent:h.slice()}))}};var hy={terrain:{Wa:"jpg",opaque:!0},"terrain-background":{Wa:"jpg",opaque:!0},"terrain-labels":{Wa:"png",opaque:!1},"terrain-lines":{Wa:"png",opaque:!1},"toner-background":{Wa:"png",opaque:!0},toner:{Wa:"png",opaque:!0},"toner-hybrid":{Wa:"png",opaque:!1},"toner-labels":{Wa:"png",opaque:!1},"toner-lines":{Wa:"png",opaque:!1},"toner-lite":{Wa:"png",opaque:!0},watercolor:{Wa:"jpg",opaque:!0}},iy={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:3,maxZoom:16}};
+function jy(b){var c=b.layer.indexOf("-"),d=hy[b.layer],e=Wb?"https://stamen-tiles-{a-d}.a.ssl.fastly.net/":"http://{a-d}.tile.stamen.com/";$x.call(this,{attributions:ky,crossOrigin:"anonymous",maxZoom:iy[-1==c?b.layer:b.layer.slice(0,c)].maxZoom,opaque:d.opaque,tileLoadFunction:b.tileLoadFunction,url:m(b.url)?b.url:e+b.layer+"/{z}/{x}/{y}."+d.Wa})}u(jy,$x);
+var ky=[new tf({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>.'}),by];function ly(b,c){uj.call(this,b,2);this.b=c.sa(b[0]);this.c={}}u(ly,uj);ly.prototype.Na=function(b){b=m(b)?ma(b):-1;if(b in this.c)return this.c[b];var c=this.b,d=Tf(c,c);d.strokeStyle="black";d.strokeRect(.5,.5,c+.5,c+.5);d.fillStyle="black";d.textAlign="center";d.textBaseline="middle";d.font="24px sans-serif";d.fillText(nf(this.a),c/2,c/2);return this.c[b]=d.canvas};function my(b){Fj.call(this,{opaque:!1,projection:b.projection,tileGrid:b.tileGrid});this.a=new ax}u(my,Fj);my.prototype.zd=function(){return bx(this.a)};
+my.prototype.se=function(b){cx(this.a,b)};my.prototype.Fb=function(b,c,d){var e=this.hb(b,c,d);if(Fo(this.a,e))return this.a.get(e);b=new ly([b,c,d],this.tileGrid);this.a.set(e,b);return b};function ny(b){dx.call(this,{crossOrigin:b.crossOrigin,projection:Ee("EPSG:3857"),state:"loading",tileLoadFunction:b.tileLoadFunction});this.d=b.wrapX;(new Qw(b.url)).send(void 0,sa(this.a,this))}u(ny,dx);
+ny.prototype.a=function(b){var c=Ee("EPSG:4326"),d=this.g,e;m(b.bounds)&&(e=we(b.bounds,De(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=new fx({extent:Ej(d),maxZoom:g,minZoom:f});this.tileUrlFunction=Zw(d.Bb({extent:e,wrapX:this.d}),Ww(b.tiles));if(m(b.attribution)){c=m(e)?e:c.D();e={};for(var h;f<=g;++f)h=f.toString(),e[h]=[zj(d,c,f)];this.e=[new tf({html:b.attribution,tileRanges:e})]}rj(this,"ready")};function oy(b){Fj.call(this,{projection:Ee("EPSG:3857"),state:"loading"});this.f=m(b.preemptive)?b.preemptive:!0;this.b=Yw;this.a=new ax;this.d=void 0;(new Qw(b.url)).send(void 0,sa(this.Gj,this))}u(oy,Fj);l=oy.prototype;l.zd=function(){return bx(this.a)};l.se=function(b){cx(this.a,b)};l.Gh=function(){return this.d};l.eh=function(b,c,d,e,f){null===this.tileGrid?!0===f?$h(function(){d.call(e,null)}):d.call(e,null):(c=this.tileGrid.Vb(b,c),py(this.Fb(c[0],c[1],c[2],1,this.g),b,d,e,f))};
+l.Gj=function(b){var c=Ee("EPSG:4326"),d=this.g,e;m(b.bounds)&&(e=we(b.bounds,De(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=new fx({extent:Ej(d),maxZoom:g,minZoom:f});this.d=b.template;var h=b.grids;if(null!=h){this.b=Zw(d.Bb({extent:e}),Ww(h));if(m(b.attribution)){c=m(e)?e:c.D();for(e={};f<=g;++f)h=f.toString(),e[h]=[zj(d,c,f)];this.e=[new tf({html:b.attribution,tileRanges:e})]}rj(this,"ready")}else rj(this,"error")};
+l.Fb=function(b,c,d,e,f){var g=this.hb(b,c,d);if(Fo(this.a,g))return this.a.get(g);b=[b,c,d];e=this.b(b,e,f);e=new qy(b,m(e)?0:4,m(e)?e:"",yj(this.tileGrid,b),this.f);this.a.set(g,e);return e};l.He=function(b,c,d){b=this.hb(b,c,d);Fo(this.a,b)&&this.a.get(b)};function qy(b,c,d,e,f){uj.call(this,b,c);this.g=d;this.c=e;this.o=f;this.d=this.e=this.b=null}u(qy,uj);l=qy.prototype;l.Na=function(){return null};
+function ry(b,c){if(null===b.b||null===b.e||null===b.d)return null;var d=b.b[Math.floor((1-(c[1]-b.c[1])/(b.c[3]-b.c[1]))*b.b.length)];if(!ia(d))return null;d=d.charCodeAt(Math.floor((c[0]-b.c[0])/(b.c[2]-b.c[0])*d.length));93<=d&&d--;35<=d&&d--;d=b.e[d-32];return null!=d?b.d[d]:null}function py(b,c,d,e,f){0==b.state&&!0===f?(Wc(b,"change",function(){d.call(e,ry(this,c))},!1,b),sy(b)):!0===f?$h(function(){d.call(e,ry(this,c))},b):d.call(e,ry(b,c))}l.mb=function(){return this.g};
+l.Vh=function(){this.state=3;vj(this)};l.gi=function(b){this.b=b.grid;this.e=b.keys;this.d=b.data;this.state=4;vj(this)};function sy(b){0==b.state&&(b.state=1,(new Qw(b.g)).send(void 0,sa(b.gi,b),sa(b.Vh,b)))}l.load=function(){this.o&&sy(this)};function ty(b){Hx.call(this,{attributions:b.attributions,format:b.format,logo:b.logo,projection:b.projection});this.p=b.tileGrid;this.q=Yw;this.t=this.p.Bb();this.j={};m(b.tileUrlFunction)?(this.q=b.tileUrlFunction,this.l()):m(b.urls)?(this.q=Ww(b.urls),this.l()):m(b.url)&&(this.q=Ww($w(b.url)),this.l())}u(ty,Hx);l=ty.prototype;l.clear=function(){yb(this.j)};
+function uy(b,c,d,e){var f=b.j;b=b.p.Vb(c,d);f=f[b[0]+"/"+b[1]+"/"+b[2]];if(m(f))for(b=0,d=f.length;b<d;++b){var g=f[b];if(g.N().Jb(c[0],c[1])&&e.call(void 0,g))break}}l.Db=function(b,c,d,e){var f=this.p,g=this.j;c=ec(f.a,c,0);b=zj(f,b,c);for(var h,f=b.a;f<=b.d;++f)for(h=b.b;h<=b.c;++h){var k=g[c+"/"+f+"/"+h];if(m(k)){var n,p;n=0;for(p=k.length;n<p;++n){var q=d.call(e,k[n]);if(q)return q}}}};l.va=function(){var b=this.j,c=[],d;for(d in b)db(c,b[d]);return c};
+l.jh=function(b,c){var d=[];uy(this,b,c,function(b){d.push(b)});return d};l.Hb=function(b,c,d){function e(b,c){k[b]=c;rj(this,"ready")}var f=this.t,g=this.p,h=this.q,k=this.j;c=ec(g.a,c,0);b=zj(g,b,c);var g=[c,0,0],n,p;for(n=b.a;n<=b.d;++n)for(p=b.b;p<=b.c;++p){var q=c+"/"+n+"/"+p;if(!(q in k)){g[0]=c;g[1]=n;g[2]=p;f(g,d,g);var r=h(g,1,d);m(r)&&(k[q]=[],Ix(this,r,ta(e,q),ca,this))}}};function vy(b){b=m(b)?b:{};var c=m(b.params)?b.params:{};dx.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,opaque:!x(c,"TRANSPARENT",!0),projection:b.projection,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction,tileUrlFunction:sa(this.Kj,this)});var d=b.urls;!m(d)&&m(b.url)&&(d=$w(b.url));this.f=null!=d?d:[];this.o=m(b.gutter)?b.gutter:0;this.a=c;this.d=!0;this.i=b.serverType;this.p=m(b.hidpi)?b.hidpi:!0;this.j="";wy(this);this.q=Vd();xy(this)}u(vy,dx);l=vy.prototype;
+l.Hj=function(b,c,d,e){d=Ee(d);var f=this.tileGrid;null===f&&(f=Gj(this,d));c=f.Vb(b,c);if(!(f.a.length<=c[0])){var g=f.ka(c[0]),h=yj(f,c,this.q),f=f.sa(c[0]),k=this.o;0!==k&&(f+=2*k,h=Yd(h,g*k,h));k={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:x(this.a,"LAYERS")};Eb(k,this.a,e);e=Math.floor((h[3]-b[1])/g);k[this.d?"I":"X"]=Math.floor((b[0]-h[0])/g);k[this.d?"J":"Y"]=e;return yy(this,c,f,h,1,d,k)}};l.bd=function(){return this.o};
+l.hb=function(b,c,d){return this.j+vy.S.hb.call(this,b,c,d)};l.Ij=function(){return this.a};
+function yy(b,c,d,e,f,g,h){var k=b.f;if(0!=k.length){h.WIDTH=d;h.HEIGHT=d;h[b.d?"CRS":"SRS"]=g.a;"STYLES"in b.a||(h.STYLES=new String(""));if(1!=f)switch(b.i){case "geoserver":h.FORMAT_OPTIONS="dpi:"+(90*f+.5|0);break;case "mapserver":h.MAP_RESOLUTION=90*f;break;case "carmentaserver":case "qgis":h.DPI=90*f}d=g.b;b.d&&"ne"==d.substr(0,2)&&(b=e[0],e[0]=e[1],e[1]=b,b=e[2],e[2]=e[3],e[3]=b);h.BBOX=e.join(",");return xh(zh([1==k.length?k[0]:k[Zb((c[1]<<c[0])+c[2],k.length)]],h))}}
+l.Gc=function(b,c,d){b=vy.S.Gc.call(this,b,c,d);return 1!=c&&this.p&&m(this.i)?b*c+.5|0:b};l.Kh=function(){return this.f};function wy(b){var c=0,d=[],e,f;e=0;for(f=b.f.length;e<f;++e)d[c++]=b.f[e];for(var g in b.a)d[c++]=g+"-"+b.a[g];b.j=d.join("#")}l.Jj=function(b){b=m(b)?$w(b):null;this.zf(b)};l.zf=function(b){this.f=null!=b?b:[];wy(this);this.l()};
+l.Kj=function(b,c,d){var e=this.tileGrid;null===e&&(e=Gj(this,d));if(!(e.a.length<=b[0])){1==c||this.p&&m(this.i)||(c=1);var f=e.ka(b[0]),g=yj(e,b,this.q),e=e.sa(b[0]),h=this.o;0!==h&&(e+=2*h,g=Yd(g,f*h,g));1!=c&&(e=e*c+.5|0);f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Eb(f,this.a);return yy(this,b,e,g,c,d,f)}};l.Lj=function(b){Eb(this.a,b);wy(this);xy(this);this.l()};function xy(b){b.d=0<=Na(x(b.a,"VERSION","1.3.0"),"1.3")};function zy(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,extent:b.extent,format:new Et({defaultDataProjection:b.defaultProjection}),logo:b.logo,object:b.object,projection:b.projection,text:b.text,url:b.url})}u(zy,Jx);function Ay(b){this.b=b.matrixIds;wj.call(this,{origin:b.origin,origins:b.origins,resolutions:b.resolutions,tileSize:b.tileSize,tileSizes:b.tileSizes})}u(Ay,wj);Ay.prototype.g=function(){return this.b};function By(b){function c(b){b="KVP"==f?xh(zh([b],h)):b.replace(/\{(\w+?)\}/g,function(b,c){return c in h?h[c]:b});return function(c){if(null!==c){var d={TileMatrix:g.b[c[0]],TileCol:c[1],TileRow:c[2]};Eb(d,k);c=b;return c="KVP"==f?xh(zh([c],d)):c.replace(/\{(\w+?)\}/g,function(b,c){return d[c]})}}}var d=m(b.version)?b.version:"1.0.0",e=m(b.format)?b.format:"image/jpeg";this.a=m(b.dimensions)?b.dimensions:{};this.d="";Cy(this);var f=m(b.requestEncoding)?b.requestEncoding:"KVP",g=b.tileGrid,h={Layer:b.layer,
+Style:b.style,TileMatrixSet:b.matrixSet};"KVP"==f&&Eb(h,{Service:"WMTS",Request:"GetTile",Version:d,Format:e});var k=this.a,d=Yw,e=b.urls;!m(e)&&m(b.url)&&(e=$w(b.url));m(e)&&(d=Xw(Va(e,c)));var n=Vd(),p=[0,0,0],d=Zw(function(b,c,d){if(g.a.length<=b[0])return null;var e=b[1],f=-b[2]-1,h=yj(g,b,n),k=c.D();null!==k&&c.f&&(c=Math.ceil(re(k)/re(h)),e=Zb(e,c),p[0]=b[0],p[1]=e,p[2]=b[2],h=yj(g,p,n));return!qe(h,k)||qe(h,k)&&(h[0]==k[2]||h[2]==k[0]||h[1]==k[3]||h[3]==k[1])?null:kf(b[0],e,f,d)},d);dx.call(this,
+{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:b.projection,tileClass:b.tileClass,tileGrid:g,tileLoadFunction:b.tileLoadFunction,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:d})}u(By,dx);By.prototype.f=function(){return this.a};By.prototype.hb=function(b,c,d){return this.d+By.S.hb.call(this,b,c,d)};function Cy(b){var c=0,d=[],e;for(e in b.a)d[c++]=e+"-"+b.a[e];b.d=d.join("/")}By.prototype.o=function(b){Eb(this.a,b);Cy(this);this.l()};function Dy(b){var c=m(b)?b:c;wj.call(this,{origin:[0,0],resolutions:c.resolutions})}u(Dy,wj);Dy.prototype.Bb=function(b){b=m(b)?b:{};var c=this.minZoom,d=this.maxZoom,e=null;if(m(b.extent)){var e=Array(d+1),f;for(f=0;f<=d;++f)e[f]=f<c?null:zj(this,b.extent,f)}return function(b,f,k){f=b[0];if(f<c||d<f)return null;var n=Math.pow(2,f),p=b[1];if(0>p||n<=p)return null;b=b[2];return b<-n||-1<b||null!==e&&!qf(e[f],p,-b-1)?null:kf(f,p,-b-1,k)}};function Ey(b){b=m(b)?b:{};var c=b.size,d=c[0],e=c[1],f=[],g=256;switch(m(b.tierSizeCalculation)?b.tierSizeCalculation:"default"){case "default":for(;d>g||e>g;)f.push([Math.ceil(d/g),Math.ceil(e/g)]),g+=g;break;case "truncated":for(;d>g||e>g;)f.push([Math.ceil(d/g),Math.ceil(e/g)]),d>>=1,e>>=1}f.push([1,1]);f.reverse();for(var g=[1],h=[0],e=1,d=f.length;e<d;e++)g.push(1<<e),h.push(f[e-1][0]*f[e-1][1]+h[e-1]);g.reverse();var g=new Dy({resolutions:g}),k=b.url,c=Zw(g.Bb({extent:[0,0,c[0],c[1]]}),function(b){if(null!==
+b){var c=b[0],d=b[1];b=b[2];return k+"TileGroup"+((d+b*f[c][0]+h[c])/256|0)+"/"+c+"-"+d+"-"+b+".jpg"}});dx.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,tileClass:Fy,tileGrid:g,tileUrlFunction:c})}u(Ey,dx);function Fy(b,c,d,e,f){Yu.call(this,b,c,d,e,f);this.e={}}u(Fy,Yu);
+Fy.prototype.Na=function(b){var c=m(b)?ma(b).toString():"";if(c in this.e)return this.e[c];b=Fy.S.Na.call(this,b);if(2==this.state){if(256==b.width&&256==b.height)return this.e[c]=b;var d=Tf(256,256);d.drawImage(b,0,0);return this.e[c]=d.canvas}return b};function Gy(b){b=m(b)?b:{};this.c=m(b.initialSize)?b.initialSize:256;this.b=m(b.maxSize)?b.maxSize:m(va)?va:2048;this.a=m(b.space)?b.space:1;this.e=[new Hy(this.c,this.a)];this.d=this.c;this.f=[new Hy(this.d,this.a)]}
+Gy.prototype.add=function(b,c,d,e,f,g){if(c+this.a>this.b||d+this.a>this.b)return null;e=Iy(this,!1,b,c,d,e,g);if(null===e)return null;var h=null;m(f)&&(h=Iy(this,!0,b,c,d,f,g));return{offsetX:e.offsetX,offsetY:e.offsetY,image:e.image,Wl:null===h?void 0:h.offsetX,Xl:null===h?void 0:h.offsetY,kf:null===h?void 0:h.image}};
+function Iy(b,c,d,e,f,g,h){var k=c?b.f:b.e,n,p,q;p=0;for(q=k.length;p<q;++p){n=k[p];n=n.add(d,e,f,g,h);if(null!==n)return n;null===n&&p===q-1&&(c?(n=Math.min(2*b.d,b.b),b.d=n):(n=Math.min(2*b.c,b.b),b.c=n),n=new Hy(n,b.a),k.push(n),++q)}}function Hy(b,c){this.a=c;this.c=[{x:0,y:0,width:b,height:b}];this.d={};this.b=If("CANVAS");this.b.width=b;this.b.height=b;this.e=this.b.getContext("2d")}Hy.prototype.get=function(b){return x(this.d,b,null)};
+Hy.prototype.add=function(b,c,d,e,f){var g,h,k;h=0;for(k=this.c.length;h<k;++h)if(g=this.c[h],g.width>=c+this.a&&g.height>=d+this.a)return k={offsetX:g.x+this.a,offsetY:g.y+this.a,image:this.b},this.d[b]=k,e.call(f,this.e,g.x+this.a,g.y+this.a),b=h,c=c+this.a,d=d+this.a,f=e=void 0,g.width-c>g.height-d?(e={x:g.x+c,y:g.y,width:g.width-c,height:g.height},f={x:g.x,y:g.y+d,width:c,height:g.height-d},Jy(this,b,e,f)):(e={x:g.x+c,y:g.y,width:g.width-c,height:d},f={x:g.x,y:g.y+d,width:g.width,height:g.height-
+d},Jy(this,b,e,f)),k;return null};function Jy(b,c,d,e){c=[c,1];0<d.width&&0<d.height&&c.push(d);0<e.width&&0<e.height&&c.push(e);b.c.splice.apply(b.c,c)};function Ky(b){this.j=this.b=this.d=null;this.o=m(b.fill)?b.fill:null;this.J=[0,0];this.a=b.points;this.c=m(b.radius)?b.radius:b.radius1;this.f=m(b.radius2)?b.radius2:this.c;this.g=m(b.angle)?b.angle:0;this.e=m(b.stroke)?b.stroke:null;this.F=this.Q=this.t=null;var c=b.atlasManager,d=null,e,f=0;null!==this.e&&(e=wg(this.e.a),f=this.e.c,m(f)||(f=1),d=this.e.b,cg||(d=null));var g=2*(this.c+f)+1,d={strokeStyle:e,Nc:f,size:g,lineDash:d};if(m(c)){g=Math.round(g);e=null===this.o;var h;e&&(h=sa(this.Df,this,
+d));f=this.ub();d=c.add(f,g,g,sa(this.Ef,this,d),h);this.b=d.image;this.J=[d.offsetX,d.offsetY];c=d.image.width;this.j=e?d.kf:this.b}else this.b=If("CANVAS"),this.b.height=g,this.b.width=g,c=g=this.b.width,h=this.b.getContext("2d"),this.Ef(d,h,0,0),null===this.o?(h=this.j=If("CANVAS"),h.height=d.size,h.width=d.size,h=h.getContext("2d"),this.Df(d,h,0,0)):this.j=this.b;this.t=[g/2,g/2];this.Q=[g,g];this.F=[c,c];Rj.call(this,{opacity:1,rotateWithView:!1,rotation:m(b.rotation)?b.rotation:0,scale:1,snapToPixel:m(b.snapToPixel)?
+b.snapToPixel:!0})}u(Ky,Rj);l=Ky.prototype;l.tb=function(){return this.t};l.Qj=function(){return this.g};l.Rj=function(){return this.o};l.te=function(){return this.j};l.yb=function(){return this.b};l.cd=function(){return this.F};l.ue=function(){return 2};l.zb=function(){return this.J};l.Sj=function(){return this.a};l.Tj=function(){return this.c};l.Eh=function(){return this.f};l.ab=function(){return this.Q};l.Uj=function(){return this.e};l.ne=ca;l.load=ca;l.Ge=ca;
+l.Ef=function(b,c,d,e){var f;c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.f!==this.c&&(this.a*=2);for(d=0;d<=this.a;d++)e=2*d*Math.PI/this.a-Math.PI/2+this.g,f=0===d%2?this.c:this.f,c.lineTo(b.size/2+f*Math.cos(e),b.size/2+f*Math.sin(e));null!==this.o&&(c.fillStyle=wg(this.o.a),c.fill());null!==this.e&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()};
+l.Df=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.f!==this.c&&(this.a*=2);var f;for(d=0;d<=this.a;d++)f=2*d*Math.PI/this.a-Math.PI/2+this.g,e=0===d%2?this.c:this.f,c.lineTo(b.size/2+e*Math.cos(f),b.size/2+e*Math.sin(f));c.fillStyle=Ol;c.fill();null!==this.e&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()};
+l.ub=function(){var b=null===this.e?"-":this.e.ub(),c=null===this.o?"-":this.o.ub();if(null===this.d||b!=this.d[1]||c!=this.d[2]||this.c!=this.d[3]||this.f!=this.d[4]||this.g!=this.d[5]||this.a!=this.d[6])this.d=["r"+b+c+(m(this.c)?this.c.toString():"-")+(m(this.f)?this.f.toString():"-")+(m(this.g)?this.g.toString():"-")+(m(this.a)?this.a.toString():"-"),b,c,this.c,this.f,this.g,this.a];return this.d[0]};t("ol.animation.bounce",function(b){var c=b.resolution,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:ff;return function(b,h){if(h.time<d)return h.animate=!0,h.viewHints[0]+=1,!0;if(h.time<d+e){var k=f((h.time-d)/e),n=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*n;h.viewHints[0]+=1;return!0}return!1}},OPENLAYERS);t("ol.animation.pan",gf,OPENLAYERS);t("ol.animation.rotate",hf,OPENLAYERS);t("ol.animation.zoom",jf,OPENLAYERS);
+t("ol.Attribution",tf,OPENLAYERS);tf.prototype.getHTML=tf.prototype.b;pg.prototype.element=pg.prototype.element;t("ol.Collection",B,OPENLAYERS);B.prototype.clear=B.prototype.clear;B.prototype.extend=B.prototype.qf;B.prototype.forEach=B.prototype.forEach;B.prototype.getArray=B.prototype.Mi;B.prototype.item=B.prototype.item;B.prototype.getLength=B.prototype.Ib;B.prototype.insertAt=B.prototype.rd;B.prototype.pop=B.prototype.pop;B.prototype.push=B.prototype.push;B.prototype.remove=B.prototype.remove;
+B.prototype.removeAt=B.prototype.De;B.prototype.setAt=B.prototype.bl;t("ol.coordinate.add",yd,OPENLAYERS);t("ol.coordinate.createStringXY",function(b){return function(c){return Fd(c,b)}},OPENLAYERS);t("ol.coordinate.format",Bd,OPENLAYERS);t("ol.coordinate.rotate",Dd,OPENLAYERS);t("ol.coordinate.toStringHDMS",function(b){return m(b)?Ad(b[1],"NS")+" "+Ad(b[0],"EW"):""},OPENLAYERS);t("ol.coordinate.toStringXY",Fd,OPENLAYERS);t("ol.DeviceOrientation",Q,OPENLAYERS);Q.prototype.getAlpha=Q.prototype.e;
+Q.prototype.getBeta=Q.prototype.f;Q.prototype.getGamma=Q.prototype.g;Q.prototype.getHeading=Q.prototype.i;Q.prototype.getTracking=Q.prototype.d;Q.prototype.setTracking=Q.prototype.b;t("ol.easing.easeIn",function(b){return Math.pow(b,3)},OPENLAYERS);t("ol.easing.easeOut",cf,OPENLAYERS);t("ol.easing.inAndOut",df,OPENLAYERS);t("ol.easing.linear",ef,OPENLAYERS);t("ol.easing.upAndDown",ff,OPENLAYERS);t("ol.extent.boundingExtent",Td,OPENLAYERS);t("ol.extent.buffer",Yd,OPENLAYERS);
+t("ol.extent.containsCoordinate",function(b,c){return ae(b,c[0],c[1])},OPENLAYERS);t("ol.extent.containsExtent",$d,OPENLAYERS);t("ol.extent.containsXY",ae,OPENLAYERS);t("ol.extent.createEmpty",Vd,OPENLAYERS);t("ol.extent.equals",de,OPENLAYERS);t("ol.extent.extend",ee,OPENLAYERS);t("ol.extent.getBottomLeft",he,OPENLAYERS);t("ol.extent.getBottomRight",ie,OPENLAYERS);t("ol.extent.getCenter",ke,OPENLAYERS);t("ol.extent.getHeight",oe,OPENLAYERS);t("ol.extent.getIntersection",pe,OPENLAYERS);
+t("ol.extent.getSize",function(b){return[b[2]-b[0],b[3]-b[1]]},OPENLAYERS);t("ol.extent.getTopLeft",me,OPENLAYERS);t("ol.extent.getTopRight",je,OPENLAYERS);t("ol.extent.getWidth",re,OPENLAYERS);t("ol.extent.intersects",qe,OPENLAYERS);t("ol.extent.isEmpty",se,OPENLAYERS);t("ol.extent.applyTransform",we,OPENLAYERS);t("ol.Feature",R,OPENLAYERS);R.prototype.clone=R.prototype.clone;R.prototype.getGeometry=R.prototype.N;R.prototype.getId=R.prototype.nh;R.prototype.getGeometryName=R.prototype.mh;
+R.prototype.getStyle=R.prototype.Si;R.prototype.getStyleFunction=R.prototype.Ti;R.prototype.setGeometry=R.prototype.Ma;R.prototype.setStyle=R.prototype.i;R.prototype.setId=R.prototype.d;R.prototype.setGeometryName=R.prototype.e;t("ol.FeatureOverlay",sp,OPENLAYERS);sp.prototype.addFeature=sp.prototype.rf;sp.prototype.getFeatures=sp.prototype.Ni;sp.prototype.removeFeature=sp.prototype.wd;sp.prototype.setFeatures=sp.prototype.Mc;sp.prototype.setMap=sp.prototype.setMap;sp.prototype.setStyle=sp.prototype.tf;
+sp.prototype.getStyle=sp.prototype.Oi;sp.prototype.getStyleFunction=sp.prototype.Pi;t("ol.Geolocation",Z,OPENLAYERS);Z.prototype.getAccuracy=Z.prototype.$e;Z.prototype.getAccuracyGeometry=Z.prototype.p;Z.prototype.getAltitude=Z.prototype.q;Z.prototype.getAltitudeAccuracy=Z.prototype.r;Z.prototype.getHeading=Z.prototype.F;Z.prototype.getPosition=Z.prototype.J;Z.prototype.getProjection=Z.prototype.g;Z.prototype.getSpeed=Z.prototype.t;Z.prototype.getTracking=Z.prototype.i;
+Z.prototype.getTrackingOptions=Z.prototype.f;Z.prototype.setProjection=Z.prototype.n;Z.prototype.setTracking=Z.prototype.b;Z.prototype.setTrackingOptions=Z.prototype.j;t("ol.Graticule",Ru,OPENLAYERS);Ru.prototype.getMap=Ru.prototype.Wi;Ru.prototype.getMeridians=Ru.prototype.wh;Ru.prototype.getParallels=Ru.prototype.Bh;Ru.prototype.setMap=Ru.prototype.setMap;t("ol.has.DEVICE_PIXEL_RATIO",ag,OPENLAYERS);t("ol.has.CANVAS",dg,OPENLAYERS);t("ol.has.DEVICE_ORIENTATION",eg,OPENLAYERS);
+t("ol.has.GEOLOCATION",fg,OPENLAYERS);t("ol.has.TOUCH",gg,OPENLAYERS);t("ol.has.WEBGL",$f,OPENLAYERS);Wu.prototype.getImage=Wu.prototype.c;Yu.prototype.getImage=Yu.prototype.Na;t("ol.Kinetic",lk,OPENLAYERS);t("ol.loadingstrategy.all",function(){return[[-Infinity,-Infinity,Infinity,Infinity]]},OPENLAYERS);t("ol.loadingstrategy.bbox",Iw,OPENLAYERS);
+t("ol.loadingstrategy.createTile",function(b){return function(c,d){var e=ec(b.a,d,0),f=zj(b,c,e),g=[],e=[e,0,0];for(e[1]=f.a;e[1]<=f.d;++e[1])for(e[2]=f.b;e[2]<=f.c;++e[2])g.push(yj(b,e));return g}},OPENLAYERS);t("ol.Map",O,OPENLAYERS);O.prototype.addControl=O.prototype.Qg;O.prototype.addInteraction=O.prototype.Rg;O.prototype.addLayer=O.prototype.Se;O.prototype.addOverlay=O.prototype.Te;O.prototype.beforeRender=O.prototype.Ua;O.prototype.forEachFeatureAtPixel=O.prototype.oe;
+O.prototype.getEventCoordinate=O.prototype.ih;O.prototype.getEventPixel=O.prototype.ad;O.prototype.getTarget=O.prototype.qc;O.prototype.getTargetElement=O.prototype.Fh;O.prototype.getCoordinateFromPixel=O.prototype.Ga;O.prototype.getControls=O.prototype.hh;O.prototype.getOverlays=O.prototype.Ah;O.prototype.getInteractions=O.prototype.oh;O.prototype.getLayerGroup=O.prototype.Eb;O.prototype.getLayers=O.prototype.ea;O.prototype.getPixelFromCoordinate=O.prototype.f;O.prototype.getSize=O.prototype.e;
+O.prototype.getView=O.prototype.a;O.prototype.getViewport=O.prototype.Lh;O.prototype.renderSync=O.prototype.Zk;O.prototype.render=O.prototype.render;O.prototype.removeControl=O.prototype.Uk;O.prototype.removeInteraction=O.prototype.Vk;O.prototype.removeLayer=O.prototype.Wk;O.prototype.removeOverlay=O.prototype.Xk;O.prototype.setLayerGroup=O.prototype.cg;O.prototype.setSize=O.prototype.J;O.prototype.setTarget=O.prototype.qa;O.prototype.setView=O.prototype.Ta;O.prototype.updateSize=O.prototype.j;
+cj.prototype.originalEvent=cj.prototype.originalEvent;cj.prototype.pixel=cj.prototype.pixel;cj.prototype.coordinate=cj.prototype.coordinate;cj.prototype.preventDefault=cj.prototype.preventDefault;cj.prototype.stopPropagation=cj.prototype.lb;Zg.prototype.map=Zg.prototype.map;Zg.prototype.frameState=Zg.prototype.frameState;od.prototype.key=od.prototype.key;od.prototype.oldValue=od.prototype.oldValue;pd.prototype.transform=pd.prototype.e;t("ol.Object",td,OPENLAYERS);td.prototype.bindTo=td.prototype.O;
+td.prototype.get=td.prototype.get;td.prototype.getKeys=td.prototype.I;td.prototype.getProperties=td.prototype.L;td.prototype.set=td.prototype.set;td.prototype.setProperties=td.prototype.G;td.prototype.unbind=td.prototype.P;td.prototype.unbindAll=td.prototype.R;t("ol.Observable",md,OPENLAYERS);t("ol.Observable.unByKey",nd,OPENLAYERS);md.prototype.changed=md.prototype.l;md.prototype.getRevision=md.prototype.A;md.prototype.on=md.prototype.u;md.prototype.once=md.prototype.B;md.prototype.un=md.prototype.v;
+md.prototype.unByKey=md.prototype.C;t("ol.WEBGL_MAX_TEXTURE_SIZE",va,OPENLAYERS);t("ol.inherits",u,OPENLAYERS);t("ol.Overlay",P,OPENLAYERS);P.prototype.getElement=P.prototype.d;P.prototype.getMap=P.prototype.e;P.prototype.getOffset=P.prototype.g;P.prototype.getPosition=P.prototype.n;P.prototype.getPositioning=P.prototype.i;P.prototype.setElement=P.prototype.Ee;P.prototype.setMap=P.prototype.setMap;P.prototype.setOffset=P.prototype.j;P.prototype.setPosition=P.prototype.f;
+P.prototype.setPositioning=P.prototype.p;uj.prototype.getTileCoord=uj.prototype.f;t("ol.View",A,OPENLAYERS);A.prototype.constrainCenter=A.prototype.i;A.prototype.constrainResolution=A.prototype.constrainResolution;A.prototype.constrainRotation=A.prototype.constrainRotation;A.prototype.getCenter=A.prototype.a;A.prototype.calculateExtent=A.prototype.g;A.prototype.getProjection=A.prototype.J;A.prototype.getResolution=A.prototype.b;A.prototype.getResolutionForExtent=A.prototype.n;
+A.prototype.getRotation=A.prototype.e;A.prototype.getZoom=A.prototype.Oh;A.prototype.fitExtent=A.prototype.ge;A.prototype.fitGeometry=A.prototype.dh;A.prototype.centerOn=A.prototype.Yg;A.prototype.rotate=A.prototype.rotate;A.prototype.setCenter=A.prototype.Oa;A.prototype.setResolution=A.prototype.d;A.prototype.setRotation=A.prototype.r;A.prototype.setZoom=A.prototype.Q;t("ol.xml.getAllTextContent",Pp,OPENLAYERS);t("ol.xml.parse",iq,OPENLAYERS);t("ol.webgl.Context",Jo,OPENLAYERS);
+Jo.prototype.getGL=Jo.prototype.kk;Jo.prototype.useProgram=Jo.prototype.Gd;t("ol.tilegrid.TileGrid",wj,OPENLAYERS);wj.prototype.getMaxZoom=wj.prototype.ed;wj.prototype.getMinZoom=wj.prototype.fd;wj.prototype.getOrigin=wj.prototype.Lb;wj.prototype.getResolution=wj.prototype.ka;wj.prototype.getResolutions=wj.prototype.Fd;wj.prototype.getTileCoordForCoordAndResolution=wj.prototype.Vb;wj.prototype.getTileCoordForCoordAndZ=wj.prototype.Fc;wj.prototype.getTileSize=wj.prototype.sa;
+t("ol.tilegrid.WMTS",Ay,OPENLAYERS);Ay.prototype.getMatrixIds=Ay.prototype.g;t("ol.tilegrid.XYZ",fx,OPENLAYERS);t("ol.tilegrid.Zoomify",Dy,OPENLAYERS);t("ol.style.AtlasManager",Gy,OPENLAYERS);t("ol.style.Circle",Sl,OPENLAYERS);Sl.prototype.getAnchor=Sl.prototype.tb;Sl.prototype.getFill=Sl.prototype.Mj;Sl.prototype.getImage=Sl.prototype.yb;Sl.prototype.getOrigin=Sl.prototype.zb;Sl.prototype.getRadius=Sl.prototype.Nj;Sl.prototype.getSize=Sl.prototype.ab;Sl.prototype.getStroke=Sl.prototype.Oj;
+t("ol.style.Fill",Rl,OPENLAYERS);Rl.prototype.getColor=Rl.prototype.b;Rl.prototype.setColor=Rl.prototype.d;t("ol.style.Icon",Sj,OPENLAYERS);Sj.prototype.getAnchor=Sj.prototype.tb;Sj.prototype.getImage=Sj.prototype.yb;Sj.prototype.getOrigin=Sj.prototype.zb;Sj.prototype.getSrc=Sj.prototype.Pj;Sj.prototype.getSize=Sj.prototype.ab;t("ol.style.Image",Rj,OPENLAYERS);Rj.prototype.getOpacity=Rj.prototype.Ad;Rj.prototype.getRotateWithView=Rj.prototype.hd;Rj.prototype.getRotation=Rj.prototype.Bd;
+Rj.prototype.getScale=Rj.prototype.Cd;Rj.prototype.getSnapToPixel=Rj.prototype.jd;Rj.prototype.getImage=Rj.prototype.yb;Rj.prototype.setRotation=Rj.prototype.Dd;Rj.prototype.setScale=Rj.prototype.Ed;t("ol.style.RegularShape",Ky,OPENLAYERS);Ky.prototype.getAnchor=Ky.prototype.tb;Ky.prototype.getAngle=Ky.prototype.Qj;Ky.prototype.getFill=Ky.prototype.Rj;Ky.prototype.getImage=Ky.prototype.yb;Ky.prototype.getOrigin=Ky.prototype.zb;Ky.prototype.getPoints=Ky.prototype.Sj;Ky.prototype.getRadius=Ky.prototype.Tj;
+Ky.prototype.getRadius2=Ky.prototype.Eh;Ky.prototype.getSize=Ky.prototype.ab;Ky.prototype.getStroke=Ky.prototype.Uj;t("ol.style.Stroke",Nl,OPENLAYERS);Nl.prototype.getColor=Nl.prototype.Vj;Nl.prototype.getLineCap=Nl.prototype.rh;Nl.prototype.getLineDash=Nl.prototype.Wj;Nl.prototype.getLineJoin=Nl.prototype.sh;Nl.prototype.getMiterLimit=Nl.prototype.xh;Nl.prototype.getWidth=Nl.prototype.Xj;Nl.prototype.setColor=Nl.prototype.Yj;Nl.prototype.setLineCap=Nl.prototype.el;Nl.prototype.setLineDash=Nl.prototype.Zj;
+Nl.prototype.setLineJoin=Nl.prototype.fl;Nl.prototype.setMiterLimit=Nl.prototype.gl;Nl.prototype.setWidth=Nl.prototype.ol;t("ol.style.Style",Tl,OPENLAYERS);Tl.prototype.getGeometry=Tl.prototype.N;Tl.prototype.getGeometryFunction=Tl.prototype.lh;Tl.prototype.getFill=Tl.prototype.$j;Tl.prototype.getImage=Tl.prototype.ak;Tl.prototype.getStroke=Tl.prototype.bk;Tl.prototype.getText=Tl.prototype.ck;Tl.prototype.getZIndex=Tl.prototype.Nh;Tl.prototype.setGeometry=Tl.prototype.Ff;Tl.prototype.setZIndex=Tl.prototype.ql;
+t("ol.style.Text",Jr,OPENLAYERS);Jr.prototype.getFont=Jr.prototype.kh;Jr.prototype.getOffsetX=Jr.prototype.yh;Jr.prototype.getOffsetY=Jr.prototype.zh;Jr.prototype.getFill=Jr.prototype.dk;Jr.prototype.getRotation=Jr.prototype.ek;Jr.prototype.getScale=Jr.prototype.fk;Jr.prototype.getStroke=Jr.prototype.gk;Jr.prototype.getText=Jr.prototype.hk;Jr.prototype.getTextAlign=Jr.prototype.Hh;Jr.prototype.getTextBaseline=Jr.prototype.Ih;Jr.prototype.setFont=Jr.prototype.dl;Jr.prototype.setFill=Jr.prototype.cl;
+Jr.prototype.setRotation=Jr.prototype.ik;Jr.prototype.setScale=Jr.prototype.jk;Jr.prototype.setStroke=Jr.prototype.kl;Jr.prototype.setText=Jr.prototype.ll;Jr.prototype.setTextAlign=Jr.prototype.ml;Jr.prototype.setTextBaseline=Jr.prototype.nl;t("ol.Sphere",xe,OPENLAYERS);t("ol.source.BingMaps",gx,OPENLAYERS);t("ol.source.BingMaps.TOS_ATTRIBUTION",hx,OPENLAYERS);t("ol.source.Cluster",ix,OPENLAYERS);Hx.prototype.readFeatures=Hx.prototype.a;t("ol.source.GeoJSON",Kx,OPENLAYERS);t("ol.source.GPX",Lx,OPENLAYERS);
+t("ol.source.IGC",Mx,OPENLAYERS);t("ol.source.ImageCanvas",Qx,OPENLAYERS);t("ol.source.ImageMapGuide",Rx,OPENLAYERS);Rx.prototype.getParams=Rx.prototype.J;Rx.prototype.updateParams=Rx.prototype.V;t("ol.source.Image",Nx,OPENLAYERS);t("ol.source.ImageStatic",Sx,OPENLAYERS);t("ol.source.ImageVector",Tx,OPENLAYERS);Tx.prototype.getSource=Tx.prototype.wj;Tx.prototype.getStyle=Tx.prototype.xj;Tx.prototype.getStyleFunction=Tx.prototype.yj;Tx.prototype.setStyle=Tx.prototype.yf;t("ol.source.ImageWMS",Vx,OPENLAYERS);
+Vx.prototype.getGetFeatureInfoUrl=Vx.prototype.Bj;Vx.prototype.getParams=Vx.prototype.Cj;Vx.prototype.getUrl=Vx.prototype.Dj;Vx.prototype.setUrl=Vx.prototype.Ej;Vx.prototype.updateParams=Vx.prototype.Fj;t("ol.source.KML",Zx,OPENLAYERS);t("ol.source.MapQuest",cy,OPENLAYERS);t("ol.source.OSM",ay,OPENLAYERS);t("ol.source.OSM.ATTRIBUTION",by,OPENLAYERS);t("ol.source.OSMXML",fy,OPENLAYERS);t("ol.source.ServerVector",gy,OPENLAYERS);gy.prototype.readFeatures=gy.prototype.a;t("ol.source.Source",qj,OPENLAYERS);
+qj.prototype.getAttributions=qj.prototype.Y;qj.prototype.getLogo=qj.prototype.W;qj.prototype.getProjection=qj.prototype.Z;qj.prototype.getState=qj.prototype.$;t("ol.source.Stamen",jy,OPENLAYERS);t("ol.source.StaticVector",Jx,OPENLAYERS);t("ol.source.TileDebug",my,OPENLAYERS);t("ol.source.TileImage",dx,OPENLAYERS);dx.prototype.getTileLoadFunction=dx.prototype.ib;dx.prototype.getTileUrlFunction=dx.prototype.jb;dx.prototype.setTileLoadFunction=dx.prototype.ob;dx.prototype.setTileUrlFunction=dx.prototype.pa;
+t("ol.source.TileJSON",ny,OPENLAYERS);t("ol.source.Tile",Fj,OPENLAYERS);Fj.prototype.getTileGrid=Fj.prototype.za;t("ol.source.TileUTFGrid",oy,OPENLAYERS);oy.prototype.getTemplate=oy.prototype.Gh;oy.prototype.forDataAtCoordinateAndResolution=oy.prototype.eh;t("ol.source.TileVector",ty,OPENLAYERS);ty.prototype.getFeatures=ty.prototype.va;ty.prototype.getFeaturesAtCoordinateAndResolution=ty.prototype.jh;t("ol.source.TileWMS",vy,OPENLAYERS);vy.prototype.getGetFeatureInfoUrl=vy.prototype.Hj;
+vy.prototype.getParams=vy.prototype.Ij;vy.prototype.getUrls=vy.prototype.Kh;vy.prototype.setUrl=vy.prototype.Jj;vy.prototype.setUrls=vy.prototype.zf;vy.prototype.updateParams=vy.prototype.Lj;t("ol.source.TopoJSON",zy,OPENLAYERS);t("ol.source.Vector",aw,OPENLAYERS);aw.prototype.addFeature=aw.prototype.Pa;aw.prototype.addFeatures=aw.prototype.ya;aw.prototype.clear=aw.prototype.clear;aw.prototype.forEachFeature=aw.prototype.Xa;aw.prototype.forEachFeatureInExtent=aw.prototype.ra;
+aw.prototype.forEachFeatureIntersectingExtent=aw.prototype.Fa;aw.prototype.getFeatures=aw.prototype.va;aw.prototype.getFeaturesAtCoordinate=aw.prototype.Ia;aw.prototype.getClosestFeatureToCoordinate=aw.prototype.Ya;aw.prototype.getExtent=aw.prototype.D;aw.prototype.getFeatureById=aw.prototype.Ha;aw.prototype.removeFeature=aw.prototype.$a;dw.prototype.feature=dw.prototype.feature;t("ol.source.WMTS",By,OPENLAYERS);By.prototype.getDimensions=By.prototype.f;By.prototype.updateDimensions=By.prototype.o;
+t("ol.source.XYZ",$x,OPENLAYERS);$x.prototype.setTileUrlFunction=$x.prototype.pa;$x.prototype.setUrl=$x.prototype.a;t("ol.source.Zoomify",Ey,OPENLAYERS);Al.prototype.vectorContext=Al.prototype.vectorContext;Al.prototype.frameState=Al.prototype.frameState;Al.prototype.context=Al.prototype.context;Al.prototype.glContext=Al.prototype.glContext;no.prototype.drawAsync=no.prototype.ic;no.prototype.drawCircleGeometry=no.prototype.jc;no.prototype.drawFeature=no.prototype.ee;
+no.prototype.drawGeometryCollectionGeometry=no.prototype.Zc;no.prototype.drawPointGeometry=no.prototype.rb;no.prototype.drawLineStringGeometry=no.prototype.Cb;no.prototype.drawMultiLineStringGeometry=no.prototype.kc;no.prototype.drawMultiPointGeometry=no.prototype.qb;no.prototype.drawMultiPolygonGeometry=no.prototype.lc;no.prototype.drawPolygonGeometry=no.prototype.Sb;no.prototype.drawText=no.prototype.sb;no.prototype.setFillStrokeStyle=no.prototype.wa;no.prototype.setImageStyle=no.prototype.cb;
+no.prototype.setTextStyle=no.prototype.xa;um.prototype.drawAsync=um.prototype.ic;um.prototype.drawCircleGeometry=um.prototype.jc;um.prototype.drawFeature=um.prototype.ee;um.prototype.drawPointGeometry=um.prototype.rb;um.prototype.drawMultiPointGeometry=um.prototype.qb;um.prototype.drawLineStringGeometry=um.prototype.Cb;um.prototype.drawMultiLineStringGeometry=um.prototype.kc;um.prototype.drawPolygonGeometry=um.prototype.Sb;um.prototype.drawMultiPolygonGeometry=um.prototype.lc;
+um.prototype.setFillStrokeStyle=um.prototype.wa;um.prototype.setImageStyle=um.prototype.cb;um.prototype.setTextStyle=um.prototype.xa;t("ol.proj.common.add",tm,OPENLAYERS);t("ol.proj.METERS_PER_UNIT",Ae,OPENLAYERS);t("ol.proj.Projection",Be,OPENLAYERS);Be.prototype.getCode=Be.prototype.gh;Be.prototype.getExtent=Be.prototype.D;Be.prototype.getUnits=Be.prototype.rj;Be.prototype.getMetersPerUnit=Be.prototype.ie;Be.prototype.getWorldExtent=Be.prototype.Mh;Be.prototype.isGlobal=Be.prototype.wi;
+Be.prototype.setExtent=Be.prototype.sj;Be.prototype.setWorldExtent=Be.prototype.pl;t("ol.proj.addEquivalentProjections",He,OPENLAYERS);t("ol.proj.addProjection",Qe,OPENLAYERS);t("ol.proj.addCoordinateTransforms",Se,OPENLAYERS);t("ol.proj.get",Ee,OPENLAYERS);t("ol.proj.getTransform",Ve,OPENLAYERS);t("ol.proj.transform",function(b,c,d){return Ve(c,d)(b,void 0,b.length)},OPENLAYERS);t("ol.proj.transformExtent",Xe,OPENLAYERS);t("ol.layer.Heatmap",$,OPENLAYERS);$.prototype.getGradient=$.prototype.qa;
+$.prototype.setGradient=$.prototype.fc;t("ol.layer.Image",J,OPENLAYERS);J.prototype.getSource=J.prototype.a;t("ol.layer.Layer",E,OPENLAYERS);E.prototype.getSource=E.prototype.a;E.prototype.setSource=E.prototype.ga;t("ol.layer.Base",D,OPENLAYERS);D.prototype.getBrightness=D.prototype.d;D.prototype.getContrast=D.prototype.e;D.prototype.getHue=D.prototype.f;D.prototype.getExtent=D.prototype.D;D.prototype.getMaxResolution=D.prototype.g;D.prototype.getMinResolution=D.prototype.i;
+D.prototype.getOpacity=D.prototype.j;D.prototype.getSaturation=D.prototype.n;D.prototype.getVisible=D.prototype.b;D.prototype.setBrightness=D.prototype.t;D.prototype.setContrast=D.prototype.F;D.prototype.setHue=D.prototype.J;D.prototype.setExtent=D.prototype.p;D.prototype.setMaxResolution=D.prototype.Q;D.prototype.setMinResolution=D.prototype.V;D.prototype.setOpacity=D.prototype.q;D.prototype.setSaturation=D.prototype.ba;D.prototype.setVisible=D.prototype.ca;t("ol.layer.Group",I,OPENLAYERS);
+I.prototype.getLayers=I.prototype.Yb;I.prototype.setLayers=I.prototype.r;t("ol.layer.Tile",K,OPENLAYERS);K.prototype.getPreload=K.prototype.r;K.prototype.getSource=K.prototype.a;K.prototype.setPreload=K.prototype.oa;K.prototype.getUseInterimTilesOnError=K.prototype.ea;K.prototype.setUseInterimTilesOnError=K.prototype.qa;t("ol.layer.Vector",L,OPENLAYERS);L.prototype.getSource=L.prototype.a;L.prototype.getStyle=L.prototype.Uc;L.prototype.getStyleFunction=L.prototype.Vc;L.prototype.setStyle=L.prototype.oa;
+t("ol.interaction.DoubleClickZoom",sk,OPENLAYERS);t("ol.interaction.DoubleClickZoom.handleEvent",tk,OPENLAYERS);t("ol.interaction.DragAndDrop",Nv,OPENLAYERS);t("ol.interaction.DragAndDrop.handleEvent",dd,OPENLAYERS);Ov.prototype.features=Ov.prototype.features;Ov.prototype.file=Ov.prototype.file;Ov.prototype.projection=Ov.prototype.projection;El.prototype.coordinate=El.prototype.coordinate;t("ol.interaction.DragBox",Fl,OPENLAYERS);Fl.prototype.getGeometry=Fl.prototype.N;
+t("ol.interaction.DragPan",Ek,OPENLAYERS);t("ol.interaction.DragRotateAndZoom",Rv,OPENLAYERS);t("ol.interaction.DragRotate",Ik,OPENLAYERS);t("ol.interaction.DragZoom",Yl,OPENLAYERS);gw.prototype.feature=gw.prototype.feature;t("ol.interaction.Draw",hw,OPENLAYERS);t("ol.interaction.Draw.handleEvent",jw,OPENLAYERS);hw.prototype.finishDrawing=hw.prototype.V;t("ol.interaction.Interaction",ok,OPENLAYERS);ok.prototype.getActive=ok.prototype.a;ok.prototype.setActive=ok.prototype.b;
+t("ol.interaction.defaults",mm,OPENLAYERS);t("ol.interaction.KeyboardPan",Zl,OPENLAYERS);t("ol.interaction.KeyboardPan.handleEvent",$l,OPENLAYERS);t("ol.interaction.KeyboardZoom",am,OPENLAYERS);t("ol.interaction.KeyboardZoom.handleEvent",bm,OPENLAYERS);t("ol.interaction.Modify",uw,OPENLAYERS);t("ol.interaction.Modify.handleEvent",xw,OPENLAYERS);t("ol.interaction.MouseWheelZoom",cm,OPENLAYERS);t("ol.interaction.MouseWheelZoom.handleEvent",dm,OPENLAYERS);t("ol.interaction.PinchRotate",em,OPENLAYERS);
+t("ol.interaction.PinchZoom",im,OPENLAYERS);t("ol.interaction.Pointer",Bk,OPENLAYERS);t("ol.interaction.Pointer.handleEvent",Ck,OPENLAYERS);t("ol.interaction.Select",Dw,OPENLAYERS);Dw.prototype.getFeatures=Dw.prototype.j;t("ol.interaction.Select.handleEvent",Ew,OPENLAYERS);Dw.prototype.setMap=Dw.prototype.setMap;t("ol.geom.Circle",gn,OPENLAYERS);gn.prototype.clone=gn.prototype.clone;gn.prototype.getCenter=gn.prototype.qe;gn.prototype.getExtent=gn.prototype.D;gn.prototype.getRadius=gn.prototype.vf;
+gn.prototype.getType=gn.prototype.H;gn.prototype.setCenter=gn.prototype.kj;gn.prototype.setCenterAndRadius=gn.prototype.ag;gn.prototype.setRadius=gn.prototype.jl;gn.prototype.transform=gn.prototype.e;t("ol.geom.Geometry",Mk,OPENLAYERS);Mk.prototype.clone=Mk.prototype.clone;Mk.prototype.getClosestPoint=Mk.prototype.f;Mk.prototype.getExtent=Mk.prototype.D;Mk.prototype.getType=Mk.prototype.H;Mk.prototype.applyTransform=Mk.prototype.ma;Mk.prototype.intersectsExtent=Mk.prototype.ha;
+Mk.prototype.translate=Mk.prototype.Aa;Mk.prototype.transform=Mk.prototype.e;t("ol.geom.GeometryCollection",jn,OPENLAYERS);jn.prototype.clone=jn.prototype.clone;jn.prototype.getExtent=jn.prototype.D;jn.prototype.getGeometries=jn.prototype.af;jn.prototype.getType=jn.prototype.H;jn.prototype.intersectsExtent=jn.prototype.ha;jn.prototype.setGeometries=jn.prototype.bg;jn.prototype.applyTransform=jn.prototype.ma;jn.prototype.translate=jn.prototype.Aa;t("ol.geom.LinearRing",hl,OPENLAYERS);
+hl.prototype.clone=hl.prototype.clone;hl.prototype.getArea=hl.prototype.nj;hl.prototype.getCoordinates=hl.prototype.K;hl.prototype.getType=hl.prototype.H;hl.prototype.setCoordinates=hl.prototype.U;t("ol.geom.LineString",M,OPENLAYERS);M.prototype.appendCoordinate=M.prototype.Sg;M.prototype.clone=M.prototype.clone;M.prototype.getCoordinateAtM=M.prototype.lj;M.prototype.getCoordinates=M.prototype.K;M.prototype.getLength=M.prototype.mj;M.prototype.getType=M.prototype.H;M.prototype.intersectsExtent=M.prototype.ha;
+M.prototype.setCoordinates=M.prototype.U;t("ol.geom.MultiLineString",rn,OPENLAYERS);rn.prototype.appendLineString=rn.prototype.Tg;rn.prototype.clone=rn.prototype.clone;rn.prototype.getCoordinateAtM=rn.prototype.oj;rn.prototype.getCoordinates=rn.prototype.K;rn.prototype.getLineString=rn.prototype.th;rn.prototype.getLineStrings=rn.prototype.Ec;rn.prototype.getType=rn.prototype.H;rn.prototype.intersectsExtent=rn.prototype.ha;rn.prototype.setCoordinates=rn.prototype.U;t("ol.geom.MultiPoint",un,OPENLAYERS);
+un.prototype.appendPoint=un.prototype.Vg;un.prototype.clone=un.prototype.clone;un.prototype.getCoordinates=un.prototype.K;un.prototype.getPoint=un.prototype.Ch;un.prototype.getPoints=un.prototype.xd;un.prototype.getType=un.prototype.H;un.prototype.intersectsExtent=un.prototype.ha;un.prototype.setCoordinates=un.prototype.U;t("ol.geom.MultiPolygon",vn,OPENLAYERS);vn.prototype.appendPolygon=vn.prototype.Wg;vn.prototype.clone=vn.prototype.clone;vn.prototype.getArea=vn.prototype.pj;
+vn.prototype.getCoordinates=vn.prototype.K;vn.prototype.getInteriorPoints=vn.prototype.qh;vn.prototype.getPolygon=vn.prototype.Dh;vn.prototype.getPolygons=vn.prototype.gd;vn.prototype.getType=vn.prototype.H;vn.prototype.intersectsExtent=vn.prototype.ha;vn.prototype.setCoordinates=vn.prototype.U;t("ol.geom.Point",jl,OPENLAYERS);jl.prototype.clone=jl.prototype.clone;jl.prototype.getCoordinates=jl.prototype.K;jl.prototype.getType=jl.prototype.H;jl.prototype.intersectsExtent=jl.prototype.ha;
+jl.prototype.setCoordinates=jl.prototype.U;t("ol.geom.Polygon",H,OPENLAYERS);H.prototype.appendLinearRing=H.prototype.Ug;H.prototype.clone=H.prototype.clone;H.prototype.getArea=H.prototype.qj;H.prototype.getCoordinates=H.prototype.K;H.prototype.getInteriorPoint=H.prototype.ph;H.prototype.getLinearRingCount=H.prototype.vh;H.prototype.getLinearRing=H.prototype.uh;H.prototype.getLinearRings=H.prototype.dd;H.prototype.getType=H.prototype.H;H.prototype.intersectsExtent=H.prototype.ha;
+H.prototype.setCoordinates=H.prototype.U;t("ol.geom.Polygon.circular",zl,OPENLAYERS);t("ol.geom.Polygon.fromExtent",function(b){var c=b[0],d=b[1],e=b[2];b=b[3];c=[c,d,c,b,e,b,e,d,c,d];d=new H(null);wl(d,"XY",c,[c.length]);return d},OPENLAYERS);t("ol.geom.SimpleGeometry",Ok,OPENLAYERS);Ok.prototype.getExtent=Ok.prototype.D;Ok.prototype.getFirstCoordinate=Ok.prototype.vb;Ok.prototype.getLastCoordinate=Ok.prototype.wb;Ok.prototype.getLayout=Ok.prototype.xb;Ok.prototype.applyTransform=Ok.prototype.ma;
+Ok.prototype.translate=Ok.prototype.Aa;t("ol.format.Feature",up,OPENLAYERS);t("ol.format.GeoJSON",Ep,OPENLAYERS);Ep.prototype.readFeature=Ep.prototype.Nb;Ep.prototype.readFeatures=Ep.prototype.ja;Ep.prototype.readGeometry=Ep.prototype.Jc;Ep.prototype.readProjection=Ep.prototype.Ba;Ep.prototype.writeFeature=Ep.prototype.Pd;Ep.prototype.writeFeatureObject=Ep.prototype.a;Ep.prototype.writeFeatures=Ep.prototype.Qb;Ep.prototype.writeFeaturesObject=Ep.prototype.d;Ep.prototype.writeGeometry=Ep.prototype.Qc;
+Ep.prototype.writeGeometryObject=Ep.prototype.e;t("ol.format.GPX",Vq,OPENLAYERS);Vq.prototype.readFeature=Vq.prototype.Nb;Vq.prototype.readFeatures=Vq.prototype.ja;Vq.prototype.readProjection=Vq.prototype.Ba;Vq.prototype.writeFeatures=Vq.prototype.Qb;Vq.prototype.writeFeaturesNode=Vq.prototype.a;t("ol.format.IGC",Fr,OPENLAYERS);Fr.prototype.readFeature=Fr.prototype.Nb;Fr.prototype.readFeatures=Fr.prototype.ja;Fr.prototype.readProjection=Fr.prototype.Ba;t("ol.format.KML",Kr,OPENLAYERS);
+Kr.prototype.readFeature=Kr.prototype.Nb;Kr.prototype.readFeatures=Kr.prototype.ja;Kr.prototype.readName=Kr.prototype.Nk;Kr.prototype.readProjection=Kr.prototype.Ba;Kr.prototype.writeFeatures=Kr.prototype.Qb;Kr.prototype.writeFeaturesNode=Kr.prototype.a;t("ol.format.OSMXML",ot,OPENLAYERS);ot.prototype.readFeatures=ot.prototype.ja;ot.prototype.readProjection=ot.prototype.Ba;t("ol.format.Polyline",zt,OPENLAYERS);t("ol.format.Polyline.encodeDeltas",At,OPENLAYERS);
+t("ol.format.Polyline.decodeDeltas",Ct,OPENLAYERS);t("ol.format.Polyline.encodeFloats",Bt,OPENLAYERS);t("ol.format.Polyline.decodeFloats",Dt,OPENLAYERS);zt.prototype.readFeature=zt.prototype.Nb;zt.prototype.readFeatures=zt.prototype.ja;zt.prototype.readGeometry=zt.prototype.Jc;zt.prototype.readProjection=zt.prototype.Ba;zt.prototype.writeGeometry=zt.prototype.Qc;t("ol.format.TopoJSON",Et,OPENLAYERS);Et.prototype.readFeatures=Et.prototype.ja;Et.prototype.readProjection=Et.prototype.Ba;
+t("ol.format.WFS",Kt,OPENLAYERS);Kt.prototype.readFeatures=Kt.prototype.ja;Kt.prototype.readTransactionResponse=Kt.prototype.g;Kt.prototype.readFeatureCollectionMetadata=Kt.prototype.f;Kt.prototype.writeGetFeature=Kt.prototype.n;Kt.prototype.writeTransaction=Kt.prototype.j;Kt.prototype.readProjection=Kt.prototype.Ba;t("ol.format.WKT",Xt,OPENLAYERS);Xt.prototype.readFeature=Xt.prototype.Nb;Xt.prototype.readFeatures=Xt.prototype.ja;Xt.prototype.readGeometry=Xt.prototype.Jc;
+Xt.prototype.writeFeature=Xt.prototype.Pd;Xt.prototype.writeFeatures=Xt.prototype.Qb;Xt.prototype.writeGeometry=Xt.prototype.Qc;t("ol.format.WMSCapabilities",ou,OPENLAYERS);ou.prototype.read=ou.prototype.a;t("ol.format.WMSGetFeatureInfo",Lu,OPENLAYERS);Lu.prototype.readFeatures=Lu.prototype.ja;t("ol.format.GML2",Uq,OPENLAYERS);t("ol.format.GML3",X,OPENLAYERS);X.prototype.writeGeometryNode=X.prototype.o;X.prototype.writeFeatures=X.prototype.Qb;X.prototype.writeFeaturesNode=X.prototype.a;
+t("ol.format.GML",X,OPENLAYERS);X.prototype.writeFeatures=X.prototype.Qb;X.prototype.writeFeaturesNode=X.prototype.a;t("ol.format.GMLBase",zq,OPENLAYERS);zq.prototype.readFeatures=zq.prototype.ja;t("ol.events.condition.altKeyOnly",function(b){b=b.a;return b.c&&!b.g&&!b.d},OPENLAYERS);t("ol.events.condition.altShiftKeysOnly",uk,OPENLAYERS);t("ol.events.condition.always",dd,OPENLAYERS);t("ol.events.condition.click",function(b){return b.type==gj},OPENLAYERS);t("ol.events.condition.mouseMove",vk,OPENLAYERS);
+t("ol.events.condition.never",cd,OPENLAYERS);t("ol.events.condition.singleClick",wk,OPENLAYERS);t("ol.events.condition.noModifierKeys",xk,OPENLAYERS);t("ol.events.condition.platformModifierKeyOnly",function(b){b=b.a;return!b.c&&b.g&&!b.d},OPENLAYERS);t("ol.events.condition.shiftKeyOnly",yk,OPENLAYERS);t("ol.events.condition.targetNotEditable",zk,OPENLAYERS);t("ol.events.condition.mouseOnly",Ak,OPENLAYERS);t("ol.dom.Input",rp,OPENLAYERS);rp.prototype.getChecked=rp.prototype.a;
+rp.prototype.getValue=rp.prototype.b;rp.prototype.setValue=rp.prototype.e;rp.prototype.setChecked=rp.prototype.d;t("ol.control.Attribution",ah,OPENLAYERS);t("ol.control.Attribution.render",bh,OPENLAYERS);ah.prototype.getCollapsible=ah.prototype.bj;ah.prototype.setCollapsible=ah.prototype.ej;ah.prototype.setCollapsed=ah.prototype.dj;ah.prototype.getCollapsed=ah.prototype.aj;t("ol.control.Control",$g,OPENLAYERS);$g.prototype.getMap=$g.prototype.d;$g.prototype.setMap=$g.prototype.setMap;
+t("ol.control.defaults",gh,OPENLAYERS);t("ol.control.FullScreen",lh,OPENLAYERS);t("ol.control.MousePosition",mh,OPENLAYERS);t("ol.control.MousePosition.render",nh,OPENLAYERS);mh.prototype.getCoordinateFormat=mh.prototype.g;mh.prototype.getProjection=mh.prototype.p;mh.prototype.setMap=mh.prototype.setMap;mh.prototype.setCoordinateFormat=mh.prototype.r;mh.prototype.setProjection=mh.prototype.q;t("ol.control.OverviewMap",Ro,OPENLAYERS);Ro.prototype.setMap=Ro.prototype.setMap;
+t("ol.control.OverviewMap.render",So,OPENLAYERS);Ro.prototype.getCollapsible=Ro.prototype.gj;Ro.prototype.setCollapsible=Ro.prototype.jj;Ro.prototype.setCollapsed=Ro.prototype.ij;Ro.prototype.getCollapsed=Ro.prototype.fj;t("ol.control.Rotate",dh,OPENLAYERS);t("ol.control.Rotate.render",eh,OPENLAYERS);t("ol.control.ScaleLine",Xo,OPENLAYERS);Xo.prototype.getUnits=Xo.prototype.j;t("ol.control.ScaleLine.render",Yo,OPENLAYERS);Xo.prototype.setUnits=Xo.prototype.p;t("ol.control.Zoom",fh,OPENLAYERS);
+t("ol.control.ZoomSlider",lp,OPENLAYERS);t("ol.control.ZoomSlider.render",np,OPENLAYERS);t("ol.control.ZoomToExtent",qp,OPENLAYERS);t("ol.color.asArray",ug,OPENLAYERS);t("ol.color.asString",wg,OPENLAYERS);td.prototype.changed=td.prototype.l;td.prototype.getRevision=td.prototype.A;td.prototype.on=td.prototype.u;td.prototype.once=td.prototype.B;td.prototype.un=td.prototype.v;td.prototype.unByKey=td.prototype.C;B.prototype.bindTo=B.prototype.O;B.prototype.get=B.prototype.get;B.prototype.getKeys=B.prototype.I;
+B.prototype.getProperties=B.prototype.L;B.prototype.set=B.prototype.set;B.prototype.setProperties=B.prototype.G;B.prototype.unbind=B.prototype.P;B.prototype.unbindAll=B.prototype.R;B.prototype.changed=B.prototype.l;B.prototype.getRevision=B.prototype.A;B.prototype.on=B.prototype.u;B.prototype.once=B.prototype.B;B.prototype.un=B.prototype.v;B.prototype.unByKey=B.prototype.C;Q.prototype.bindTo=Q.prototype.O;Q.prototype.get=Q.prototype.get;Q.prototype.getKeys=Q.prototype.I;
+Q.prototype.getProperties=Q.prototype.L;Q.prototype.set=Q.prototype.set;Q.prototype.setProperties=Q.prototype.G;Q.prototype.unbind=Q.prototype.P;Q.prototype.unbindAll=Q.prototype.R;Q.prototype.changed=Q.prototype.l;Q.prototype.getRevision=Q.prototype.A;Q.prototype.on=Q.prototype.u;Q.prototype.once=Q.prototype.B;Q.prototype.un=Q.prototype.v;Q.prototype.unByKey=Q.prototype.C;R.prototype.bindTo=R.prototype.O;R.prototype.get=R.prototype.get;R.prototype.getKeys=R.prototype.I;
+R.prototype.getProperties=R.prototype.L;R.prototype.set=R.prototype.set;R.prototype.setProperties=R.prototype.G;R.prototype.unbind=R.prototype.P;R.prototype.unbindAll=R.prototype.R;R.prototype.changed=R.prototype.l;R.prototype.getRevision=R.prototype.A;R.prototype.on=R.prototype.u;R.prototype.once=R.prototype.B;R.prototype.un=R.prototype.v;R.prototype.unByKey=R.prototype.C;Z.prototype.bindTo=Z.prototype.O;Z.prototype.get=Z.prototype.get;Z.prototype.getKeys=Z.prototype.I;
+Z.prototype.getProperties=Z.prototype.L;Z.prototype.set=Z.prototype.set;Z.prototype.setProperties=Z.prototype.G;Z.prototype.unbind=Z.prototype.P;Z.prototype.unbindAll=Z.prototype.R;Z.prototype.changed=Z.prototype.l;Z.prototype.getRevision=Z.prototype.A;Z.prototype.on=Z.prototype.u;Z.prototype.once=Z.prototype.B;Z.prototype.un=Z.prototype.v;Z.prototype.unByKey=Z.prototype.C;Yu.prototype.getTileCoord=Yu.prototype.f;O.prototype.bindTo=O.prototype.O;O.prototype.get=O.prototype.get;
+O.prototype.getKeys=O.prototype.I;O.prototype.getProperties=O.prototype.L;O.prototype.set=O.prototype.set;O.prototype.setProperties=O.prototype.G;O.prototype.unbind=O.prototype.P;O.prototype.unbindAll=O.prototype.R;O.prototype.changed=O.prototype.l;O.prototype.getRevision=O.prototype.A;O.prototype.on=O.prototype.u;O.prototype.once=O.prototype.B;O.prototype.un=O.prototype.v;O.prototype.unByKey=O.prototype.C;cj.prototype.map=cj.prototype.map;cj.prototype.frameState=cj.prototype.frameState;
+dj.prototype.originalEvent=dj.prototype.originalEvent;dj.prototype.pixel=dj.prototype.pixel;dj.prototype.coordinate=dj.prototype.coordinate;dj.prototype.preventDefault=dj.prototype.preventDefault;dj.prototype.stopPropagation=dj.prototype.lb;dj.prototype.map=dj.prototype.map;dj.prototype.frameState=dj.prototype.frameState;P.prototype.bindTo=P.prototype.O;P.prototype.get=P.prototype.get;P.prototype.getKeys=P.prototype.I;P.prototype.getProperties=P.prototype.L;P.prototype.set=P.prototype.set;
+P.prototype.setProperties=P.prototype.G;P.prototype.unbind=P.prototype.P;P.prototype.unbindAll=P.prototype.R;P.prototype.changed=P.prototype.l;P.prototype.getRevision=P.prototype.A;P.prototype.on=P.prototype.u;P.prototype.once=P.prototype.B;P.prototype.un=P.prototype.v;P.prototype.unByKey=P.prototype.C;A.prototype.bindTo=A.prototype.O;A.prototype.get=A.prototype.get;A.prototype.getKeys=A.prototype.I;A.prototype.getProperties=A.prototype.L;A.prototype.set=A.prototype.set;
+A.prototype.setProperties=A.prototype.G;A.prototype.unbind=A.prototype.P;A.prototype.unbindAll=A.prototype.R;A.prototype.changed=A.prototype.l;A.prototype.getRevision=A.prototype.A;A.prototype.on=A.prototype.u;A.prototype.once=A.prototype.B;A.prototype.un=A.prototype.v;A.prototype.unByKey=A.prototype.C;Ay.prototype.getMaxZoom=Ay.prototype.ed;Ay.prototype.getMinZoom=Ay.prototype.fd;Ay.prototype.getOrigin=Ay.prototype.Lb;Ay.prototype.getResolution=Ay.prototype.ka;Ay.prototype.getResolutions=Ay.prototype.Fd;
+Ay.prototype.getTileCoordForCoordAndResolution=Ay.prototype.Vb;Ay.prototype.getTileCoordForCoordAndZ=Ay.prototype.Fc;Ay.prototype.getTileSize=Ay.prototype.sa;fx.prototype.getMaxZoom=fx.prototype.ed;fx.prototype.getMinZoom=fx.prototype.fd;fx.prototype.getOrigin=fx.prototype.Lb;fx.prototype.getResolution=fx.prototype.ka;fx.prototype.getResolutions=fx.prototype.Fd;fx.prototype.getTileCoordForCoordAndResolution=fx.prototype.Vb;fx.prototype.getTileCoordForCoordAndZ=fx.prototype.Fc;
+fx.prototype.getTileSize=fx.prototype.sa;Dy.prototype.getMaxZoom=Dy.prototype.ed;Dy.prototype.getMinZoom=Dy.prototype.fd;Dy.prototype.getOrigin=Dy.prototype.Lb;Dy.prototype.getResolution=Dy.prototype.ka;Dy.prototype.getResolutions=Dy.prototype.Fd;Dy.prototype.getTileCoordForCoordAndResolution=Dy.prototype.Vb;Dy.prototype.getTileCoordForCoordAndZ=Dy.prototype.Fc;Dy.prototype.getTileSize=Dy.prototype.sa;Sl.prototype.getOpacity=Sl.prototype.Ad;Sl.prototype.getRotateWithView=Sl.prototype.hd;
+Sl.prototype.getRotation=Sl.prototype.Bd;Sl.prototype.getScale=Sl.prototype.Cd;Sl.prototype.getSnapToPixel=Sl.prototype.jd;Sl.prototype.setRotation=Sl.prototype.Dd;Sl.prototype.setScale=Sl.prototype.Ed;Sj.prototype.getOpacity=Sj.prototype.Ad;Sj.prototype.getRotateWithView=Sj.prototype.hd;Sj.prototype.getRotation=Sj.prototype.Bd;Sj.prototype.getScale=Sj.prototype.Cd;Sj.prototype.getSnapToPixel=Sj.prototype.jd;Sj.prototype.setRotation=Sj.prototype.Dd;Sj.prototype.setScale=Sj.prototype.Ed;
+Ky.prototype.getOpacity=Ky.prototype.Ad;Ky.prototype.getRotateWithView=Ky.prototype.hd;Ky.prototype.getRotation=Ky.prototype.Bd;Ky.prototype.getScale=Ky.prototype.Cd;Ky.prototype.getSnapToPixel=Ky.prototype.jd;Ky.prototype.setRotation=Ky.prototype.Dd;Ky.prototype.setScale=Ky.prototype.Ed;qj.prototype.changed=qj.prototype.l;qj.prototype.getRevision=qj.prototype.A;qj.prototype.on=qj.prototype.u;qj.prototype.once=qj.prototype.B;qj.prototype.un=qj.prototype.v;qj.prototype.unByKey=qj.prototype.C;
+Fj.prototype.getAttributions=Fj.prototype.Y;Fj.prototype.getLogo=Fj.prototype.W;Fj.prototype.getProjection=Fj.prototype.Z;Fj.prototype.getState=Fj.prototype.$;Fj.prototype.changed=Fj.prototype.l;Fj.prototype.getRevision=Fj.prototype.A;Fj.prototype.on=Fj.prototype.u;Fj.prototype.once=Fj.prototype.B;Fj.prototype.un=Fj.prototype.v;Fj.prototype.unByKey=Fj.prototype.C;dx.prototype.getTileGrid=dx.prototype.za;dx.prototype.getAttributions=dx.prototype.Y;dx.prototype.getLogo=dx.prototype.W;
+dx.prototype.getProjection=dx.prototype.Z;dx.prototype.getState=dx.prototype.$;dx.prototype.changed=dx.prototype.l;dx.prototype.getRevision=dx.prototype.A;dx.prototype.on=dx.prototype.u;dx.prototype.once=dx.prototype.B;dx.prototype.un=dx.prototype.v;dx.prototype.unByKey=dx.prototype.C;gx.prototype.getTileLoadFunction=gx.prototype.ib;gx.prototype.getTileUrlFunction=gx.prototype.jb;gx.prototype.setTileLoadFunction=gx.prototype.ob;gx.prototype.setTileUrlFunction=gx.prototype.pa;
+gx.prototype.getTileGrid=gx.prototype.za;gx.prototype.getAttributions=gx.prototype.Y;gx.prototype.getLogo=gx.prototype.W;gx.prototype.getProjection=gx.prototype.Z;gx.prototype.getState=gx.prototype.$;gx.prototype.changed=gx.prototype.l;gx.prototype.getRevision=gx.prototype.A;gx.prototype.on=gx.prototype.u;gx.prototype.once=gx.prototype.B;gx.prototype.un=gx.prototype.v;gx.prototype.unByKey=gx.prototype.C;aw.prototype.getAttributions=aw.prototype.Y;aw.prototype.getLogo=aw.prototype.W;
+aw.prototype.getProjection=aw.prototype.Z;aw.prototype.getState=aw.prototype.$;aw.prototype.changed=aw.prototype.l;aw.prototype.getRevision=aw.prototype.A;aw.prototype.on=aw.prototype.u;aw.prototype.once=aw.prototype.B;aw.prototype.un=aw.prototype.v;aw.prototype.unByKey=aw.prototype.C;ix.prototype.addFeature=ix.prototype.Pa;ix.prototype.addFeatures=ix.prototype.ya;ix.prototype.clear=ix.prototype.clear;ix.prototype.forEachFeature=ix.prototype.Xa;ix.prototype.forEachFeatureInExtent=ix.prototype.ra;
+ix.prototype.forEachFeatureIntersectingExtent=ix.prototype.Fa;ix.prototype.getFeatures=ix.prototype.va;ix.prototype.getFeaturesAtCoordinate=ix.prototype.Ia;ix.prototype.getClosestFeatureToCoordinate=ix.prototype.Ya;ix.prototype.getExtent=ix.prototype.D;ix.prototype.getFeatureById=ix.prototype.Ha;ix.prototype.removeFeature=ix.prototype.$a;ix.prototype.getAttributions=ix.prototype.Y;ix.prototype.getLogo=ix.prototype.W;ix.prototype.getProjection=ix.prototype.Z;ix.prototype.getState=ix.prototype.$;
+ix.prototype.changed=ix.prototype.l;ix.prototype.getRevision=ix.prototype.A;ix.prototype.on=ix.prototype.u;ix.prototype.once=ix.prototype.B;ix.prototype.un=ix.prototype.v;ix.prototype.unByKey=ix.prototype.C;Hx.prototype.addFeature=Hx.prototype.Pa;Hx.prototype.addFeatures=Hx.prototype.ya;Hx.prototype.clear=Hx.prototype.clear;Hx.prototype.forEachFeature=Hx.prototype.Xa;Hx.prototype.forEachFeatureInExtent=Hx.prototype.ra;Hx.prototype.forEachFeatureIntersectingExtent=Hx.prototype.Fa;
+Hx.prototype.getFeatures=Hx.prototype.va;Hx.prototype.getFeaturesAtCoordinate=Hx.prototype.Ia;Hx.prototype.getClosestFeatureToCoordinate=Hx.prototype.Ya;Hx.prototype.getExtent=Hx.prototype.D;Hx.prototype.getFeatureById=Hx.prototype.Ha;Hx.prototype.removeFeature=Hx.prototype.$a;Hx.prototype.getAttributions=Hx.prototype.Y;Hx.prototype.getLogo=Hx.prototype.W;Hx.prototype.getProjection=Hx.prototype.Z;Hx.prototype.getState=Hx.prototype.$;Hx.prototype.changed=Hx.prototype.l;Hx.prototype.getRevision=Hx.prototype.A;
+Hx.prototype.on=Hx.prototype.u;Hx.prototype.once=Hx.prototype.B;Hx.prototype.un=Hx.prototype.v;Hx.prototype.unByKey=Hx.prototype.C;Jx.prototype.readFeatures=Jx.prototype.a;Jx.prototype.addFeature=Jx.prototype.Pa;Jx.prototype.addFeatures=Jx.prototype.ya;Jx.prototype.clear=Jx.prototype.clear;Jx.prototype.forEachFeature=Jx.prototype.Xa;Jx.prototype.forEachFeatureInExtent=Jx.prototype.ra;Jx.prototype.forEachFeatureIntersectingExtent=Jx.prototype.Fa;Jx.prototype.getFeatures=Jx.prototype.va;
+Jx.prototype.getFeaturesAtCoordinate=Jx.prototype.Ia;Jx.prototype.getClosestFeatureToCoordinate=Jx.prototype.Ya;Jx.prototype.getExtent=Jx.prototype.D;Jx.prototype.getFeatureById=Jx.prototype.Ha;Jx.prototype.removeFeature=Jx.prototype.$a;Jx.prototype.getAttributions=Jx.prototype.Y;Jx.prototype.getLogo=Jx.prototype.W;Jx.prototype.getProjection=Jx.prototype.Z;Jx.prototype.getState=Jx.prototype.$;Jx.prototype.changed=Jx.prototype.l;Jx.prototype.getRevision=Jx.prototype.A;Jx.prototype.on=Jx.prototype.u;
+Jx.prototype.once=Jx.prototype.B;Jx.prototype.un=Jx.prototype.v;Jx.prototype.unByKey=Jx.prototype.C;Kx.prototype.readFeatures=Kx.prototype.a;Kx.prototype.addFeature=Kx.prototype.Pa;Kx.prototype.addFeatures=Kx.prototype.ya;Kx.prototype.clear=Kx.prototype.clear;Kx.prototype.forEachFeature=Kx.prototype.Xa;Kx.prototype.forEachFeatureInExtent=Kx.prototype.ra;Kx.prototype.forEachFeatureIntersectingExtent=Kx.prototype.Fa;Kx.prototype.getFeatures=Kx.prototype.va;Kx.prototype.getFeaturesAtCoordinate=Kx.prototype.Ia;
+Kx.prototype.getClosestFeatureToCoordinate=Kx.prototype.Ya;Kx.prototype.getExtent=Kx.prototype.D;Kx.prototype.getFeatureById=Kx.prototype.Ha;Kx.prototype.removeFeature=Kx.prototype.$a;Kx.prototype.getAttributions=Kx.prototype.Y;Kx.prototype.getLogo=Kx.prototype.W;Kx.prototype.getProjection=Kx.prototype.Z;Kx.prototype.getState=Kx.prototype.$;Kx.prototype.changed=Kx.prototype.l;Kx.prototype.getRevision=Kx.prototype.A;Kx.prototype.on=Kx.prototype.u;Kx.prototype.once=Kx.prototype.B;Kx.prototype.un=Kx.prototype.v;
+Kx.prototype.unByKey=Kx.prototype.C;Lx.prototype.readFeatures=Lx.prototype.a;Lx.prototype.addFeature=Lx.prototype.Pa;Lx.prototype.addFeatures=Lx.prototype.ya;Lx.prototype.clear=Lx.prototype.clear;Lx.prototype.forEachFeature=Lx.prototype.Xa;Lx.prototype.forEachFeatureInExtent=Lx.prototype.ra;Lx.prototype.forEachFeatureIntersectingExtent=Lx.prototype.Fa;Lx.prototype.getFeatures=Lx.prototype.va;Lx.prototype.getFeaturesAtCoordinate=Lx.prototype.Ia;Lx.prototype.getClosestFeatureToCoordinate=Lx.prototype.Ya;
+Lx.prototype.getExtent=Lx.prototype.D;Lx.prototype.getFeatureById=Lx.prototype.Ha;Lx.prototype.removeFeature=Lx.prototype.$a;Lx.prototype.getAttributions=Lx.prototype.Y;Lx.prototype.getLogo=Lx.prototype.W;Lx.prototype.getProjection=Lx.prototype.Z;Lx.prototype.getState=Lx.prototype.$;Lx.prototype.changed=Lx.prototype.l;Lx.prototype.getRevision=Lx.prototype.A;Lx.prototype.on=Lx.prototype.u;Lx.prototype.once=Lx.prototype.B;Lx.prototype.un=Lx.prototype.v;Lx.prototype.unByKey=Lx.prototype.C;
+Mx.prototype.readFeatures=Mx.prototype.a;Mx.prototype.addFeature=Mx.prototype.Pa;Mx.prototype.addFeatures=Mx.prototype.ya;Mx.prototype.clear=Mx.prototype.clear;Mx.prototype.forEachFeature=Mx.prototype.Xa;Mx.prototype.forEachFeatureInExtent=Mx.prototype.ra;Mx.prototype.forEachFeatureIntersectingExtent=Mx.prototype.Fa;Mx.prototype.getFeatures=Mx.prototype.va;Mx.prototype.getFeaturesAtCoordinate=Mx.prototype.Ia;Mx.prototype.getClosestFeatureToCoordinate=Mx.prototype.Ya;Mx.prototype.getExtent=Mx.prototype.D;
+Mx.prototype.getFeatureById=Mx.prototype.Ha;Mx.prototype.removeFeature=Mx.prototype.$a;Mx.prototype.getAttributions=Mx.prototype.Y;Mx.prototype.getLogo=Mx.prototype.W;Mx.prototype.getProjection=Mx.prototype.Z;Mx.prototype.getState=Mx.prototype.$;Mx.prototype.changed=Mx.prototype.l;Mx.prototype.getRevision=Mx.prototype.A;Mx.prototype.on=Mx.prototype.u;Mx.prototype.once=Mx.prototype.B;Mx.prototype.un=Mx.prototype.v;Mx.prototype.unByKey=Mx.prototype.C;Nx.prototype.getAttributions=Nx.prototype.Y;
+Nx.prototype.getLogo=Nx.prototype.W;Nx.prototype.getProjection=Nx.prototype.Z;Nx.prototype.getState=Nx.prototype.$;Nx.prototype.changed=Nx.prototype.l;Nx.prototype.getRevision=Nx.prototype.A;Nx.prototype.on=Nx.prototype.u;Nx.prototype.once=Nx.prototype.B;Nx.prototype.un=Nx.prototype.v;Nx.prototype.unByKey=Nx.prototype.C;Qx.prototype.getAttributions=Qx.prototype.Y;Qx.prototype.getLogo=Qx.prototype.W;Qx.prototype.getProjection=Qx.prototype.Z;Qx.prototype.getState=Qx.prototype.$;
+Qx.prototype.changed=Qx.prototype.l;Qx.prototype.getRevision=Qx.prototype.A;Qx.prototype.on=Qx.prototype.u;Qx.prototype.once=Qx.prototype.B;Qx.prototype.un=Qx.prototype.v;Qx.prototype.unByKey=Qx.prototype.C;Rx.prototype.getAttributions=Rx.prototype.Y;Rx.prototype.getLogo=Rx.prototype.W;Rx.prototype.getProjection=Rx.prototype.Z;Rx.prototype.getState=Rx.prototype.$;Rx.prototype.changed=Rx.prototype.l;Rx.prototype.getRevision=Rx.prototype.A;Rx.prototype.on=Rx.prototype.u;Rx.prototype.once=Rx.prototype.B;
+Rx.prototype.un=Rx.prototype.v;Rx.prototype.unByKey=Rx.prototype.C;Sx.prototype.getAttributions=Sx.prototype.Y;Sx.prototype.getLogo=Sx.prototype.W;Sx.prototype.getProjection=Sx.prototype.Z;Sx.prototype.getState=Sx.prototype.$;Sx.prototype.changed=Sx.prototype.l;Sx.prototype.getRevision=Sx.prototype.A;Sx.prototype.on=Sx.prototype.u;Sx.prototype.once=Sx.prototype.B;Sx.prototype.un=Sx.prototype.v;Sx.prototype.unByKey=Sx.prototype.C;Tx.prototype.getAttributions=Tx.prototype.Y;Tx.prototype.getLogo=Tx.prototype.W;
+Tx.prototype.getProjection=Tx.prototype.Z;Tx.prototype.getState=Tx.prototype.$;Tx.prototype.changed=Tx.prototype.l;Tx.prototype.getRevision=Tx.prototype.A;Tx.prototype.on=Tx.prototype.u;Tx.prototype.once=Tx.prototype.B;Tx.prototype.un=Tx.prototype.v;Tx.prototype.unByKey=Tx.prototype.C;Vx.prototype.getAttributions=Vx.prototype.Y;Vx.prototype.getLogo=Vx.prototype.W;Vx.prototype.getProjection=Vx.prototype.Z;Vx.prototype.getState=Vx.prototype.$;Vx.prototype.changed=Vx.prototype.l;
+Vx.prototype.getRevision=Vx.prototype.A;Vx.prototype.on=Vx.prototype.u;Vx.prototype.once=Vx.prototype.B;Vx.prototype.un=Vx.prototype.v;Vx.prototype.unByKey=Vx.prototype.C;Zx.prototype.readFeatures=Zx.prototype.a;Zx.prototype.addFeature=Zx.prototype.Pa;Zx.prototype.addFeatures=Zx.prototype.ya;Zx.prototype.clear=Zx.prototype.clear;Zx.prototype.forEachFeature=Zx.prototype.Xa;Zx.prototype.forEachFeatureInExtent=Zx.prototype.ra;Zx.prototype.forEachFeatureIntersectingExtent=Zx.prototype.Fa;
+Zx.prototype.getFeatures=Zx.prototype.va;Zx.prototype.getFeaturesAtCoordinate=Zx.prototype.Ia;Zx.prototype.getClosestFeatureToCoordinate=Zx.prototype.Ya;Zx.prototype.getExtent=Zx.prototype.D;Zx.prototype.getFeatureById=Zx.prototype.Ha;Zx.prototype.removeFeature=Zx.prototype.$a;Zx.prototype.getAttributions=Zx.prototype.Y;Zx.prototype.getLogo=Zx.prototype.W;Zx.prototype.getProjection=Zx.prototype.Z;Zx.prototype.getState=Zx.prototype.$;Zx.prototype.changed=Zx.prototype.l;Zx.prototype.getRevision=Zx.prototype.A;
+Zx.prototype.on=Zx.prototype.u;Zx.prototype.once=Zx.prototype.B;Zx.prototype.un=Zx.prototype.v;Zx.prototype.unByKey=Zx.prototype.C;$x.prototype.getTileLoadFunction=$x.prototype.ib;$x.prototype.getTileUrlFunction=$x.prototype.jb;$x.prototype.setTileLoadFunction=$x.prototype.ob;$x.prototype.getTileGrid=$x.prototype.za;$x.prototype.getAttributions=$x.prototype.Y;$x.prototype.getLogo=$x.prototype.W;$x.prototype.getProjection=$x.prototype.Z;$x.prototype.getState=$x.prototype.$;$x.prototype.changed=$x.prototype.l;
+$x.prototype.getRevision=$x.prototype.A;$x.prototype.on=$x.prototype.u;$x.prototype.once=$x.prototype.B;$x.prototype.un=$x.prototype.v;$x.prototype.unByKey=$x.prototype.C;cy.prototype.setTileUrlFunction=cy.prototype.pa;cy.prototype.setUrl=cy.prototype.a;cy.prototype.getTileLoadFunction=cy.prototype.ib;cy.prototype.getTileUrlFunction=cy.prototype.jb;cy.prototype.setTileLoadFunction=cy.prototype.ob;cy.prototype.getTileGrid=cy.prototype.za;cy.prototype.getAttributions=cy.prototype.Y;
+cy.prototype.getLogo=cy.prototype.W;cy.prototype.getProjection=cy.prototype.Z;cy.prototype.getState=cy.prototype.$;cy.prototype.changed=cy.prototype.l;cy.prototype.getRevision=cy.prototype.A;cy.prototype.on=cy.prototype.u;cy.prototype.once=cy.prototype.B;cy.prototype.un=cy.prototype.v;cy.prototype.unByKey=cy.prototype.C;ay.prototype.setTileUrlFunction=ay.prototype.pa;ay.prototype.setUrl=ay.prototype.a;ay.prototype.getTileLoadFunction=ay.prototype.ib;ay.prototype.getTileUrlFunction=ay.prototype.jb;
+ay.prototype.setTileLoadFunction=ay.prototype.ob;ay.prototype.getTileGrid=ay.prototype.za;ay.prototype.getAttributions=ay.prototype.Y;ay.prototype.getLogo=ay.prototype.W;ay.prototype.getProjection=ay.prototype.Z;ay.prototype.getState=ay.prototype.$;ay.prototype.changed=ay.prototype.l;ay.prototype.getRevision=ay.prototype.A;ay.prototype.on=ay.prototype.u;ay.prototype.once=ay.prototype.B;ay.prototype.un=ay.prototype.v;ay.prototype.unByKey=ay.prototype.C;fy.prototype.readFeatures=fy.prototype.a;
+fy.prototype.addFeature=fy.prototype.Pa;fy.prototype.addFeatures=fy.prototype.ya;fy.prototype.clear=fy.prototype.clear;fy.prototype.forEachFeature=fy.prototype.Xa;fy.prototype.forEachFeatureInExtent=fy.prototype.ra;fy.prototype.forEachFeatureIntersectingExtent=fy.prototype.Fa;fy.prototype.getFeatures=fy.prototype.va;fy.prototype.getFeaturesAtCoordinate=fy.prototype.Ia;fy.prototype.getClosestFeatureToCoordinate=fy.prototype.Ya;fy.prototype.getExtent=fy.prototype.D;fy.prototype.getFeatureById=fy.prototype.Ha;
+fy.prototype.removeFeature=fy.prototype.$a;fy.prototype.getAttributions=fy.prototype.Y;fy.prototype.getLogo=fy.prototype.W;fy.prototype.getProjection=fy.prototype.Z;fy.prototype.getState=fy.prototype.$;fy.prototype.changed=fy.prototype.l;fy.prototype.getRevision=fy.prototype.A;fy.prototype.on=fy.prototype.u;fy.prototype.once=fy.prototype.B;fy.prototype.un=fy.prototype.v;fy.prototype.unByKey=fy.prototype.C;gy.prototype.addFeature=gy.prototype.Pa;gy.prototype.addFeatures=gy.prototype.ya;
+gy.prototype.forEachFeature=gy.prototype.Xa;gy.prototype.forEachFeatureInExtent=gy.prototype.ra;gy.prototype.forEachFeatureIntersectingExtent=gy.prototype.Fa;gy.prototype.getFeatures=gy.prototype.va;gy.prototype.getFeaturesAtCoordinate=gy.prototype.Ia;gy.prototype.getClosestFeatureToCoordinate=gy.prototype.Ya;gy.prototype.getExtent=gy.prototype.D;gy.prototype.getFeatureById=gy.prototype.Ha;gy.prototype.removeFeature=gy.prototype.$a;gy.prototype.getAttributions=gy.prototype.Y;
+gy.prototype.getLogo=gy.prototype.W;gy.prototype.getProjection=gy.prototype.Z;gy.prototype.getState=gy.prototype.$;gy.prototype.changed=gy.prototype.l;gy.prototype.getRevision=gy.prototype.A;gy.prototype.on=gy.prototype.u;gy.prototype.once=gy.prototype.B;gy.prototype.un=gy.prototype.v;gy.prototype.unByKey=gy.prototype.C;jy.prototype.setTileUrlFunction=jy.prototype.pa;jy.prototype.setUrl=jy.prototype.a;jy.prototype.getTileLoadFunction=jy.prototype.ib;jy.prototype.getTileUrlFunction=jy.prototype.jb;
+jy.prototype.setTileLoadFunction=jy.prototype.ob;jy.prototype.getTileGrid=jy.prototype.za;jy.prototype.getAttributions=jy.prototype.Y;jy.prototype.getLogo=jy.prototype.W;jy.prototype.getProjection=jy.prototype.Z;jy.prototype.getState=jy.prototype.$;jy.prototype.changed=jy.prototype.l;jy.prototype.getRevision=jy.prototype.A;jy.prototype.on=jy.prototype.u;jy.prototype.once=jy.prototype.B;jy.prototype.un=jy.prototype.v;jy.prototype.unByKey=jy.prototype.C;my.prototype.getTileGrid=my.prototype.za;
+my.prototype.getAttributions=my.prototype.Y;my.prototype.getLogo=my.prototype.W;my.prototype.getProjection=my.prototype.Z;my.prototype.getState=my.prototype.$;my.prototype.changed=my.prototype.l;my.prototype.getRevision=my.prototype.A;my.prototype.on=my.prototype.u;my.prototype.once=my.prototype.B;my.prototype.un=my.prototype.v;my.prototype.unByKey=my.prototype.C;ny.prototype.getTileLoadFunction=ny.prototype.ib;ny.prototype.getTileUrlFunction=ny.prototype.jb;ny.prototype.setTileLoadFunction=ny.prototype.ob;
+ny.prototype.setTileUrlFunction=ny.prototype.pa;ny.prototype.getTileGrid=ny.prototype.za;ny.prototype.getAttributions=ny.prototype.Y;ny.prototype.getLogo=ny.prototype.W;ny.prototype.getProjection=ny.prototype.Z;ny.prototype.getState=ny.prototype.$;ny.prototype.changed=ny.prototype.l;ny.prototype.getRevision=ny.prototype.A;ny.prototype.on=ny.prototype.u;ny.prototype.once=ny.prototype.B;ny.prototype.un=ny.prototype.v;ny.prototype.unByKey=ny.prototype.C;oy.prototype.getTileGrid=oy.prototype.za;
+oy.prototype.getAttributions=oy.prototype.Y;oy.prototype.getLogo=oy.prototype.W;oy.prototype.getProjection=oy.prototype.Z;oy.prototype.getState=oy.prototype.$;oy.prototype.changed=oy.prototype.l;oy.prototype.getRevision=oy.prototype.A;oy.prototype.on=oy.prototype.u;oy.prototype.once=oy.prototype.B;oy.prototype.un=oy.prototype.v;oy.prototype.unByKey=oy.prototype.C;ty.prototype.readFeatures=ty.prototype.a;ty.prototype.forEachFeatureIntersectingExtent=ty.prototype.Fa;
+ty.prototype.getFeaturesAtCoordinate=ty.prototype.Ia;ty.prototype.getFeatureById=ty.prototype.Ha;ty.prototype.getAttributions=ty.prototype.Y;ty.prototype.getLogo=ty.prototype.W;ty.prototype.getProjection=ty.prototype.Z;ty.prototype.getState=ty.prototype.$;ty.prototype.changed=ty.prototype.l;ty.prototype.getRevision=ty.prototype.A;ty.prototype.on=ty.prototype.u;ty.prototype.once=ty.prototype.B;ty.prototype.un=ty.prototype.v;ty.prototype.unByKey=ty.prototype.C;vy.prototype.getTileLoadFunction=vy.prototype.ib;
+vy.prototype.getTileUrlFunction=vy.prototype.jb;vy.prototype.setTileLoadFunction=vy.prototype.ob;vy.prototype.setTileUrlFunction=vy.prototype.pa;vy.prototype.getTileGrid=vy.prototype.za;vy.prototype.getAttributions=vy.prototype.Y;vy.prototype.getLogo=vy.prototype.W;vy.prototype.getProjection=vy.prototype.Z;vy.prototype.getState=vy.prototype.$;vy.prototype.changed=vy.prototype.l;vy.prototype.getRevision=vy.prototype.A;vy.prototype.on=vy.prototype.u;vy.prototype.once=vy.prototype.B;
+vy.prototype.un=vy.prototype.v;vy.prototype.unByKey=vy.prototype.C;zy.prototype.readFeatures=zy.prototype.a;zy.prototype.addFeature=zy.prototype.Pa;zy.prototype.addFeatures=zy.prototype.ya;zy.prototype.clear=zy.prototype.clear;zy.prototype.forEachFeature=zy.prototype.Xa;zy.prototype.forEachFeatureInExtent=zy.prototype.ra;zy.prototype.forEachFeatureIntersectingExtent=zy.prototype.Fa;zy.prototype.getFeatures=zy.prototype.va;zy.prototype.getFeaturesAtCoordinate=zy.prototype.Ia;
+zy.prototype.getClosestFeatureToCoordinate=zy.prototype.Ya;zy.prototype.getExtent=zy.prototype.D;zy.prototype.getFeatureById=zy.prototype.Ha;zy.prototype.removeFeature=zy.prototype.$a;zy.prototype.getAttributions=zy.prototype.Y;zy.prototype.getLogo=zy.prototype.W;zy.prototype.getProjection=zy.prototype.Z;zy.prototype.getState=zy.prototype.$;zy.prototype.changed=zy.prototype.l;zy.prototype.getRevision=zy.prototype.A;zy.prototype.on=zy.prototype.u;zy.prototype.once=zy.prototype.B;zy.prototype.un=zy.prototype.v;
+zy.prototype.unByKey=zy.prototype.C;By.prototype.getTileLoadFunction=By.prototype.ib;By.prototype.getTileUrlFunction=By.prototype.jb;By.prototype.setTileLoadFunction=By.prototype.ob;By.prototype.setTileUrlFunction=By.prototype.pa;By.prototype.getTileGrid=By.prototype.za;By.prototype.getAttributions=By.prototype.Y;By.prototype.getLogo=By.prototype.W;By.prototype.getProjection=By.prototype.Z;By.prototype.getState=By.prototype.$;By.prototype.changed=By.prototype.l;By.prototype.getRevision=By.prototype.A;
+By.prototype.on=By.prototype.u;By.prototype.once=By.prototype.B;By.prototype.un=By.prototype.v;By.prototype.unByKey=By.prototype.C;Ey.prototype.getTileLoadFunction=Ey.prototype.ib;Ey.prototype.getTileUrlFunction=Ey.prototype.jb;Ey.prototype.setTileLoadFunction=Ey.prototype.ob;Ey.prototype.setTileUrlFunction=Ey.prototype.pa;Ey.prototype.getTileGrid=Ey.prototype.za;Ey.prototype.getAttributions=Ey.prototype.Y;Ey.prototype.getLogo=Ey.prototype.W;Ey.prototype.getProjection=Ey.prototype.Z;
+Ey.prototype.getState=Ey.prototype.$;Ey.prototype.changed=Ey.prototype.l;Ey.prototype.getRevision=Ey.prototype.A;Ey.prototype.on=Ey.prototype.u;Ey.prototype.once=Ey.prototype.B;Ey.prototype.un=Ey.prototype.v;Ey.prototype.unByKey=Ey.prototype.C;D.prototype.bindTo=D.prototype.O;D.prototype.get=D.prototype.get;D.prototype.getKeys=D.prototype.I;D.prototype.getProperties=D.prototype.L;D.prototype.set=D.prototype.set;D.prototype.setProperties=D.prototype.G;D.prototype.unbind=D.prototype.P;
+D.prototype.unbindAll=D.prototype.R;D.prototype.changed=D.prototype.l;D.prototype.getRevision=D.prototype.A;D.prototype.on=D.prototype.u;D.prototype.once=D.prototype.B;D.prototype.un=D.prototype.v;D.prototype.unByKey=D.prototype.C;E.prototype.getBrightness=E.prototype.d;E.prototype.getContrast=E.prototype.e;E.prototype.getHue=E.prototype.f;E.prototype.getExtent=E.prototype.D;E.prototype.getMaxResolution=E.prototype.g;E.prototype.getMinResolution=E.prototype.i;E.prototype.getOpacity=E.prototype.j;
+E.prototype.getSaturation=E.prototype.n;E.prototype.getVisible=E.prototype.b;E.prototype.setBrightness=E.prototype.t;E.prototype.setContrast=E.prototype.F;E.prototype.setHue=E.prototype.J;E.prototype.setExtent=E.prototype.p;E.prototype.setMaxResolution=E.prototype.Q;E.prototype.setMinResolution=E.prototype.V;E.prototype.setOpacity=E.prototype.q;E.prototype.setSaturation=E.prototype.ba;E.prototype.setVisible=E.prototype.ca;E.prototype.bindTo=E.prototype.O;E.prototype.get=E.prototype.get;
+E.prototype.getKeys=E.prototype.I;E.prototype.getProperties=E.prototype.L;E.prototype.set=E.prototype.set;E.prototype.setProperties=E.prototype.G;E.prototype.unbind=E.prototype.P;E.prototype.unbindAll=E.prototype.R;E.prototype.changed=E.prototype.l;E.prototype.getRevision=E.prototype.A;E.prototype.on=E.prototype.u;E.prototype.once=E.prototype.B;E.prototype.un=E.prototype.v;E.prototype.unByKey=E.prototype.C;L.prototype.setSource=L.prototype.ga;L.prototype.getBrightness=L.prototype.d;
+L.prototype.getContrast=L.prototype.e;L.prototype.getHue=L.prototype.f;L.prototype.getExtent=L.prototype.D;L.prototype.getMaxResolution=L.prototype.g;L.prototype.getMinResolution=L.prototype.i;L.prototype.getOpacity=L.prototype.j;L.prototype.getSaturation=L.prototype.n;L.prototype.getVisible=L.prototype.b;L.prototype.setBrightness=L.prototype.t;L.prototype.setContrast=L.prototype.F;L.prototype.setHue=L.prototype.J;L.prototype.setExtent=L.prototype.p;L.prototype.setMaxResolution=L.prototype.Q;
+L.prototype.setMinResolution=L.prototype.V;L.prototype.setOpacity=L.prototype.q;L.prototype.setSaturation=L.prototype.ba;L.prototype.setVisible=L.prototype.ca;L.prototype.bindTo=L.prototype.O;L.prototype.get=L.prototype.get;L.prototype.getKeys=L.prototype.I;L.prototype.getProperties=L.prototype.L;L.prototype.set=L.prototype.set;L.prototype.setProperties=L.prototype.G;L.prototype.unbind=L.prototype.P;L.prototype.unbindAll=L.prototype.R;L.prototype.changed=L.prototype.l;L.prototype.getRevision=L.prototype.A;
+L.prototype.on=L.prototype.u;L.prototype.once=L.prototype.B;L.prototype.un=L.prototype.v;L.prototype.unByKey=L.prototype.C;$.prototype.getSource=$.prototype.a;$.prototype.getStyle=$.prototype.Uc;$.prototype.getStyleFunction=$.prototype.Vc;$.prototype.setStyle=$.prototype.oa;$.prototype.setSource=$.prototype.ga;$.prototype.getBrightness=$.prototype.d;$.prototype.getContrast=$.prototype.e;$.prototype.getHue=$.prototype.f;$.prototype.getExtent=$.prototype.D;$.prototype.getMaxResolution=$.prototype.g;
+$.prototype.getMinResolution=$.prototype.i;$.prototype.getOpacity=$.prototype.j;$.prototype.getSaturation=$.prototype.n;$.prototype.getVisible=$.prototype.b;$.prototype.setBrightness=$.prototype.t;$.prototype.setContrast=$.prototype.F;$.prototype.setHue=$.prototype.J;$.prototype.setExtent=$.prototype.p;$.prototype.setMaxResolution=$.prototype.Q;$.prototype.setMinResolution=$.prototype.V;$.prototype.setOpacity=$.prototype.q;$.prototype.setSaturation=$.prototype.ba;$.prototype.setVisible=$.prototype.ca;
+$.prototype.bindTo=$.prototype.O;$.prototype.get=$.prototype.get;$.prototype.getKeys=$.prototype.I;$.prototype.getProperties=$.prototype.L;$.prototype.set=$.prototype.set;$.prototype.setProperties=$.prototype.G;$.prototype.unbind=$.prototype.P;$.prototype.unbindAll=$.prototype.R;$.prototype.changed=$.prototype.l;$.prototype.getRevision=$.prototype.A;$.prototype.on=$.prototype.u;$.prototype.once=$.prototype.B;$.prototype.un=$.prototype.v;$.prototype.unByKey=$.prototype.C;J.prototype.setSource=J.prototype.ga;
+J.prototype.getBrightness=J.prototype.d;J.prototype.getContrast=J.prototype.e;J.prototype.getHue=J.prototype.f;J.prototype.getExtent=J.prototype.D;J.prototype.getMaxResolution=J.prototype.g;J.prototype.getMinResolution=J.prototype.i;J.prototype.getOpacity=J.prototype.j;J.prototype.getSaturation=J.prototype.n;J.prototype.getVisible=J.prototype.b;J.prototype.setBrightness=J.prototype.t;J.prototype.setContrast=J.prototype.F;J.prototype.setHue=J.prototype.J;J.prototype.setExtent=J.prototype.p;
+J.prototype.setMaxResolution=J.prototype.Q;J.prototype.setMinResolution=J.prototype.V;J.prototype.setOpacity=J.prototype.q;J.prototype.setSaturation=J.prototype.ba;J.prototype.setVisible=J.prototype.ca;J.prototype.bindTo=J.prototype.O;J.prototype.get=J.prototype.get;J.prototype.getKeys=J.prototype.I;J.prototype.getProperties=J.prototype.L;J.prototype.set=J.prototype.set;J.prototype.setProperties=J.prototype.G;J.prototype.unbind=J.prototype.P;J.prototype.unbindAll=J.prototype.R;
+J.prototype.changed=J.prototype.l;J.prototype.getRevision=J.prototype.A;J.prototype.on=J.prototype.u;J.prototype.once=J.prototype.B;J.prototype.un=J.prototype.v;J.prototype.unByKey=J.prototype.C;I.prototype.getBrightness=I.prototype.d;I.prototype.getContrast=I.prototype.e;I.prototype.getHue=I.prototype.f;I.prototype.getExtent=I.prototype.D;I.prototype.getMaxResolution=I.prototype.g;I.prototype.getMinResolution=I.prototype.i;I.prototype.getOpacity=I.prototype.j;I.prototype.getSaturation=I.prototype.n;
+I.prototype.getVisible=I.prototype.b;I.prototype.setBrightness=I.prototype.t;I.prototype.setContrast=I.prototype.F;I.prototype.setHue=I.prototype.J;I.prototype.setExtent=I.prototype.p;I.prototype.setMaxResolution=I.prototype.Q;I.prototype.setMinResolution=I.prototype.V;I.prototype.setOpacity=I.prototype.q;I.prototype.setSaturation=I.prototype.ba;I.prototype.setVisible=I.prototype.ca;I.prototype.bindTo=I.prototype.O;I.prototype.get=I.prototype.get;I.prototype.getKeys=I.prototype.I;
+I.prototype.getProperties=I.prototype.L;I.prototype.set=I.prototype.set;I.prototype.setProperties=I.prototype.G;I.prototype.unbind=I.prototype.P;I.prototype.unbindAll=I.prototype.R;I.prototype.changed=I.prototype.l;I.prototype.getRevision=I.prototype.A;I.prototype.on=I.prototype.u;I.prototype.once=I.prototype.B;I.prototype.un=I.prototype.v;I.prototype.unByKey=I.prototype.C;K.prototype.setSource=K.prototype.ga;K.prototype.getBrightness=K.prototype.d;K.prototype.getContrast=K.prototype.e;
+K.prototype.getHue=K.prototype.f;K.prototype.getExtent=K.prototype.D;K.prototype.getMaxResolution=K.prototype.g;K.prototype.getMinResolution=K.prototype.i;K.prototype.getOpacity=K.prototype.j;K.prototype.getSaturation=K.prototype.n;K.prototype.getVisible=K.prototype.b;K.prototype.setBrightness=K.prototype.t;K.prototype.setContrast=K.prototype.F;K.prototype.setHue=K.prototype.J;K.prototype.setExtent=K.prototype.p;K.prototype.setMaxResolution=K.prototype.Q;K.prototype.setMinResolution=K.prototype.V;
+K.prototype.setOpacity=K.prototype.q;K.prototype.setSaturation=K.prototype.ba;K.prototype.setVisible=K.prototype.ca;K.prototype.bindTo=K.prototype.O;K.prototype.get=K.prototype.get;K.prototype.getKeys=K.prototype.I;K.prototype.getProperties=K.prototype.L;K.prototype.set=K.prototype.set;K.prototype.setProperties=K.prototype.G;K.prototype.unbind=K.prototype.P;K.prototype.unbindAll=K.prototype.R;K.prototype.changed=K.prototype.l;K.prototype.getRevision=K.prototype.A;K.prototype.on=K.prototype.u;
+K.prototype.once=K.prototype.B;K.prototype.un=K.prototype.v;K.prototype.unByKey=K.prototype.C;ok.prototype.bindTo=ok.prototype.O;ok.prototype.get=ok.prototype.get;ok.prototype.getKeys=ok.prototype.I;ok.prototype.getProperties=ok.prototype.L;ok.prototype.set=ok.prototype.set;ok.prototype.setProperties=ok.prototype.G;ok.prototype.unbind=ok.prototype.P;ok.prototype.unbindAll=ok.prototype.R;ok.prototype.changed=ok.prototype.l;ok.prototype.getRevision=ok.prototype.A;ok.prototype.on=ok.prototype.u;
+ok.prototype.once=ok.prototype.B;ok.prototype.un=ok.prototype.v;ok.prototype.unByKey=ok.prototype.C;sk.prototype.getActive=sk.prototype.a;sk.prototype.setActive=sk.prototype.b;sk.prototype.bindTo=sk.prototype.O;sk.prototype.get=sk.prototype.get;sk.prototype.getKeys=sk.prototype.I;sk.prototype.getProperties=sk.prototype.L;sk.prototype.set=sk.prototype.set;sk.prototype.setProperties=sk.prototype.G;sk.prototype.unbind=sk.prototype.P;sk.prototype.unbindAll=sk.prototype.R;sk.prototype.changed=sk.prototype.l;
+sk.prototype.getRevision=sk.prototype.A;sk.prototype.on=sk.prototype.u;sk.prototype.once=sk.prototype.B;sk.prototype.un=sk.prototype.v;sk.prototype.unByKey=sk.prototype.C;Nv.prototype.getActive=Nv.prototype.a;Nv.prototype.setActive=Nv.prototype.b;Nv.prototype.bindTo=Nv.prototype.O;Nv.prototype.get=Nv.prototype.get;Nv.prototype.getKeys=Nv.prototype.I;Nv.prototype.getProperties=Nv.prototype.L;Nv.prototype.set=Nv.prototype.set;Nv.prototype.setProperties=Nv.prototype.G;Nv.prototype.unbind=Nv.prototype.P;
+Nv.prototype.unbindAll=Nv.prototype.R;Nv.prototype.changed=Nv.prototype.l;Nv.prototype.getRevision=Nv.prototype.A;Nv.prototype.on=Nv.prototype.u;Nv.prototype.once=Nv.prototype.B;Nv.prototype.un=Nv.prototype.v;Nv.prototype.unByKey=Nv.prototype.C;Bk.prototype.getActive=Bk.prototype.a;Bk.prototype.setActive=Bk.prototype.b;Bk.prototype.bindTo=Bk.prototype.O;Bk.prototype.get=Bk.prototype.get;Bk.prototype.getKeys=Bk.prototype.I;Bk.prototype.getProperties=Bk.prototype.L;Bk.prototype.set=Bk.prototype.set;
+Bk.prototype.setProperties=Bk.prototype.G;Bk.prototype.unbind=Bk.prototype.P;Bk.prototype.unbindAll=Bk.prototype.R;Bk.prototype.changed=Bk.prototype.l;Bk.prototype.getRevision=Bk.prototype.A;Bk.prototype.on=Bk.prototype.u;Bk.prototype.once=Bk.prototype.B;Bk.prototype.un=Bk.prototype.v;Bk.prototype.unByKey=Bk.prototype.C;Fl.prototype.getActive=Fl.prototype.a;Fl.prototype.setActive=Fl.prototype.b;Fl.prototype.bindTo=Fl.prototype.O;Fl.prototype.get=Fl.prototype.get;Fl.prototype.getKeys=Fl.prototype.I;
+Fl.prototype.getProperties=Fl.prototype.L;Fl.prototype.set=Fl.prototype.set;Fl.prototype.setProperties=Fl.prototype.G;Fl.prototype.unbind=Fl.prototype.P;Fl.prototype.unbindAll=Fl.prototype.R;Fl.prototype.changed=Fl.prototype.l;Fl.prototype.getRevision=Fl.prototype.A;Fl.prototype.on=Fl.prototype.u;Fl.prototype.once=Fl.prototype.B;Fl.prototype.un=Fl.prototype.v;Fl.prototype.unByKey=Fl.prototype.C;Ek.prototype.getActive=Ek.prototype.a;Ek.prototype.setActive=Ek.prototype.b;Ek.prototype.bindTo=Ek.prototype.O;
+Ek.prototype.get=Ek.prototype.get;Ek.prototype.getKeys=Ek.prototype.I;Ek.prototype.getProperties=Ek.prototype.L;Ek.prototype.set=Ek.prototype.set;Ek.prototype.setProperties=Ek.prototype.G;Ek.prototype.unbind=Ek.prototype.P;Ek.prototype.unbindAll=Ek.prototype.R;Ek.prototype.changed=Ek.prototype.l;Ek.prototype.getRevision=Ek.prototype.A;Ek.prototype.on=Ek.prototype.u;Ek.prototype.once=Ek.prototype.B;Ek.prototype.un=Ek.prototype.v;Ek.prototype.unByKey=Ek.prototype.C;Rv.prototype.getActive=Rv.prototype.a;
+Rv.prototype.setActive=Rv.prototype.b;Rv.prototype.bindTo=Rv.prototype.O;Rv.prototype.get=Rv.prototype.get;Rv.prototype.getKeys=Rv.prototype.I;Rv.prototype.getProperties=Rv.prototype.L;Rv.prototype.set=Rv.prototype.set;Rv.prototype.setProperties=Rv.prototype.G;Rv.prototype.unbind=Rv.prototype.P;Rv.prototype.unbindAll=Rv.prototype.R;Rv.prototype.changed=Rv.prototype.l;Rv.prototype.getRevision=Rv.prototype.A;Rv.prototype.on=Rv.prototype.u;Rv.prototype.once=Rv.prototype.B;Rv.prototype.un=Rv.prototype.v;
+Rv.prototype.unByKey=Rv.prototype.C;Ik.prototype.getActive=Ik.prototype.a;Ik.prototype.setActive=Ik.prototype.b;Ik.prototype.bindTo=Ik.prototype.O;Ik.prototype.get=Ik.prototype.get;Ik.prototype.getKeys=Ik.prototype.I;Ik.prototype.getProperties=Ik.prototype.L;Ik.prototype.set=Ik.prototype.set;Ik.prototype.setProperties=Ik.prototype.G;Ik.prototype.unbind=Ik.prototype.P;Ik.prototype.unbindAll=Ik.prototype.R;Ik.prototype.changed=Ik.prototype.l;Ik.prototype.getRevision=Ik.prototype.A;Ik.prototype.on=Ik.prototype.u;
+Ik.prototype.once=Ik.prototype.B;Ik.prototype.un=Ik.prototype.v;Ik.prototype.unByKey=Ik.prototype.C;Yl.prototype.getGeometry=Yl.prototype.N;Yl.prototype.getActive=Yl.prototype.a;Yl.prototype.setActive=Yl.prototype.b;Yl.prototype.bindTo=Yl.prototype.O;Yl.prototype.get=Yl.prototype.get;Yl.prototype.getKeys=Yl.prototype.I;Yl.prototype.getProperties=Yl.prototype.L;Yl.prototype.set=Yl.prototype.set;Yl.prototype.setProperties=Yl.prototype.G;Yl.prototype.unbind=Yl.prototype.P;Yl.prototype.unbindAll=Yl.prototype.R;
+Yl.prototype.changed=Yl.prototype.l;Yl.prototype.getRevision=Yl.prototype.A;Yl.prototype.on=Yl.prototype.u;Yl.prototype.once=Yl.prototype.B;Yl.prototype.un=Yl.prototype.v;Yl.prototype.unByKey=Yl.prototype.C;hw.prototype.getActive=hw.prototype.a;hw.prototype.setActive=hw.prototype.b;hw.prototype.bindTo=hw.prototype.O;hw.prototype.get=hw.prototype.get;hw.prototype.getKeys=hw.prototype.I;hw.prototype.getProperties=hw.prototype.L;hw.prototype.set=hw.prototype.set;hw.prototype.setProperties=hw.prototype.G;
+hw.prototype.unbind=hw.prototype.P;hw.prototype.unbindAll=hw.prototype.R;hw.prototype.changed=hw.prototype.l;hw.prototype.getRevision=hw.prototype.A;hw.prototype.on=hw.prototype.u;hw.prototype.once=hw.prototype.B;hw.prototype.un=hw.prototype.v;hw.prototype.unByKey=hw.prototype.C;Zl.prototype.getActive=Zl.prototype.a;Zl.prototype.setActive=Zl.prototype.b;Zl.prototype.bindTo=Zl.prototype.O;Zl.prototype.get=Zl.prototype.get;Zl.prototype.getKeys=Zl.prototype.I;Zl.prototype.getProperties=Zl.prototype.L;
+Zl.prototype.set=Zl.prototype.set;Zl.prototype.setProperties=Zl.prototype.G;Zl.prototype.unbind=Zl.prototype.P;Zl.prototype.unbindAll=Zl.prototype.R;Zl.prototype.changed=Zl.prototype.l;Zl.prototype.getRevision=Zl.prototype.A;Zl.prototype.on=Zl.prototype.u;Zl.prototype.once=Zl.prototype.B;Zl.prototype.un=Zl.prototype.v;Zl.prototype.unByKey=Zl.prototype.C;am.prototype.getActive=am.prototype.a;am.prototype.setActive=am.prototype.b;am.prototype.bindTo=am.prototype.O;am.prototype.get=am.prototype.get;
+am.prototype.getKeys=am.prototype.I;am.prototype.getProperties=am.prototype.L;am.prototype.set=am.prototype.set;am.prototype.setProperties=am.prototype.G;am.prototype.unbind=am.prototype.P;am.prototype.unbindAll=am.prototype.R;am.prototype.changed=am.prototype.l;am.prototype.getRevision=am.prototype.A;am.prototype.on=am.prototype.u;am.prototype.once=am.prototype.B;am.prototype.un=am.prototype.v;am.prototype.unByKey=am.prototype.C;uw.prototype.getActive=uw.prototype.a;uw.prototype.setActive=uw.prototype.b;
+uw.prototype.bindTo=uw.prototype.O;uw.prototype.get=uw.prototype.get;uw.prototype.getKeys=uw.prototype.I;uw.prototype.getProperties=uw.prototype.L;uw.prototype.set=uw.prototype.set;uw.prototype.setProperties=uw.prototype.G;uw.prototype.unbind=uw.prototype.P;uw.prototype.unbindAll=uw.prototype.R;uw.prototype.changed=uw.prototype.l;uw.prototype.getRevision=uw.prototype.A;uw.prototype.on=uw.prototype.u;uw.prototype.once=uw.prototype.B;uw.prototype.un=uw.prototype.v;uw.prototype.unByKey=uw.prototype.C;
+cm.prototype.getActive=cm.prototype.a;cm.prototype.setActive=cm.prototype.b;cm.prototype.bindTo=cm.prototype.O;cm.prototype.get=cm.prototype.get;cm.prototype.getKeys=cm.prototype.I;cm.prototype.getProperties=cm.prototype.L;cm.prototype.set=cm.prototype.set;cm.prototype.setProperties=cm.prototype.G;cm.prototype.unbind=cm.prototype.P;cm.prototype.unbindAll=cm.prototype.R;cm.prototype.changed=cm.prototype.l;cm.prototype.getRevision=cm.prototype.A;cm.prototype.on=cm.prototype.u;cm.prototype.once=cm.prototype.B;
+cm.prototype.un=cm.prototype.v;cm.prototype.unByKey=cm.prototype.C;em.prototype.getActive=em.prototype.a;em.prototype.setActive=em.prototype.b;em.prototype.bindTo=em.prototype.O;em.prototype.get=em.prototype.get;em.prototype.getKeys=em.prototype.I;em.prototype.getProperties=em.prototype.L;em.prototype.set=em.prototype.set;em.prototype.setProperties=em.prototype.G;em.prototype.unbind=em.prototype.P;em.prototype.unbindAll=em.prototype.R;em.prototype.changed=em.prototype.l;em.prototype.getRevision=em.prototype.A;
+em.prototype.on=em.prototype.u;em.prototype.once=em.prototype.B;em.prototype.un=em.prototype.v;em.prototype.unByKey=em.prototype.C;im.prototype.getActive=im.prototype.a;im.prototype.setActive=im.prototype.b;im.prototype.bindTo=im.prototype.O;im.prototype.get=im.prototype.get;im.prototype.getKeys=im.prototype.I;im.prototype.getProperties=im.prototype.L;im.prototype.set=im.prototype.set;im.prototype.setProperties=im.prototype.G;im.prototype.unbind=im.prototype.P;im.prototype.unbindAll=im.prototype.R;
+im.prototype.changed=im.prototype.l;im.prototype.getRevision=im.prototype.A;im.prototype.on=im.prototype.u;im.prototype.once=im.prototype.B;im.prototype.un=im.prototype.v;im.prototype.unByKey=im.prototype.C;Dw.prototype.getActive=Dw.prototype.a;Dw.prototype.setActive=Dw.prototype.b;Dw.prototype.bindTo=Dw.prototype.O;Dw.prototype.get=Dw.prototype.get;Dw.prototype.getKeys=Dw.prototype.I;Dw.prototype.getProperties=Dw.prototype.L;Dw.prototype.set=Dw.prototype.set;Dw.prototype.setProperties=Dw.prototype.G;
+Dw.prototype.unbind=Dw.prototype.P;Dw.prototype.unbindAll=Dw.prototype.R;Dw.prototype.changed=Dw.prototype.l;Dw.prototype.getRevision=Dw.prototype.A;Dw.prototype.on=Dw.prototype.u;Dw.prototype.once=Dw.prototype.B;Dw.prototype.un=Dw.prototype.v;Dw.prototype.unByKey=Dw.prototype.C;Mk.prototype.changed=Mk.prototype.l;Mk.prototype.getRevision=Mk.prototype.A;Mk.prototype.on=Mk.prototype.u;Mk.prototype.once=Mk.prototype.B;Mk.prototype.un=Mk.prototype.v;Mk.prototype.unByKey=Mk.prototype.C;
+Ok.prototype.clone=Ok.prototype.clone;Ok.prototype.getClosestPoint=Ok.prototype.f;Ok.prototype.getType=Ok.prototype.H;Ok.prototype.intersectsExtent=Ok.prototype.ha;Ok.prototype.transform=Ok.prototype.e;Ok.prototype.changed=Ok.prototype.l;Ok.prototype.getRevision=Ok.prototype.A;Ok.prototype.on=Ok.prototype.u;Ok.prototype.once=Ok.prototype.B;Ok.prototype.un=Ok.prototype.v;Ok.prototype.unByKey=Ok.prototype.C;gn.prototype.getFirstCoordinate=gn.prototype.vb;gn.prototype.getLastCoordinate=gn.prototype.wb;
+gn.prototype.getLayout=gn.prototype.xb;gn.prototype.applyTransform=gn.prototype.ma;gn.prototype.translate=gn.prototype.Aa;gn.prototype.getClosestPoint=gn.prototype.f;gn.prototype.intersectsExtent=gn.prototype.ha;gn.prototype.changed=gn.prototype.l;gn.prototype.getRevision=gn.prototype.A;gn.prototype.on=gn.prototype.u;gn.prototype.once=gn.prototype.B;gn.prototype.un=gn.prototype.v;gn.prototype.unByKey=gn.prototype.C;jn.prototype.getClosestPoint=jn.prototype.f;jn.prototype.transform=jn.prototype.e;
+jn.prototype.changed=jn.prototype.l;jn.prototype.getRevision=jn.prototype.A;jn.prototype.on=jn.prototype.u;jn.prototype.once=jn.prototype.B;jn.prototype.un=jn.prototype.v;jn.prototype.unByKey=jn.prototype.C;hl.prototype.getExtent=hl.prototype.D;hl.prototype.getFirstCoordinate=hl.prototype.vb;hl.prototype.getLastCoordinate=hl.prototype.wb;hl.prototype.getLayout=hl.prototype.xb;hl.prototype.applyTransform=hl.prototype.ma;hl.prototype.translate=hl.prototype.Aa;hl.prototype.getClosestPoint=hl.prototype.f;
+hl.prototype.intersectsExtent=hl.prototype.ha;hl.prototype.transform=hl.prototype.e;hl.prototype.changed=hl.prototype.l;hl.prototype.getRevision=hl.prototype.A;hl.prototype.on=hl.prototype.u;hl.prototype.once=hl.prototype.B;hl.prototype.un=hl.prototype.v;hl.prototype.unByKey=hl.prototype.C;M.prototype.getExtent=M.prototype.D;M.prototype.getFirstCoordinate=M.prototype.vb;M.prototype.getLastCoordinate=M.prototype.wb;M.prototype.getLayout=M.prototype.xb;M.prototype.applyTransform=M.prototype.ma;
+M.prototype.translate=M.prototype.Aa;M.prototype.getClosestPoint=M.prototype.f;M.prototype.transform=M.prototype.e;M.prototype.changed=M.prototype.l;M.prototype.getRevision=M.prototype.A;M.prototype.on=M.prototype.u;M.prototype.once=M.prototype.B;M.prototype.un=M.prototype.v;M.prototype.unByKey=M.prototype.C;rn.prototype.getExtent=rn.prototype.D;rn.prototype.getFirstCoordinate=rn.prototype.vb;rn.prototype.getLastCoordinate=rn.prototype.wb;rn.prototype.getLayout=rn.prototype.xb;
+rn.prototype.applyTransform=rn.prototype.ma;rn.prototype.translate=rn.prototype.Aa;rn.prototype.getClosestPoint=rn.prototype.f;rn.prototype.transform=rn.prototype.e;rn.prototype.changed=rn.prototype.l;rn.prototype.getRevision=rn.prototype.A;rn.prototype.on=rn.prototype.u;rn.prototype.once=rn.prototype.B;rn.prototype.un=rn.prototype.v;rn.prototype.unByKey=rn.prototype.C;un.prototype.getExtent=un.prototype.D;un.prototype.getFirstCoordinate=un.prototype.vb;un.prototype.getLastCoordinate=un.prototype.wb;
+un.prototype.getLayout=un.prototype.xb;un.prototype.applyTransform=un.prototype.ma;un.prototype.translate=un.prototype.Aa;un.prototype.getClosestPoint=un.prototype.f;un.prototype.transform=un.prototype.e;un.prototype.changed=un.prototype.l;un.prototype.getRevision=un.prototype.A;un.prototype.on=un.prototype.u;un.prototype.once=un.prototype.B;un.prototype.un=un.prototype.v;un.prototype.unByKey=un.prototype.C;vn.prototype.getExtent=vn.prototype.D;vn.prototype.getFirstCoordinate=vn.prototype.vb;
+vn.prototype.getLastCoordinate=vn.prototype.wb;vn.prototype.getLayout=vn.prototype.xb;vn.prototype.applyTransform=vn.prototype.ma;vn.prototype.translate=vn.prototype.Aa;vn.prototype.getClosestPoint=vn.prototype.f;vn.prototype.transform=vn.prototype.e;vn.prototype.changed=vn.prototype.l;vn.prototype.getRevision=vn.prototype.A;vn.prototype.on=vn.prototype.u;vn.prototype.once=vn.prototype.B;vn.prototype.un=vn.prototype.v;vn.prototype.unByKey=vn.prototype.C;jl.prototype.getFirstCoordinate=jl.prototype.vb;
+jl.prototype.getLastCoordinate=jl.prototype.wb;jl.prototype.getLayout=jl.prototype.xb;jl.prototype.applyTransform=jl.prototype.ma;jl.prototype.translate=jl.prototype.Aa;jl.prototype.getClosestPoint=jl.prototype.f;jl.prototype.transform=jl.prototype.e;jl.prototype.changed=jl.prototype.l;jl.prototype.getRevision=jl.prototype.A;jl.prototype.on=jl.prototype.u;jl.prototype.once=jl.prototype.B;jl.prototype.un=jl.prototype.v;jl.prototype.unByKey=jl.prototype.C;H.prototype.getExtent=H.prototype.D;
+H.prototype.getFirstCoordinate=H.prototype.vb;H.prototype.getLastCoordinate=H.prototype.wb;H.prototype.getLayout=H.prototype.xb;H.prototype.applyTransform=H.prototype.ma;H.prototype.translate=H.prototype.Aa;H.prototype.getClosestPoint=H.prototype.f;H.prototype.transform=H.prototype.e;H.prototype.changed=H.prototype.l;H.prototype.getRevision=H.prototype.A;H.prototype.on=H.prototype.u;H.prototype.once=H.prototype.B;H.prototype.un=H.prototype.v;H.prototype.unByKey=H.prototype.C;
+Uq.prototype.readFeatures=Uq.prototype.ja;X.prototype.readFeatures=X.prototype.ja;X.prototype.readFeatures=X.prototype.ja;rp.prototype.bindTo=rp.prototype.O;rp.prototype.get=rp.prototype.get;rp.prototype.getKeys=rp.prototype.I;rp.prototype.getProperties=rp.prototype.L;rp.prototype.set=rp.prototype.set;rp.prototype.setProperties=rp.prototype.G;rp.prototype.unbind=rp.prototype.P;rp.prototype.unbindAll=rp.prototype.R;rp.prototype.changed=rp.prototype.l;rp.prototype.getRevision=rp.prototype.A;
+rp.prototype.on=rp.prototype.u;rp.prototype.once=rp.prototype.B;rp.prototype.un=rp.prototype.v;rp.prototype.unByKey=rp.prototype.C;$g.prototype.bindTo=$g.prototype.O;$g.prototype.get=$g.prototype.get;$g.prototype.getKeys=$g.prototype.I;$g.prototype.getProperties=$g.prototype.L;$g.prototype.set=$g.prototype.set;$g.prototype.setProperties=$g.prototype.G;$g.prototype.unbind=$g.prototype.P;$g.prototype.unbindAll=$g.prototype.R;$g.prototype.changed=$g.prototype.l;$g.prototype.getRevision=$g.prototype.A;
+$g.prototype.on=$g.prototype.u;$g.prototype.once=$g.prototype.B;$g.prototype.un=$g.prototype.v;$g.prototype.unByKey=$g.prototype.C;ah.prototype.getMap=ah.prototype.d;ah.prototype.setMap=ah.prototype.setMap;ah.prototype.bindTo=ah.prototype.O;ah.prototype.get=ah.prototype.get;ah.prototype.getKeys=ah.prototype.I;ah.prototype.getProperties=ah.prototype.L;ah.prototype.set=ah.prototype.set;ah.prototype.setProperties=ah.prototype.G;ah.prototype.unbind=ah.prototype.P;ah.prototype.unbindAll=ah.prototype.R;
+ah.prototype.changed=ah.prototype.l;ah.prototype.getRevision=ah.prototype.A;ah.prototype.on=ah.prototype.u;ah.prototype.once=ah.prototype.B;ah.prototype.un=ah.prototype.v;ah.prototype.unByKey=ah.prototype.C;lh.prototype.getMap=lh.prototype.d;lh.prototype.setMap=lh.prototype.setMap;lh.prototype.bindTo=lh.prototype.O;lh.prototype.get=lh.prototype.get;lh.prototype.getKeys=lh.prototype.I;lh.prototype.getProperties=lh.prototype.L;lh.prototype.set=lh.prototype.set;lh.prototype.setProperties=lh.prototype.G;
+lh.prototype.unbind=lh.prototype.P;lh.prototype.unbindAll=lh.prototype.R;lh.prototype.changed=lh.prototype.l;lh.prototype.getRevision=lh.prototype.A;lh.prototype.on=lh.prototype.u;lh.prototype.once=lh.prototype.B;lh.prototype.un=lh.prototype.v;lh.prototype.unByKey=lh.prototype.C;mh.prototype.getMap=mh.prototype.d;mh.prototype.bindTo=mh.prototype.O;mh.prototype.get=mh.prototype.get;mh.prototype.getKeys=mh.prototype.I;mh.prototype.getProperties=mh.prototype.L;mh.prototype.set=mh.prototype.set;
+mh.prototype.setProperties=mh.prototype.G;mh.prototype.unbind=mh.prototype.P;mh.prototype.unbindAll=mh.prototype.R;mh.prototype.changed=mh.prototype.l;mh.prototype.getRevision=mh.prototype.A;mh.prototype.on=mh.prototype.u;mh.prototype.once=mh.prototype.B;mh.prototype.un=mh.prototype.v;mh.prototype.unByKey=mh.prototype.C;Ro.prototype.getMap=Ro.prototype.d;Ro.prototype.bindTo=Ro.prototype.O;Ro.prototype.get=Ro.prototype.get;Ro.prototype.getKeys=Ro.prototype.I;Ro.prototype.getProperties=Ro.prototype.L;
+Ro.prototype.set=Ro.prototype.set;Ro.prototype.setProperties=Ro.prototype.G;Ro.prototype.unbind=Ro.prototype.P;Ro.prototype.unbindAll=Ro.prototype.R;Ro.prototype.changed=Ro.prototype.l;Ro.prototype.getRevision=Ro.prototype.A;Ro.prototype.on=Ro.prototype.u;Ro.prototype.once=Ro.prototype.B;Ro.prototype.un=Ro.prototype.v;Ro.prototype.unByKey=Ro.prototype.C;dh.prototype.getMap=dh.prototype.d;dh.prototype.setMap=dh.prototype.setMap;dh.prototype.bindTo=dh.prototype.O;dh.prototype.get=dh.prototype.get;
+dh.prototype.getKeys=dh.prototype.I;dh.prototype.getProperties=dh.prototype.L;dh.prototype.set=dh.prototype.set;dh.prototype.setProperties=dh.prototype.G;dh.prototype.unbind=dh.prototype.P;dh.prototype.unbindAll=dh.prototype.R;dh.prototype.changed=dh.prototype.l;dh.prototype.getRevision=dh.prototype.A;dh.prototype.on=dh.prototype.u;dh.prototype.once=dh.prototype.B;dh.prototype.un=dh.prototype.v;dh.prototype.unByKey=dh.prototype.C;Xo.prototype.getMap=Xo.prototype.d;Xo.prototype.setMap=Xo.prototype.setMap;
+Xo.prototype.bindTo=Xo.prototype.O;Xo.prototype.get=Xo.prototype.get;Xo.prototype.getKeys=Xo.prototype.I;Xo.prototype.getProperties=Xo.prototype.L;Xo.prototype.set=Xo.prototype.set;Xo.prototype.setProperties=Xo.prototype.G;Xo.prototype.unbind=Xo.prototype.P;Xo.prototype.unbindAll=Xo.prototype.R;Xo.prototype.changed=Xo.prototype.l;Xo.prototype.getRevision=Xo.prototype.A;Xo.prototype.on=Xo.prototype.u;Xo.prototype.once=Xo.prototype.B;Xo.prototype.un=Xo.prototype.v;Xo.prototype.unByKey=Xo.prototype.C;
+fh.prototype.getMap=fh.prototype.d;fh.prototype.setMap=fh.prototype.setMap;fh.prototype.bindTo=fh.prototype.O;fh.prototype.get=fh.prototype.get;fh.prototype.getKeys=fh.prototype.I;fh.prototype.getProperties=fh.prototype.L;fh.prototype.set=fh.prototype.set;fh.prototype.setProperties=fh.prototype.G;fh.prototype.unbind=fh.prototype.P;fh.prototype.unbindAll=fh.prototype.R;fh.prototype.changed=fh.prototype.l;fh.prototype.getRevision=fh.prototype.A;fh.prototype.on=fh.prototype.u;fh.prototype.once=fh.prototype.B;
+fh.prototype.un=fh.prototype.v;fh.prototype.unByKey=fh.prototype.C;lp.prototype.getMap=lp.prototype.d;lp.prototype.bindTo=lp.prototype.O;lp.prototype.get=lp.prototype.get;lp.prototype.getKeys=lp.prototype.I;lp.prototype.getProperties=lp.prototype.L;lp.prototype.set=lp.prototype.set;lp.prototype.setProperties=lp.prototype.G;lp.prototype.unbind=lp.prototype.P;lp.prototype.unbindAll=lp.prototype.R;lp.prototype.changed=lp.prototype.l;lp.prototype.getRevision=lp.prototype.A;lp.prototype.on=lp.prototype.u;
+lp.prototype.once=lp.prototype.B;lp.prototype.un=lp.prototype.v;lp.prototype.unByKey=lp.prototype.C;qp.prototype.getMap=qp.prototype.d;qp.prototype.setMap=qp.prototype.setMap;qp.prototype.bindTo=qp.prototype.O;qp.prototype.get=qp.prototype.get;qp.prototype.getKeys=qp.prototype.I;qp.prototype.getProperties=qp.prototype.L;qp.prototype.set=qp.prototype.set;qp.prototype.setProperties=qp.prototype.G;qp.prototype.unbind=qp.prototype.P;qp.prototype.unbindAll=qp.prototype.R;qp.prototype.changed=qp.prototype.l;
+qp.prototype.getRevision=qp.prototype.A;qp.prototype.on=qp.prototype.u;qp.prototype.once=qp.prototype.B;qp.prototype.un=qp.prototype.v;qp.prototype.unByKey=qp.prototype.C;
+  return OPENLAYERS.ol;
+}));
+
diff --git a/VIPSWeb/static/js/forecastmap.js b/VIPSWeb/static/js/forecastmap.js
index 37bd862b0bd873caf0ae9bd988c1e56079f5935a..ab5a7cfc9f8d661acf3a28e2c3f59836914b702b 100644
--- a/VIPSWeb/static/js/forecastmap.js
+++ b/VIPSWeb/static/js/forecastmap.js
@@ -27,16 +27,14 @@ var map;
 var forecastLayer;
 
 /**
- * Initalizes the forecast map.
+ * Initializes the forecast map.
  * @param {ol.Coordinate} lonLat - coordinates for the map's center (WGS84)
  * @param {int} zoom - the zoom level (1-15, 1 is world wide view, 15 is greatest zoom)
- * @param {int} organizationId
- * @param {string} sourceHostName - host name for the initial KML file
  * @param {string} mapAttribution - The text in the corner of the map giving credits where it's due
  */
-function initForecastMap(lonLat, zoomLevel, organizationId, sourceHostname, mapAttribution)
+function initForecastMap(lonLat, zoomLevel, mapAttribution)
 {
-	if(sourceHostname === undefined)
+	if(settings.vipslogicServerName === undefined)
 	{
 		alert(gettext("Source hostname not defined."));
 		return;
@@ -65,7 +63,7 @@ function initForecastMap(lonLat, zoomLevel, organizationId, sourceHostname, mapA
 	
 	forecastLayer = new ol.layer.Vector({
 		source: new ol.source.KML({
-		    url: "http://" + sourceHostname + "/rest/forecastresults/aggregate/" + organizationId + "?" + buildPathParamString("cropOrganismId", getSelectedCropIds()),
+		    url: "http://" + settings.vipslogicServerName + "/rest/forecastresults/aggregate/" + settings.vipsOrganizationId + "?" + buildPathParamString("cropOrganismId", getSelectedCropIds()),
 			projection: ol.proj.get('EPSG:3857')
 		  })
 		});
@@ -156,7 +154,7 @@ function initForecastMap(lonLat, zoomLevel, organizationId, sourceHostname, mapA
 	    	  // Fetching information asynchronously from server 
 	    	  var request = $.ajax({
                     type:"GET",
-                    url: "http://" + sourceHostname + "/rest/forecastresults/latest/poi/" + feature.getId(),
+                    url: "http://" + settings.vipslogicServerName + "/rest/forecastresults/latest/poi/" + feature.getId(),
                     statusCode:{
                         200: function(data,textStatus, jqXHR){
                         	// Building result HTML
@@ -231,10 +229,10 @@ function initForecastMap(lonLat, zoomLevel, organizationId, sourceHostname, mapA
 			var pixel = map.getEventPixel(evt.originalEvent);
 			  displayFeatureDetails(pixel);
 			});
-		/*
+		
 		map.on('moveend', function(evt) {
-			  
-			});*/
+			updateForecastSummaries()
+			});
 	//For testing: adding mouseposition geographic control
 	/*
 	var mousePositionControl = new ol.control.MousePosition({
@@ -249,52 +247,38 @@ function initForecastMap(lonLat, zoomLevel, organizationId, sourceHostname, mapA
 }
 
 /**
- * Which crops have been selected? Checks the form
- * @returns
+ * 
+ * @returns the current zoom level of the map
  */
-function getSelectedCropIds()
+function getCurrentMapZoomLevel()
 {
-	selectedCropIds = [];
-	formFields = document.getElementsByName("cropIds");
-	for(i in formFields)
-	{
-		if(formFields[i].checked)
-		{
-			selectedCropIds = selectedCropIds.concat(eval(formFields[i].value));
-		}
-	}
-	return selectedCropIds;
+	return map.getView().getZoom();
 }
 
-/**
- * Builds a parameter string from an array of values
- * @param paramName
- * @param values
- * @returns {String}
- */
-function buildPathParamString(paramName, values)
-{
-	var pathParamString = "";
-	for(i in values)
-	{
-		pathParamString += "&" + paramName + "=" + values[i];
-	}
-	return pathParamString;
-}
 
 /**
  * Loads new KML info from VIPSLogic
- * @param organizationId
- * @param sourceHostname
  */
-function refreshForecasts(organizationId, sourceHostname)
+function updateForecastLayers()
 {
 	map.removeLayer(forecastLayer);
 	forecastLayer = new ol.layer.Vector({
 		source: new ol.source.KML({
-		    url: "http://" + sourceHostname + "/rest/forecastresults/aggregate/" + organizationId + "?" + buildPathParamString("cropOrganismId", getSelectedCropIds()),
+		    url: "http://" + settings.vipslogicServerName + "/rest/forecastresults/aggregate/" + settings.vipsOrganizationId + "?" + buildPathParamString("cropOrganismId", getSelectedCropIds()),
 			projection: "EPSG:3857"
 		  })
 		});
 	map.addLayer(forecastLayer);
+}
+
+/**
+ * 
+ * @param coordinate [longitude,latitude] 
+ * @param projection 
+ * @returns boolean
+ */
+function isCoordinateOnVisibleMap(coordinate, projection){
+	var mapExtent = map.getView().calculateExtent(map.getSize());
+	var coordinate = ol.proj.transform(coordinate, 'EPSG:4326', map.getView().getProjection().getCode())
+	return ol.extent.containsCoordinate(mapExtent,coordinate);
 }
\ No newline at end of file
diff --git a/VIPSWeb/static/js/frontpage.js b/VIPSWeb/static/js/frontpage.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c0f2993f7e1c9d65f944e8716fbee7d36e3fb45
--- /dev/null
+++ b/VIPSWeb/static/js/frontpage.js
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. 
+ * 
+ * This file is part of VIPSWeb.
+ * VIPSWeb is free software: you can redistribute it and/or modify
+ * it under the terms of the Bioforsk Open Source License as published by 
+ * Bioforsk, either version 1 of the License, or (at your option) any
+ * later version.
+ * 
+ * VIPSWeb 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
+ * Bioforsk Open Source License for more details.
+ * 
+ * You should have received a copy of the Bioforsk Open Source License
+ * along with VIPSWeb.  If not, see <http://www.bioforsk.no/licenses/>.
+ * 
+ */
+
+/**
+ * Interactive stuff on the VIPSWeb front page. Code for
+ * map is found in ./forecastmap.js 
+ * 
+ * Depending on /currentLanguage.js
+ *
+ * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no>
+ */
+
+
+/**
+ * Which crops have been selected? Checks the form
+ * @returns
+ */
+function getSelectedCropIds()
+{
+	selectedCropIds = [];
+	formFields = document.getElementsByName("cropIds");
+	for(i in formFields)
+	{
+		if(formFields[i].checked)
+		{
+			selectedCropIds = selectedCropIds.concat(eval(formFields[i].value));
+		}
+	}
+	return selectedCropIds;
+}
+
+/**
+ * Converts JSON data of forecast configurations into HTML and 
+ * renders it to the table with id="forecastSummariesTable"
+ * 
+ * @param forecastConfigurations
+ */
+function renderForecastConfigurationSummaries(forecastConfigurations)
+{
+	systemTime = getSystemTime();
+	//console.log(systemTime.format());
+	var forecastSummariesTable = document.getElementById("forecastSummariesTable");
+	var summariesHTML = [];
+	for(var i in forecastConfigurations)
+	{
+		var forecastConfiguration = forecastConfigurations[i];
+		//console.log("ModelId=" + forecastConfiguration.modelId);
+		
+		// TODO: Get correct headline
+		summariesHTML.push("<tbody class=\"forecastSummaryRowGroup\" onclick=\"viewForecastResults(" + forecastConfiguration.forecastConfigurationId + ")\">");
+		summariesHTML.push("<tr><td colspan=\"7\">");
+		summariesHTML.push(forecastConfiguration.locationPointOfInterestId.name);
+		if(forecastConfiguration.weatherStationPointOfInterestId.name !== forecastConfiguration.locationPointOfInterestId.name)
+		{
+			summariesHTML.push(" <em>" + forecastConfiguration.weatherStationPointOfInterestId.name + "</em>");
+		}
+		summariesHTML.push(", " + getLocalizedOrganismName(forecastConfiguration.cropOrganismId));
+		summariesHTML.push(", " + getLocalizedOrganismName(forecastConfiguration.pestOrganismId));
+		summariesHTML.push(", " + modelLocalNames[forecastConfiguration.modelId]);
+		summariesHTML.push("</td></tr>");
+		
+		// Get correct list of summaries
+		var twoDaysAgo = getSystemTime().subtract(2,"days");
+		var fourDaysAhead = getSystemTime().add(4,"days");
+		var forecastSummaries = getSortedAndFilteredForecastSummaries(forecastConfiguration.forecastSummaries,twoDaysAgo,fourDaysAhead);
+		
+		// Row with symbols and colors
+		summariesHTML.push("<tr>");
+		for(var j in forecastSummaries)
+		{
+			var summary = forecastSummaries[j];
+			var statusText = getLocalizedStatusText(summary.warningStatus);
+			summariesHTML.push("<td class=\"forecastStatus s" + summary.warningStatus + "\" title=\"" + statusText + "\"></td>");
+		}
+		summariesHTML.push("</tr>");
+		// Row with dates
+		summariesHTML.push("<tr class=\"forecastSummaryDateRow\">");
+		for(var j in forecastSummaries)	
+		{
+			var summary = forecastSummaries[j];
+			var summaryDate = moment(summary.forecastSummaryPK.summaryForDate);
+			summariesHTML.push(
+					"<td class=\"" + (summaryDate.isSame(getSystemTime(),"day") ? "forecastSummaryCellToday":"") + "\">"
+					+ summaryDate.format("MM-DD")
+					+ "</td>"
+			);
+		}
+		summariesHTML.push("</tr></tbody>");
+	}
+	forecastSummariesTable.innerHTML = summariesHTML.join("");
+	forecastSummariesTable.style.display="block";
+}
+
+/**
+ * Picks the correct summaries, fills in empty ones where missing, sorts by date ascending
+ * @param allForecastSummaries
+ * @param firstDay
+ * @param lastDay
+ * @returns array of the selected summaries
+ */
+function getSortedAndFilteredForecastSummaries(allForecastSummaries,firstDay,lastDay)
+{
+	/*console.log(firstDay.format() + "-" + lastDay.format());
+	console.log("Difference: " + lastDay.diff(firstDay,"days"));
+	console.log(allForecastSummaries.length);*/
+	var retVal = Array();
+	// Filtering first
+	for(var i in allForecastSummaries)
+	{
+		var summary = allForecastSummaries[i]
+		var summaryDate = moment(summary.forecastSummaryPK.summaryForDate);
+		if(
+				summaryDate.isSame(firstDay,"day")
+				|| summaryDate.isSame(lastDay,"day")
+				|| (summaryDate.isAfter(firstDay,"day") && summaryDate.isBefore(lastDay,"day"))
+		)
+		{
+			retVal.push(summary);
+		}
+	}
+	// Check for missing dates
+	if(retVal.length < lastDay.diff(firstDay,"days") + 1)
+	{
+		// Find the missing date(s)
+		var currentDate = firstDay.clone();
+		while(currentDate.isBefore(lastDay) || currentDate.isSame(lastDay))
+		{
+			var dateFound = false;
+			for(var i in retVal)
+			{
+				if(moment(retVal[i].forecastSummaryPK.summaryForDate).isSame(currentDate,"day"))
+				{
+					dateFound = true;
+				}
+			}
+			if(!dateFound)
+			{
+				var emptySummary = {
+						"forecastSummaryPK":{
+							"forecastConfigurationId":0,
+							"summaryForDate":currentDate.format("YYYY-MM-DD")},
+						"summaryCreatedTime":0,
+						"warningStatus":0
+						};
+				retVal.push(emptySummary);
+			}
+			currentDate.add(1,"days");
+		}
+	}
+	// Sorting
+	retVal.sort(sortForecastSummaries);
+	return retVal;
+}
+
+var sortForecastSummaries = function(a,b){
+	var momentA = moment(a.forecastSummaryPK.summaryForDate);
+	var momentB = moment(b.forecastSummaryPK.summaryForDate);
+	if(momentA.isBefore(momentB))
+	{
+		return -1;
+	}
+	if(momentA.isSame(momentB))
+	{
+		return 0;
+	};
+	return 1;
+}
+
+/**
+ * Returns the localized status message
+ * @param warningStatus
+ * @returns String
+ */
+function getLocalizedStatusText(warningStatus) 
+{
+	switch(warningStatus)
+	{
+		case 0:
+			return gettext("No forecast available");
+		case 1:
+			return gettext("Missing data");
+		case 2:
+			return gettext("No risk of infection");
+		case 3:
+			return gettext("Medium risk of infection");
+		case 4:
+			return gettext("High risk of infection");
+		default:
+			return gettext("Invalid forecast status");
+	}
+}
+
+/**
+ * Depends on the value of currentLanguage and defaultLanguage in /currentLanguage.js
+ * @param forecastConfiguration
+ * @returns {String}
+ */
+function getLocalizedOrganismName(organism)
+{
+	// Fallback in case nothing works
+	if(organism == null)
+	{
+		return gettext("Unnamed");
+	}
+	// Attempting the following languages (in order): current language, default language, English
+	var languages = [settings.currentLanguage, settings.defaultLanguage, "en"];
+	for(var j in languages)
+	{
+		for(var i in organism.organismLocaleSet)
+		{
+			localeSet = organism.organismLocaleSet[i];
+			//console.log(localeSet);
+			if(localeSet.organismLocalePK.locale === languages[j])
+			{
+				return localeSet.localName;
+			}
+		}
+	}
+	// Then we try the latin name
+	if(organism.latinName !== null 
+			&& organism.latinName !== "")
+	{
+		return organism.latinName;
+	}
+	// Then the trade name
+	if(organism.tradeName !== null
+			&& organism.tradeName !== "")
+	{
+		return organism.tradeName;
+	}
+	// Then we give up
+	return gettext("Unnamed");
+}
+
+
+/**
+ * Navigation to details page for forecast results
+ * @param forecastConfigurationId
+ */
+function viewForecastResults(forecastConfigurationId)
+{
+	window.location.href="/forecasts/" + forecastConfigurationId;
+}
+
+/**
+ * Refreshes forecast lists based on selected crops
+ */
+function refreshForecasts()
+{
+	updateForecastLayers();
+	updateForecastSummaries();
+	
+}
+
+// The globally available caching of forecast summaries
+var cachedForecastSummaries;
+
+/**
+ * Retrieves all the forecast summaries for further filtering
+ * by JavaScript
+ */
+function cacheForecastSummaries()
+{
+	$.getJSON( "http://" + settings.vipslogicServerName + "/rest/forecastconfigurationsummaries/" + settings.vipsOrganizationId, function( json ) {
+		  cachedForecastSummaries = json;
+		  updateForecastSummariesHeading(cachedForecastSummaries.length);
+		  });
+	
+}
+
+/**
+ * 
+ * @param numberOfForecastSummaries
+ */
+function updateForecastSummariesHeading(numberOfForecastSummaries)
+{
+	document.getElementById("numberOfForecastSummaries").innerHTML=numberOfForecastSummaries;
+}
+
+/**
+ *  
+ */
+function updateForecastSummaries()
+{
+	currentMapZoomlevel = getCurrentMapZoomLevel();
+	// Display forecasts when zoom level is closer than default, and from there on
+	//console.log(mapZoomlevel + "<=" + settings.mapZoomlevel + " ? " + (mapZoomlevel <= settings.mapZoomlevel));
+	//console.log("tableDisplay=" + getElementComputedDisplay(document.getElementById("forecastSummariesTable")));
+	if(currentMapZoomlevel <= settings.mapZoomlevel && getElementComputedDisplay(document.getElementById("forecastSummariesTable")) == "none")
+	{
+		return;
+	}
+	
+	// Iterate all forecast summaries, filter by crop (must be among the selected crops)
+	// and location on the map (must be visible on the map) 
+	var selectedCropIds = getSelectedCropIds();
+	var filteredForecastSummaries = [];
+	for(var i in cachedForecastSummaries)
+	{
+		var forecastSummary = cachedForecastSummaries[i];
+		// Filter by crop
+		var isASelectedCrop = false;
+		for(var j in selectedCropIds)
+		{
+			if(selectedCropIds[j] === forecastSummary.cropOrganismId.organismId)
+			{
+				isASelectedCrop = true;
+			}
+		}
+		if(!isASelectedCrop)
+		{
+			continue;
+		}
+		// Filter by map visibility
+		var coordinate = [
+		                  forecastSummary.locationPointOfInterestId.longitude,
+		                  forecastSummary.locationPointOfInterestId.latitude
+		                  ];
+		if(isCoordinateOnVisibleMap(coordinate,'EPSG:4326'))
+		{
+			filteredForecastSummaries.push(forecastSummary);
+		}
+	}
+	renderForecastConfigurationSummaries(filteredForecastSummaries);
+	updateForecastSummariesHeading(filteredForecastSummaries.length);
+}
\ No newline at end of file
diff --git a/VIPSWeb/static/js/util.js b/VIPSWeb/static/js/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..ff3273bc23d0778a867068d46eb62a4314c48d5d
--- /dev/null
+++ b/VIPSWeb/static/js/util.js
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. 
+ * 
+ * This file is part of VIPSWeb.
+ * VIPSWeb is free software: you can redistribute it and/or modify
+ * it under the terms of the Bioforsk Open Source License as published by 
+ * Bioforsk, either version 1 of the License, or (at your option) any
+ * later version.
+ * 
+ * VIPSWeb 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
+ * Bioforsk Open Source License for more details.
+ * 
+ * You should have received a copy of the Bioforsk Open Source License
+ * along with VIPSWeb.  If not, see <http://www.bioforsk.no/licenses/>.
+ * 
+ */
+
+/**
+ * Utility JavaScript. Used here and there. To whom it may concern... 
+ *
+ * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no>
+ */
+
+/**
+ * Builds a parameter string from an array of values
+ * @param paramName
+ * @param values
+ * @returns {String}
+ */
+function buildPathParamString(paramName, values)
+{
+	var pathParamString = "";
+	for(i in values)
+	{
+		pathParamString += "&" + paramName + "=" + values[i];
+	}
+	return pathParamString;
+}
+
+var systemTimeOffsetMonths = -6;
+
+/**
+ * 
+ * @returns the current system time as a Moment.js object
+ */
+function getSystemTime(){
+	return systemTime = moment().add(systemTimeOffsetMonths,"months");
+}
+
+function setMomentToMidnight(momentObj)
+{
+	return momentObj.hour(0).minute(0).second(0).millisecond(0);
+}
+
+/**
+ * Returns the computed CSS display of the element. Handling both IE
+ * and the normal browsers
+ * @param theElement
+ * @returns {String} the computed display value
+ */
+function getElementComputedDisplay(theElement)
+{
+	return theElement.currentStyle ? theElement.currentStyle.display :
+        getComputedStyle(theElement, null).display;
+}
\ No newline at end of file
diff --git a/VIPSWeb/templates/base.html b/VIPSWeb/templates/base.html
index 98326cb12099230d93c8a0516e27fa0ca62041f6..19281f1637815eb0b0a77eb629348657f48a8791 100644
--- a/VIPSWeb/templates/base.html
+++ b/VIPSWeb/templates/base.html
@@ -19,7 +19,7 @@
  * 
  */
  
-{% endcomment %}{% load i18n staticfiles %}<!DOCTYPE html>
+{% endcomment %}{% load i18n staticfiles template_helper %}<!DOCTYPE html>
 <html lang="no">
 <head>
 	<meta charset="utf-8">
@@ -57,22 +57,23 @@
 	
 	  <!-- Collect the nav links, forms, and other content for toggling -->
 	  <div class="collapse navbar-collapse navbar-ex1-collapse">
-	    <ul class="nav navbar-nav">
-	      <li><a href="/forecasts">{% trans "Forecasts" %}</a></li>
-	      <li><a href="/messages">{% trans "Messages" %}</a></li>
-	    </ul>
-	    {% get_current_language as LANGUAGE_CODE %}
-	      <form class="navbar-form navbar-right" action="/i18n/setlang/" method="post">
-			{% csrf_token %}
-			<input name="next" type="hidden" value="{{ redirect_to }}" />
-			<select name="language" onchange="this.form.submit();">
-			{% get_language_info_list for settings.AVAILABLE_LANGUAGES as languages %}
-			{% for language in languages %}
-			<option value="{{ language.code }}" {% if language.code == LANGUAGE_CODE %} selected="selected" {% endif %}>{{ language.name_local }} ({{ language.code }})</option>
-			{% endfor %}
-			</select>
-			<!--button type="submit" class="btn btn-default" >Select language </button-->
-			</form>
+	    <ul class="nav navbar-nav navbar-right">
+	      	{% generate_main_menu %}
+	    	<li>
+	    	{% get_current_language as LANGUAGE_CODE %}
+		      <form class="navbar-form navbar-right" action="/i18n/setlang/" method="post">
+				{% csrf_token %}
+				<input name="next" type="hidden" value="{{ redirect_to }}" />
+				<select name="language" class="form-control languageSelect" onchange="this.form.submit();">
+				{% get_language_info_list for settings.AVAILABLE_LANGUAGES as languages %}
+				{% for language in languages %}
+				<option value="{{ language.code }}" {% if language.code == LANGUAGE_CODE %} selected="selected" {% endif %}>{{ language.name_local }} ({{ language.code }})</option>
+				{% endfor %}
+				</select>
+				<!--button type="submit" class="btn btn-default" >Select language </button-->
+				</form>
+			</li>
+		 </ul>
 	  </div><!-- /.navbar-collapse -->
 	</nav>
 
@@ -87,9 +88,9 @@
 {% block pageContentWrapEnd%}
 		</div><!-- End contents container -->
 	</div><!-- End row with sidebar and contents container -->
-	<hr>
 	<footer>
 		<p>&copy; {% now "Y" %} <a href="{{settings.SITE_OWNER_URL}}" target="new">{{settings.SITE_OWNER_NAME}}</a></p>
+		{% get_footer_text_i18n %}
 	</footer>
 </div>
 <!-- end contentwrapper-->
diff --git a/VIPSWeb/templates/index.html b/VIPSWeb/templates/index.html
index 1a86ad21a3cc492359cdfd840bd354b8adcb9ac4..f23ae2da37208b45d2f3e5a32a3a3df22ff9b4d6 100644
--- a/VIPSWeb/templates/index.html
+++ b/VIPSWeb/templates/index.html
@@ -27,16 +27,25 @@
 <link rel="stylesheet" href="{% static "css/3rdparty/ol.css" %}" type="text/css">
 {% endblock %}
 {% block customJS %}
-<script type="text/javascript" src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
-<script type="text/javascript" src="{% static "js/3rdparty/ol.js" %}"></script>
+<script type="text/javascript" src="{% url "django.views.i18n.javascript_catalog" %}"></script>
+<script type="text/javascript" src="{% static "js/3rdparty/ol-debug.js" %}"></script>
 <script type="text/javascript" src="/forecasts/models/js/modelLocalNames.js"></script>
 <script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script>
+<script type="text/javascript" src="{% static "js/util.js" %}"></script>
+<script type="text/javascript" src="{% url "views.settings_js" %}"></script>
+<script type="text/javascript" src="{% static "js/frontpage.js" %}"></script>
 <script type="text/javascript" src="{% static "js/forecastmap.js" %}"></script>
+<!-- Mock! -->
+<script type="text/javascript" src="{% static "test/forecastSummaries.js" %}"></script>
+<!-- End of Mock -->
 <script type="text/javascript">
 	$(document).ready(function() {
 		// Init frontpage map. Depending on forecastmap.js.
-		initForecastMap([{{settings.MAP_CENTER_LONGITUDE|unlocalize}},{{settings.MAP_CENTER_LATITUDE|unlocalize}}],{{settings.MAP_ZOOMLEVEL}},{{settings.VIPS_ORGANIZATION_ID}},"{{settings.VIPSLOGIC_SERVER_NAME}}","{{settings.MAP_ATTRIBUTION|safe}}");
-		//initForecastMap();
+		initForecastMap([{{settings.MAP_CENTER_LONGITUDE|unlocalize}},{{settings.MAP_CENTER_LATITUDE|unlocalize}}],{{settings.MAP_ZOOMLEVEL}},"{{settings.MAP_ATTRIBUTION|safe}}");
+		// Mock!
+		//renderForecastConfigurationSummaries(forecastSummaries);
+		// End of Mock
+		cacheForecastSummaries();
 	});
 </script>
 {% endblock %}
@@ -44,7 +53,7 @@
 	<div class="row">
 		<!-- Start map container -->
 		<div class="col-md-8">
-			<div id="map" class="map">
+			<div id="map" class="map" style="height: {{settings.MAP_HEIGHT}}px;">
 				<!-- Info window box -->
 				<div id="tooltip"></div>
 				<div id="popover"></div>
@@ -52,18 +61,22 @@
 		</div><!-- End map container -->
 		<div class="col-md-4">
 			<h1>{% trans "Crops" %}</h1>
-			<div class="row">
-				<div class="col-md-6">
-					<ul class="list-group">
-						{% for crop_group in crop_groups %}
-						<li class="list-group-item"><input type="checkbox" name="cropIds" value="{{crop_group.crop_ids}}" checked="checked" onchange="refreshForecasts({{settings.VIPS_ORGANIZATION_ID}},'{{settings.VIPSLOGIC_SERVER_NAME}}')"/>{{ crop_group.name }}</li>
-						{% endfor %}
-					</ul>
-				</div>
-				<div class="col-md-6">
-					
-				</div>
-			</div>
+			
+				<ul class="cropList double">
+					{% for crop_group in crop_groups %}
+					<li>
+						<input type="checkbox" name="cropIds" id="cropIds_{{crop_group.crop_ids|slugify}}" value="{{crop_group.crop_ids}}" checked="checked" onchange="refreshForecasts({{settings.VIPS_ORGANIZATION_ID}},'{{settings.VIPSLOGIC_SERVER_NAME}}')"/>
+						<label for="cropIds_{{crop_group.crop_ids|slugify}}"><span></span>{{ crop_group.name }}</label>
+					</li>
+					{% endfor %}
+				</ul>
+			<div class="forecastSummaries">
+			<h1>{% trans "Forecasts" %} (<span id="numberOfForecastSummaries">...</span>)</h1>
+			
+        <table class="table-responsive" id="forecastSummariesTable">
+             
+        </table>
+		</div>
 			<h1>{% trans "Messages" %}</h1>
 			<table class="table table-hover table-condensed table-striped">
 				{% for message_tag in message_tags %}
diff --git a/VIPSWeb/templates/settings.js b/VIPSWeb/templates/settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..b103aaec087bf9727ec926f593d93cad7b98d757
--- /dev/null
+++ b/VIPSWeb/templates/settings.js
@@ -0,0 +1,16 @@
+{% load i18n %}
+{% get_current_language as CURRENT_LANGUAGE %}
+/**
+ * These are global configuration variables, dynamically generated by
+ * Django and made accessible for JavaScript
+ */
+
+var settings = {
+		currentLanguage: "{{CURRENT_LANGUAGE}}", // Computed by Django's i18n middleware
+		languageCode: "{{settings.LANGUAGE_CODE}}", // Default language of Django application
+		
+		vipsOrganizationId: {{settings.VIPS_ORGANIZATION_ID}},
+		vipslogicServerName: "{{settings.VIPSLOGIC_SERVER_NAME}}",
+		
+		mapZoomlevel: {{settings.MAP_ZOOMLEVEL}}
+};
diff --git a/VIPSWeb/templatetags/__init__.py b/VIPSWeb/templatetags/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/VIPSWeb/templatetags/template_helper.py b/VIPSWeb/templatetags/template_helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..7244b0ce268aac798faf4e192a0c45ae955f40f7
--- /dev/null
+++ b/VIPSWeb/templatetags/template_helper.py
@@ -0,0 +1,65 @@
+from django import template
+from django.utils.translation import ugettext as _
+from django.utils import translation
+from django.conf import settings
+
+register = template.Library()
+
+# Reads the variable local_settings.MAIN_MENU
+# Parses it, returns HTML
+@register.simple_tag(takes_context=True)
+def generate_main_menu(context):
+	# Get the request object
+	try:
+		request = context['request']
+	except KeyError:
+		request = context['view'].request
+	menu_html = ''
+	# Iterating the horizontal elements
+	for menu_item in settings.MAIN_MENU:
+		# If URL given, it's a main menu link
+		if menu_item.get("url",None) != None:
+			# Adding the active class attribute if url matches request path
+			active_class_attr = ' class="currentLink"' if request.path.startswith(menu_item["url"]) else ''
+			menu_html += '<li><a href="%s"%s>%s</a></li>' % (menu_item["url"], active_class_attr, _(menu_item["label"]))
+		# Else, it's a dropdown menu
+		else:
+			dropdown_html = ""
+			child_item_is_active = False
+			# Iterating through the vertical elements
+			for menu_child_item in menu_item["child_items"]:
+				# If url of this page matches request.path, add the active class
+				# AND flag that this dropdown menu contains the active item
+				active_class_attr = ' class="currentLink"' if request.path.startswith(menu_child_item["url"]) else ''
+				if request.path.startswith(menu_child_item["url"]):
+					child_item_is_active = True
+				dropdown_html += '<li><a href="%s">%s</a></li>' % (menu_child_item["url"], _(menu_child_item["label"]))
+			# If this dropdown meny contains the active item:
+			# add an active class to the dropdown item
+			active_class_attr = ' currentLink' if child_item_is_active else ''
+			# Prepending the list item
+			dropdown_html = """<li class="dropdown">
+                <a href="#" class="dropdown-toggle%s" data-toggle="dropdown" role="button" aria-expanded="false">%s <span class="caret"></span></a>
+                <ul class="dropdown-menu" role="menu">""" % (active_class_attr, _(menu_item["label"])) + dropdown_html
+            # Wrap up the HTML
+			dropdown_html += "</ul></li>"
+			menu_html += dropdown_html
+	return menu_html
+
+# Get the footer text in correct language
+@register.simple_tag()
+def get_footer_text_i18n():
+	# Absolute fallback is the empty string
+	footer_html = None
+	# Languages in preferred order: user's selected language, default language of site, English
+	language_candidates = [translation.get_language(), settings.LANGUAGE_CODE, "en"]
+	for language_candidate in language_candidates:
+		if settings.FOOTER_HTML.get(language_candidate, None) != None:
+			footer_html = settings.FOOTER_HTML.get(language_candidate, footer_html)
+			break
+	# Fallback: First item in list
+	if footer_html == None and len(settings.FOOTER_HTML.keys()) > 0:
+		footer_html = settings.FOOTER_HTML[settings.FOOTER_HTML.keys()[0]]
+	
+	return footer_html if footer_html != None else ""
+	
\ No newline at end of file
diff --git a/VIPSWeb/urls.py b/VIPSWeb/urls.py
index fa42198de8e5e49ab09766e0f85401eb6c8ca0c8..472f81d8cd82f9e66a185dd4ffb8a97288274ef9 100644
--- a/VIPSWeb/urls.py
+++ b/VIPSWeb/urls.py
@@ -49,9 +49,10 @@ urlpatterns = patterns('',
     # Static serving of media files
     url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
     # Enabling translation in JavaScript files
-    # See https://docs.djangoproject.com/en/1.5/topics/i18n/translation/#internationalization-in-javascript-code
+    # See https://docs.djangoproject.com/en/1.6/topics/i18n/translation/#internationalization-in-javascript-code
     (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
     (r'^i18n/', include('django.conf.urls.i18n')),
+    (r'^settings.js', views.settings_js),
     # Default view is index
     url(r'^$', views.index, name='index'),
     #url(r'^$', TemplateView.as_view(template_name="index.html"))
diff --git a/VIPSWeb/views.py b/VIPSWeb/views.py
index 2314092487d97b7c13dba3c05f31830aa746ee0b..32983910ebd76482810105462ec2d5f9f768eef9 100644
--- a/VIPSWeb/views.py
+++ b/VIPSWeb/views.py
@@ -42,5 +42,11 @@ def index(request):
               }
     return render(request, 'index.html', context)
 
+# Serving settings for JavaScript
+def settings_js(request):
+    context = {}
+    return render(request,'settings.js', context, content_type="application/javascript;charset=UTF-8")
+
+## TODO: Is this being used???
 def resourcebundle(request):
     return render(request, 'resourcebundle.js',content_type="application/javascript")
\ No newline at end of file
diff --git a/forecasts/models.py b/forecasts/models.py
index 645857848bd8e872514ee3a9d3a17eeebecfd64a..ea2c1d25859768314f8fc11fa8970d9172343b31 100644
--- a/forecasts/models.py
+++ b/forecasts/models.py
@@ -130,10 +130,13 @@ class ForecastConfiguration:
         self.weather_station_point_of_interest = weather_station_point_of_interest
         self.user_id = user_id
         self.crop_organism = crop_organism
+        self.forecast_summaries = []
     
     def set_model_local_name(self, model_local_name):
         self.model_local_name = model_local_name
         
+    def set_forecast_summaries(self, forecast_summaries):
+        self.forecast_summaries = forecast_summaries
     
     """
     Getting information from persistence layer
@@ -180,6 +183,38 @@ class ForecastConfiguration:
         requestResult = requests.get("http://%s/rest/forecastconfigurations/%s" % (settings.VIPSLOGIC_SERVER_NAME, forecast_configuration_id))
         return requestResult.json()
     
+    @staticmethod
+    def get_forecast_configurations_with_summaries(crop_organism_ids):
+        forecasts = []
+        model_local_names = {}
+    
+        for item in ForecastConfiguration.get_forecast_configurations_with_summaries_as_json(None):
+            forecast = ForecastConfiguration.get_instance_from_dict(item)
+            forecast.set_forecast_summaries(item.get("forecastSummaries", []))
+            # Get the name of the model in the current locale
+            if model_local_names.get(forecast.model_id, None) == None:
+                model_local_names[forecast.model_id] = Model.get_local_name_for_model(forecast.model_id)
+            forecast.set_model_local_name(model_local_names[forecast.model_id])
+            # Append forecast to list   
+            forecasts.append(forecast)
+        return forecasts
+    
+    @staticmethod
+    def get_forecast_configurations_with_summaries_as_json(crop_organism_ids):
+        """
+        crop_organism_id_paramstring = ""
+        if crop_organism_ids != None:
+            for crop_organism_id in crop_organism_ids:
+                crop_organism_id_paramstring += "&cropOrganismId=%s" % crop_organism_id
+        """
+            
+        request_result = requests.get("http://%s/rest/forecastconfigurationsummaries/%s" % (
+                                                                              settings.VIPSLOGIC_SERVER_NAME, 
+                                                                              settings.VIPS_ORGANIZATION_ID
+                                                                              )
+                                     )
+        return request_result.json()
+    
     @staticmethod
     def get_instance_from_dict(theDict):
         instance = ForecastConfiguration(
diff --git a/messages/templates/messages/index.html b/messages/templates/messages/index.html
index 10354d5dc76f93ab3b59f2095dcd3bc77af34893..26991c1e2a822a7c78ca100d0982e5571dd64b41 100644
--- a/messages/templates/messages/index.html
+++ b/messages/templates/messages/index.html
@@ -3,7 +3,6 @@
 {% block title%}{% trans "Messages" %}{%endblock%}
 {% block content %}
 <h1>{% trans "Messages" %}</h1>
-
 <div class="row">
 	<div class="col-xs-12">
 		<div class="input-group">
diff --git a/messages/urls.py b/messages/urls.py
index e734cff1fb953b0f966e4c97f82807cc6f57610f..b8ed55de95999b2ffe77ecee25662dbaf7cb8f21 100644
--- a/messages/urls.py
+++ b/messages/urls.py
@@ -21,8 +21,8 @@ from django.conf.urls import patterns, url
 from messages import views
 
 urlpatterns = patterns('',
-    # ex: /forecasts/                   
+    # ex: /messages/                   
     url(r'^$', views.index, name='index'),
-    # ex: /forecasts/5/
+    # ex: /messages/5/
     url(r'^(?P<message_id>\d+)/$', views.detail, name='detail'),
 )
\ No newline at end of file